Module: Smacro

Defined in:
lib/smacro.rb,
lib/smacro/cli.rb,
lib/smacro/version.rb

Defined Under Namespace

Modules: CLI Classes: Collector

Constant Summary collapse

MACROS =
{}
VERSION =
"0.1.0"

Class Method Summary collapse

Class Method Details

.const_value(node) ⇒ Object

AST literal -> Ruby value. Anything not known at build time is an error.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/smacro.rb', line 16

def self.const_value(node)
  case node
  when Prism::SymbolNode  then node.unescaped.to_sym
  when Prism::StringNode  then node.unescaped
  when Prism::IntegerNode then node.value
  when Prism::TrueNode    then true
  when Prism::FalseNode   then false
  when Prism::NilNode     then nil
  when Prism::ArrayNode   then node.elements.map { |e| const_value(e) }
  else
    raise ArgumentError,
          "macro arg is not a compile-time literal (#{node.class}) at line #{node.location.start_line}"
  end
end

.define(name, &blk) ⇒ Object



11
12
13
# File 'lib/smacro.rb', line 11

def self.define(name, &blk)
  MACROS[name] = blk
end

.expand(src) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/smacro.rb', line 47

def self.expand(src)
  collector = Collector.new
  Prism.parse(src).value.accept(collector)

  bytes = src.b # Prism offsets are byte offsets
  collector.hits.sort_by { |n| -n.location.start_offset }.each do |n|
    args = (n.arguments ? n.arguments.arguments : []).map { |a| const_value(a) }
    code = MACROS[n.name].call(*args).to_s
    # Collapse to one line with ";" so every line number stays identical
    # to the original file (free source map for error messages).
    oneline = code.lines.map(&:strip).reject(&:empty?).join("; ")
    bytes[n.location.start_offset, n.location.length] = oneline.b
  end
  bytes.force_encoding(Encoding::UTF_8)
end