Class: Koota::Compiler

Inherits:
Object
  • Object
show all
Includes:
VM::Opcodes
Defined in:
lib/koota/compiler.rb

Overview

Compiles an AST as returned by Parser#call into bytecode to be consumed by VM#call.

Constant Summary

Constants included from VM::Opcodes

VM::Opcodes::CALL, VM::Opcodes::HALT, VM::Opcodes::JRND, VM::Opcodes::JUMP, VM::Opcodes::PICK, VM::Opcodes::PUT, VM::Opcodes::RET

Instance Method Summary collapse

Instance Method Details

#call(ast, refs = {}) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/koota/compiler.rb', line 13

def call(ast, refs = {})
  @memory = []
  @refs   = refs
  @links  = {
    calls: {},
    picks: {}
  }
  @name = ''

  compile(ast)

  (@memory << HALT).tap do
    link_calls(@links[:calls])
    link_picks(@links[:picks])
  end
end

#compile_atom(atom) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/koota/compiler.rb', line 123

def compile_atom(atom)
  atom.each_char do |char|
    symbolified_char = char.to_sym

    # The name check ensures we aren't calling ourselves.
    if @refs[symbolified_char] && @name != symbolified_char
      # This will be linked to the real offset in a later stage (aka
      # `link_calls`). For now, we use zero as a placeholder and store the
      # current offset in `@links`. This is needed because we don't know
      # what offset to use before all the code is compiled.
      @links[:calls][@memory.length] = symbolified_char
      add_bytecode(CALL, 0, 0)
    else
      add_bytecode(PUT, *Encode.utf8(char))
    end
  end
end

#compile_choice(*choices) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/koota/compiler.rb', line 75

def compile_choice(*choices)
  # First of all, we need to emit a pick opcode. The offset to the pick
  # list is a placeholder for the same reasons calls use placeholders.
  pick_offset = @memory.length
  add_bytecode(PICK, 0, 0)

  # We now loop through each choice and compile it. We also add a jump to
  # the end of the "choice area" after each choice (except the last, since
  # it's just gonna be a jump into the next opcode) in order to skip the
  # rest of the choices. The jumps use a placeholder too, which will be
  # linked after the loop using the `jumps` array.
  jumps   = []
  offsets = []
  choices.each_with_index do |choice, i|
    # We need the offset for each choice for the pick list.
    offsets << @memory.length
    compile(choice)

    # The last choice doesn't need a jump, since it's just gonna be a jump
    # to the next opcode.
    unless i == choices.length - 1
      jumps << @memory.length
      add_bytecode(JUMP, 0, 0)
    end
  end

  # Link the jumps.
  encoded_jump = Encode.short(@memory.length)
  jumps.each do |offset|
    @memory[offset + 1], @memory[offset + 2] = encoded_jump
  end

  # Add the pick offset and the choice offsets to the pick links to be
  # processed later, in `link_picks`.
  @links[:picks][pick_offset] = offsets
end

#compile_maybe(maybe) ⇒ Object



112
113
114
115
116
117
118
119
120
121
# File 'lib/koota/compiler.rb', line 112

def compile_maybe(maybe)
  # A maybe is compiled down to a jrnd pointing after its contents. We can
  # link the offset immediately since we know the length of the maybe.
  jrnd_offset = @memory.length
  add_bytecode(JRND, 0, 0)

  compile(maybe)

  @memory[jrnd_offset + 1], @memory[jrnd_offset + 2] = Encode.short(@memory.length)
end

#compile_pattern(*sequence) ⇒ Object



71
72
73
# File 'lib/koota/compiler.rb', line 71

def compile_pattern(*sequence)
  sequence.each { |it| compile(it) }
end

#compile_raw(raw) ⇒ Object



141
142
143
144
145
# File 'lib/koota/compiler.rb', line 141

def compile_raw(raw)
  raw.each_char do |char|
    add_bytecode(PUT, *Encode.utf8(char))
  end
end


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/koota/compiler.rb', line 30

def link_calls(links)
  return if links.empty?

  offsets = {}

  # Compile referred ASTs, storing their start offset. Only compile those
  # that are referenced by a link.
  @refs.each do |ref, ref_ast|
    next unless links.value?(ref)

    offsets[ref] = @memory.length

    # Defeat self-references by knowing thy name.
    @name = ref
    compile(ref_ast)
    @name = ''

    add_bytecode(RET) # Close off with a ret since they are subroutines after all
  end

  # And link everything!
  links.each do |offset, ref|
    @memory[offset + 1], @memory[offset + 2] = Encode.short(offsets[ref])
  end
end


56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/koota/compiler.rb', line 56

def link_picks(links)
  return if links.empty?

  links.each do |offset, picks|
    # Replace the placeholder with the real offset.
    @memory[offset + 1], @memory[offset + 2] = Encode.short(@memory.length)

    # Now just output the length and the picks' offsets.
    add_bytecode(*Encode.short(picks.length))
    picks.each do |pick|
      add_bytecode(*Encode.short(pick))
    end
  end
end