class Koota::Compiler
Compiles an AST as returned by {Koota::Parser#call} into bytecode to be consumed by {Koota::VM#call}.
Public Instance Methods
call(ast, refs = {})
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# File lib/koota/compiler.rb, line 71 def compile_pattern(*sequence) sequence.each { |it| compile(it) } end
compile_raw(raw)
click to toggle source
# File lib/koota/compiler.rb, line 141 def compile_raw(raw) raw.each_char do |char| add_bytecode(PUT, *Encode.utf8(char)) end end
link_calls(links)
click to toggle source
# 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
link_picks(links)
click to toggle source
# 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
Private Instance Methods
add_bytecode(*bytecode)
click to toggle source
# File lib/koota/compiler.rb, line 149 def add_bytecode(*bytecode) @memory.concat(bytecode) end
compile(ast)
click to toggle source
# File lib/koota/compiler.rb, line 153 def compile(ast) raise ArgumentError, 'invalid AST' if ast.empty? send(:"compile_#{ast[0]}", *ast[1..-1]) end