Write documentation.
This commit is contained in:
parent
96059d6e04
commit
8d015d47f3
|
@ -1,23 +1,37 @@
|
|||
module Chalk
|
||||
module Builtin
|
||||
class BuiltinFunction
|
||||
# A normal function (i.e., a "call" is generated for it)
|
||||
# that is provided by chalk's standard library, and therefore
|
||||
# has predefined output.
|
||||
abstract class BuiltinFunction
|
||||
# Gets the number of parameters this function has.
|
||||
getter param_count : Int32
|
||||
|
||||
# Creates a new function with *param_count* parameters.
|
||||
def initialize(@param_count)
|
||||
end
|
||||
|
||||
def generate!(codegen)
|
||||
end
|
||||
# Uses the given `Compiler::Emitter` to output code.
|
||||
abstract def generate!(codegen)
|
||||
end
|
||||
|
||||
class InlineFunction
|
||||
# A function to which a call is not generated. This function
|
||||
# is copied everywhere a call to it occurs. Besides this, the
|
||||
# function also accepts trees rather than register numbers,
|
||||
# and therefore can accept and manipulate trees.
|
||||
abstract class InlineFunction
|
||||
# Gets the number of parameters this function has.
|
||||
getter param_count : Int32
|
||||
|
||||
# Creates a new function with *param_count* parameters.
|
||||
def initialize(@param_count)
|
||||
end
|
||||
|
||||
def generate!(codegen, params, table, target, free)
|
||||
end
|
||||
# Generates code like `Compiler::CodeGenerator` would.
|
||||
# The *codegen* parameter is used to emit instructions,
|
||||
# the *params* are trees that are being passed as arguments.
|
||||
# See `Compiler::CodeGenerator#generate!` for what the other parameters mean.
|
||||
abstract def generate!(codegen, params, table, target, free)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,14 +3,21 @@ require "./emitter.cr"
|
|||
|
||||
module Chalk
|
||||
module Compiler
|
||||
# A class that converts a tree into the corresponding
|
||||
# intermediate representation, without optimizing.
|
||||
class CodeGenerator
|
||||
include Emitter
|
||||
|
||||
# The register into which the return value of a function is stored.
|
||||
RETURN_REG = 14
|
||||
# The register into which the "stack pointer" is stored.
|
||||
STACK_REG = 13
|
||||
|
||||
property instructions : Array(Ir::Instruction)
|
||||
# Gets the instructions currently emitted by this code generator.
|
||||
getter instructions
|
||||
|
||||
# Creates a new compiler with the given symbol *table*
|
||||
# and *function* for which code should be generated.
|
||||
def initialize(table, @function : Trees::TreeFunction)
|
||||
@registers = 0
|
||||
@instructions = [] of Ir::Instruction
|
||||
|
@ -22,10 +29,17 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Generates code for an inline function, with the given *tree* being the `Trees::TreeCall`
|
||||
# that caused the function call. The other parameters are as described in the more general
|
||||
# `#generate!` call.
|
||||
def generate!(tree, function : Builtin::InlineFunction, table, target, free)
|
||||
function.generate!(self, tree.params, table, target, free)
|
||||
end
|
||||
|
||||
# Generates code for a tree or a builtin function (that is, a call is actually necessary).
|
||||
# I is set to the current stack pointer, the registers are stored, and the call is made.
|
||||
# The registers are then restored. The other parameters are as described in the more general
|
||||
# `#generate!` call.
|
||||
def generate!(tree, function : Trees::TreeFunction | Builtin::BuiltinFunction, table, target, free)
|
||||
start_at = free
|
||||
# Move I to stack
|
||||
|
@ -63,6 +77,11 @@ module Chalk
|
|||
loadr target, RETURN_REG
|
||||
end
|
||||
|
||||
# Generates code for a *tree*, using a symbol *table*
|
||||
# housing all the names for identifiers in the code.
|
||||
# The result is stored into the *target* register,
|
||||
# and the *free* register is the next register into
|
||||
# which a value can be stored for "scratch work".
|
||||
def generate!(tree, table, target, free)
|
||||
case tree
|
||||
when Trees::TreeId
|
||||
|
@ -135,6 +154,7 @@ module Chalk
|
|||
return 0
|
||||
end
|
||||
|
||||
# Generates code for the function that was given to it.
|
||||
def generate!
|
||||
generate!(@function.block, @table, -1, @registers)
|
||||
return @instructions
|
||||
|
|
|
@ -4,13 +4,19 @@ require "./table.cr"
|
|||
|
||||
module Chalk
|
||||
module Compiler
|
||||
# Top-level class to tie together the various
|
||||
# components, such as the `Lexer`,
|
||||
# `ParserCombinators::Parser`, and `Optimizer`
|
||||
class Compiler
|
||||
# Creates a new compiler with the given *config*.
|
||||
def initialize(@config : Ui::Config)
|
||||
@logger = Logger.new STDOUT
|
||||
@logger.debug("Initialized compiler")
|
||||
@logger.level = Logger::DEBUG
|
||||
end
|
||||
|
||||
# Reads a file an extracts instances of
|
||||
# `Trees:TreeFunction`.
|
||||
private def create_trees(file)
|
||||
string = File.read(file)
|
||||
@logger.debug("Tokenizing")
|
||||
|
@ -36,6 +42,8 @@ module Chalk
|
|||
raise "Unable to parse file."
|
||||
end
|
||||
|
||||
# Creates a default symbol table using the default functions,
|
||||
# as well as the functions declared by *trees*
|
||||
private def create_table(trees)
|
||||
table = Table.new
|
||||
@logger.debug("Creating symbol table")
|
||||
|
@ -53,6 +61,9 @@ module Chalk
|
|||
return table
|
||||
end
|
||||
|
||||
# Generates and optimizes intermediate representation for the given *tree*,
|
||||
# looking up identifiers in the symbol *table*, and appending the given *instruction*
|
||||
# at the end of the function to ensure correct program flow.
|
||||
private def create_code(tree : Trees::TreeFunction, table, instruction = Ir::ReturnInstruction.new)
|
||||
optimizer = Optimizer.new
|
||||
generator = CodeGenerator.new table, tree
|
||||
|
@ -62,12 +73,17 @@ module Chalk
|
|||
return optimizer.optimize(code)
|
||||
end
|
||||
|
||||
private def create_code(tree : Builtin::BuiltinFunction, table, instruction = nil)
|
||||
# Generate code for a builtin function. Neither the *table* nor the *instruction*
|
||||
# are used, and serve to allow function overloading.
|
||||
private def create_code(function : Builtin::BuiltinFunction, table, instruction = nil)
|
||||
instructions = [] of Ir::Instruction
|
||||
tree.generate!(instructions)
|
||||
function.generate!(instructions)
|
||||
return instructions
|
||||
end
|
||||
|
||||
# Creates a hash containing function names and their generated code.
|
||||
# Only functions parsed from the file are compiled, and the *table*
|
||||
# is used for looking up identifiers.
|
||||
private def create_code(trees : Array(Trees::TreeFunction), table)
|
||||
code = {} of String => Array(Ir::Instruction)
|
||||
trees.each do |tree|
|
||||
|
@ -76,6 +92,9 @@ module Chalk
|
|||
return code
|
||||
end
|
||||
|
||||
# Runs in the tree `Ui::OutputMode`. The file is
|
||||
# tokenized and parsed, and the result is printed
|
||||
# to the standard output.
|
||||
private def run_tree
|
||||
trees = create_trees(@config.file)
|
||||
trees.each do |it|
|
||||
|
@ -83,6 +102,11 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Runs in the intermediate `Ui::OutputMode`. The file
|
||||
# is tokenized and parsed, and for each function,
|
||||
# intermediate representation is generated. However,
|
||||
# an executable is not generated, and the IR
|
||||
# is printed to the screen.
|
||||
private def run_intermediate
|
||||
trees = create_trees(@config.file)
|
||||
table = create_table(trees)
|
||||
|
@ -94,9 +118,11 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Creates binary from the given *instructions*,
|
||||
# using the symbol *table* for lookups, and writes
|
||||
# the output to *dest*
|
||||
private def generate_binary(table, instructions, dest)
|
||||
context = Ir::InstructionContext.new table, instructions.size
|
||||
binary = instructions.map_with_index { |it, i| it.to_bin(context, i).to_u16 }
|
||||
binary = instructions.map_with_index { |it, i| it.to_bin(table, instructions.size, i).to_u16 }
|
||||
binary.each do |inst|
|
||||
first = (inst >> 8).to_u8
|
||||
dest.write_byte(first)
|
||||
|
@ -105,6 +131,8 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Find all calls performed by the functions
|
||||
# stored in the *table*, starting at the main function.
|
||||
private def collect_calls(table)
|
||||
open = Set(String).new
|
||||
done = Set(String).new
|
||||
|
@ -128,6 +156,8 @@ module Chalk
|
|||
return done
|
||||
end
|
||||
|
||||
# Runs in the binary `Ui::OutputMode`. The file is
|
||||
# converted into an executable.
|
||||
private def run_binary
|
||||
all_instructions = [] of Ir::Instruction
|
||||
trees = create_trees(@config.file)
|
||||
|
@ -154,6 +184,7 @@ module Chalk
|
|||
file.close
|
||||
end
|
||||
|
||||
# Runs the compiler.
|
||||
def run
|
||||
case @config.mode
|
||||
when Ui::OutputMode::Tree
|
||||
|
|
|
@ -1,19 +1,34 @@
|
|||
module Chalk
|
||||
module Ui
|
||||
# The mode in which the compiler operates.
|
||||
# Defines what actions are and aren't performed.
|
||||
enum OutputMode
|
||||
# The text is only parsed, and the result is printed to the screen.
|
||||
Tree,
|
||||
# The text is parsed and converted to intermediate representation.
|
||||
# The intermediate representation is then printed to the screen.
|
||||
Intermediate,
|
||||
# The text is converted into a full CHIP-8 executable.
|
||||
Binary
|
||||
end
|
||||
|
||||
# A configuration class created from the command-line parameters.
|
||||
class Config
|
||||
property file : String
|
||||
property mode : OutputMode
|
||||
# Gets the file to be compiled.
|
||||
getter file : String
|
||||
# Sets the file to be compiled.
|
||||
setter file : String
|
||||
# Gets the mode in which the compiler should operate.
|
||||
getter mode : OutputMode
|
||||
# Sets the mode in which the compiler should operate.
|
||||
setter mode : OutputMode
|
||||
|
||||
# Creates a new configuration.
|
||||
def initialize(@file = "",
|
||||
@mode = OutputMode::Tree)
|
||||
end
|
||||
|
||||
# Reads a configuration from the command line options.
|
||||
def self.parse!
|
||||
config = self.new
|
||||
OptionParser.parse! do |parser|
|
||||
|
@ -38,6 +53,8 @@ module Chalk
|
|||
return config
|
||||
end
|
||||
|
||||
# Validates the options provided, returning true if
|
||||
# they are valid and false otherwise.
|
||||
def validate!
|
||||
if file == ""
|
||||
puts "No source file specified."
|
||||
|
|
|
@ -2,6 +2,8 @@ require "./tree.cr"
|
|||
|
||||
module Chalk
|
||||
module Trees
|
||||
# `Trees::Transformer` that turns operations on
|
||||
# Constants into constants.
|
||||
class ConstantFolder < Transformer
|
||||
private def perform_op(op, left, right)
|
||||
case op
|
||||
|
|
|
@ -1,72 +1,106 @@
|
|||
module Chalk
|
||||
module Compiler
|
||||
# Module to emit instructions and store
|
||||
# them into an existing array.
|
||||
module Emitter
|
||||
# Emits an instruction to load a *value* into a register, *into*.
|
||||
def load(into, value)
|
||||
inst = Ir::LoadInstruction.new into, value.to_i32
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits an instruction to load a register, *from*, into
|
||||
# another register, *into*
|
||||
def loadr(into, from)
|
||||
inst = Ir::LoadRegInstruction.new into, from
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits an instruction that's converted
|
||||
# to an operation, *op* that mutates the register, *into*,
|
||||
# with the right hand operand *from*
|
||||
def op(op, into, from)
|
||||
inst = Ir::OpInstruction.new op, into, from
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits an instruction that's converted
|
||||
# to an operation, *op*, that mutates the register, *into*,
|
||||
# with the right hand operand (a register), *from*
|
||||
def opr(op, into, from)
|
||||
inst = Ir::OpRegInstruction.new op, into, from
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits a "skip next instruction if not equal"
|
||||
# instruction. The left hand side is a register,
|
||||
# an the right hand side is a value.
|
||||
def sne(l, r)
|
||||
inst = Ir::SkipNeInstruction.new l, r
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits an instruction to jump relative to
|
||||
# where the instruction is.
|
||||
# ```
|
||||
# jr 0 # Infinite loop
|
||||
# jr -1 # Run previous instruction
|
||||
# jr 1 # pretty much a no-op.
|
||||
# ```
|
||||
def jr(o)
|
||||
inst = Ir::JumpRelativeInstruction.new o
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits instruction that stores register 0 through *up_to* into
|
||||
# memory at address I.
|
||||
def store(up_to)
|
||||
inst = Ir::StoreInstruction.new up_to
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits instruction that loads values from address I into
|
||||
# register 0 through *up_t*
|
||||
def restore(up_to)
|
||||
inst = Ir::RestoreInstruction.new up_to
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits a return instruction.
|
||||
def ret
|
||||
inst = Ir::ReturnInstruction.new
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits an instruction to call
|
||||
# the given function name.
|
||||
def call(func)
|
||||
inst = Ir::CallInstruction.new func
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits instruction to set I
|
||||
# to the baste stack location. The stack
|
||||
# pointer will need to be added to I
|
||||
# to get the next available stack slot.
|
||||
def setis
|
||||
inst = Ir::SetIStackInstruction.new
|
||||
@instructions << inst
|
||||
return inst
|
||||
end
|
||||
|
||||
# Emits instruction to add the value of a
|
||||
# register to I
|
||||
def addi(reg)
|
||||
inst = Ir::AddIRegInstruction.new reg
|
||||
@instructions << inst
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module Chalk
|
||||
module Trees
|
||||
# Visitor that finds all function calls in a function.
|
||||
class CallVisitor < Visitor
|
||||
property calls : Set(String)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module Chalk
|
||||
module Builtin
|
||||
# Inline function to draw sprite at address I.
|
||||
class InlineDrawFunction < InlineFunction
|
||||
def initialize
|
||||
@param_count = 3
|
||||
|
@ -15,6 +16,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Inline function to await for a key and return it.
|
||||
class InlineAwaitKeyFunction < InlineFunction
|
||||
def initialize
|
||||
@param_count = 0
|
||||
|
@ -25,6 +27,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Inline function to get font for a given value.
|
||||
class InlineGetFontFunction < InlineFunction
|
||||
def initialize
|
||||
@param_count = 1
|
||||
|
@ -36,6 +39,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Inline function to set the delay timer.
|
||||
class InlineSetDelayFunction < InlineFunction
|
||||
def initialize
|
||||
@param_count = 1
|
||||
|
@ -47,6 +51,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Inline function to get the delay timer.
|
||||
class InlineGetDelayFunction < InlineFunction
|
||||
def initialize
|
||||
@param_count = 0
|
||||
|
|
206
src/chalk/ir.cr
206
src/chalk/ir.cr
|
@ -2,25 +2,19 @@ require "./lexer.cr"
|
|||
|
||||
module Chalk
|
||||
module Ir
|
||||
# Base instruction class.
|
||||
class Instruction
|
||||
def to_bin(i, index)
|
||||
# Converts the instruction to binary, using
|
||||
# A table for symbol lookups, the stack position,
|
||||
# and the inex of the instruction.
|
||||
def to_bin(table, stack, index)
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
class InstructionContext
|
||||
property table : Compiler::Table
|
||||
property stack : Int32
|
||||
|
||||
def initialize(@table, @stack)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to load a value into a register.
|
||||
class LoadInstruction < Instruction
|
||||
property register : Int32
|
||||
property value : Int32
|
||||
|
||||
def initialize(@register, @value)
|
||||
def initialize(@register : Int32, @value : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -29,16 +23,19 @@ module Chalk
|
|||
io << " " << @value
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
0x6000 | (@register << 8) | @value
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to load a register into another register.
|
||||
class LoadRegInstruction < Instruction
|
||||
property into : Int32
|
||||
property from : Int32
|
||||
# Gets the register being written to.
|
||||
getter into
|
||||
# Gets the register being used as right-hand operand.
|
||||
getter from
|
||||
|
||||
def initialize(@into, @from)
|
||||
def initialize(@into : Int32, @from : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -48,27 +45,25 @@ module Chalk
|
|||
@from.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
0x8000 | (@into << 8) | (@from << 4)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to perform an operation on a register and a value,
|
||||
# storing the output back into the register.
|
||||
class OpInstruction < Instruction
|
||||
property op : Compiler::TokenType
|
||||
property into : Int32
|
||||
property value : Int32
|
||||
|
||||
def initialize(@op, @into, @value)
|
||||
def initialize(@op : Compiler::TokenType, @into : Int32, @value : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << "op " << op << " R"
|
||||
io << "op " << @op << " R"
|
||||
@into.to_s(16, io)
|
||||
io << " " << @value
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
case op
|
||||
def to_bin(table, stack, index)
|
||||
case @op
|
||||
when Compiler::TokenType::OpAdd
|
||||
return 0x7000 | (@into << 8) | @value
|
||||
else
|
||||
|
@ -77,24 +72,22 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Instruction to perform an operation on a register and another register,
|
||||
# storing the output back into left hand register.
|
||||
class OpRegInstruction < Instruction
|
||||
property op : Compiler::TokenType
|
||||
property into : Int32
|
||||
property from : Int32
|
||||
|
||||
def initialize(@op, @into, @from)
|
||||
def initialize(@op : Compiler::TokenType, @into : Int32, @from : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << "opr " << op << " R"
|
||||
io << "opr " << @op << " R"
|
||||
@into.to_s(16, io)
|
||||
io << " R"
|
||||
@from.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
code = 0
|
||||
case op
|
||||
case @op
|
||||
when Compiler::TokenType::OpAdd
|
||||
code = 4
|
||||
when Compiler::TokenType::OpSub
|
||||
|
@ -112,10 +105,11 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Instruction to write registers to memory at address I.
|
||||
# The *up_to* parameter specifies the highest register
|
||||
# that should be stored.
|
||||
class StoreInstruction < Instruction
|
||||
property up_to : Int32
|
||||
|
||||
def initialize(@up_to)
|
||||
def initialize(@up_to : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -123,15 +117,16 @@ module Chalk
|
|||
@up_to.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0xf055 | (@up_to << 8)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to read registers from memory at address I.
|
||||
# The *up_to* parameter specifies the highest register
|
||||
# that should be read into.
|
||||
class RestoreInstruction < Instruction
|
||||
property up_to : Int32
|
||||
|
||||
def initialize(@up_to)
|
||||
def initialize(@up_to : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -139,11 +134,12 @@ module Chalk
|
|||
@up_to.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0xf065 | (@up_to << 8)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to return from a call.
|
||||
class ReturnInstruction < Instruction
|
||||
def initialize
|
||||
end
|
||||
|
@ -152,67 +148,68 @@ module Chalk
|
|||
io << "return"
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0x00ee
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to jump relative to its own position.
|
||||
class JumpRelativeInstruction < Instruction
|
||||
property offset : Int32
|
||||
# Gets the offset of this instruction.
|
||||
getter offset
|
||||
# Sets the offset of this instruction
|
||||
setter offset
|
||||
|
||||
def initialize(@offset)
|
||||
def initialize(@offset : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << "jr " << @offset
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0x1000 | ((@offset + index) * 2 + 0x200)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to skip the next instruction if
|
||||
# the left-hand register is equal to the right-hand value.
|
||||
class SkipEqInstruction < Instruction
|
||||
property left : Int32
|
||||
property right : Int32
|
||||
|
||||
def initialize(@left, @right)
|
||||
def initialize(@left : Int32, @right : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << "seq R"
|
||||
@left.to_s(16, io)
|
||||
io << " " << right
|
||||
io << " " << @right
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0x3000 | (@left << 8) | @right
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to skip the next instruction if
|
||||
# the left-hand register is not equal to the right-hand value.
|
||||
class SkipNeInstruction < Instruction
|
||||
property left : Int32
|
||||
property right : Int32
|
||||
|
||||
def initialize(@left, @right)
|
||||
def initialize(@left : Int32, @right : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << "sne R"
|
||||
@left.to_s(16, io)
|
||||
io << " " << right
|
||||
io << " " << @right
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0x4000 | (@left << 8) | @right
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to skip the next instruction if
|
||||
# the left-hand register is equal to the right-hand register.
|
||||
class SkipRegEqInstruction < Instruction
|
||||
property left : Int32
|
||||
property right : Int32
|
||||
|
||||
def initialize(@left, @right)
|
||||
def initialize(@left : Int32, @right : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -222,16 +219,15 @@ module Chalk
|
|||
@right.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0x5000 | (@left << 8) | (@right << 4)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to skip the next instruction if
|
||||
# the left-hand register is not equal to the right-hand register.
|
||||
class SkipRegNeInstruction < Instruction
|
||||
property left : Int32
|
||||
property right : Int32
|
||||
|
||||
def initialize(@left, @right)
|
||||
def initialize(@left : Int32, @right : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -241,77 +237,77 @@ module Chalk
|
|||
@right.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0x9000 | (@left << 8) | (@right << 4)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to call a function by name.
|
||||
class CallInstruction < Instruction
|
||||
property name : String
|
||||
# Gets the name of the function being called.
|
||||
getter name
|
||||
|
||||
def initialize(@name)
|
||||
def initialize(@name : String)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << "call " << @name
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
return 0x2000 | (i.table[name]?.as(Compiler::FunctionEntry).addr * 2 + 0x200)
|
||||
def to_bin(table, stack, index)
|
||||
return 0x2000 | (table[name]?.as(Compiler::FunctionEntry).addr * 2 + 0x200)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to set I to the base position of the stack.
|
||||
class SetIStackInstruction < Instruction
|
||||
def to_s(io)
|
||||
io << "setis"
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
return 0xa000 | (i.stack * 2 + 0x200)
|
||||
def to_bin(table, stack, index)
|
||||
return 0xa000 | (stack * 2 + 0x200)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to add a register to I.
|
||||
class AddIRegInstruction < Instruction
|
||||
property reg : Int32
|
||||
|
||||
def initialize(@reg)
|
||||
def initialize(@reg : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << "addi R"
|
||||
reg.to_s(16, io)
|
||||
@reg.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0xf000 | (@reg << 8) | 0x1e
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to draw on screen.
|
||||
# The x and y coordinates specify the position of the sprite,
|
||||
# and the height gives the height of the sprite.
|
||||
class DrawInstruction < Instruction
|
||||
property x : Int32
|
||||
property y : Int32
|
||||
property height : Int32
|
||||
|
||||
def initialize(@x, @y, @height)
|
||||
def initialize(@x : Int32, @y : Int32, @height : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << "draw R"
|
||||
x.to_s(16, io)
|
||||
@x.to_s(16, io)
|
||||
io << " R"
|
||||
y.to_s(16, io)
|
||||
io << " " << height
|
||||
@y.to_s(16, io)
|
||||
io << " " << @height
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
return 0xd000 | (@x << 8) | (@y << 4) | height
|
||||
def to_bin(table, stack, index)
|
||||
return 0xd000 | (@x << 8) | (@y << 4) | @height
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to await a key press and store it into a register.
|
||||
class AwaitKeyInstruction < Instruction
|
||||
property into : Int32
|
||||
|
||||
def initialize(@into)
|
||||
def initialize(@into : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -319,15 +315,15 @@ module Chalk
|
|||
@into.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0xf00a | (@into << 8)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to set I to the font given by the value
|
||||
# of a register.
|
||||
class GetFontInstruction < Instruction
|
||||
property from : Int32
|
||||
|
||||
def initialize(@from)
|
||||
def initialize(@from : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -335,15 +331,15 @@ module Chalk
|
|||
@from.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0xf029 | (@from << 8)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to set the delay timer to the value
|
||||
# of the given register.
|
||||
class SetDelayTimerInstruction < Instruction
|
||||
property from : Int32
|
||||
|
||||
def initialize(@from)
|
||||
def initialize(@from : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -351,15 +347,15 @@ module Chalk
|
|||
@from.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0xf015 | (@from << 8)
|
||||
end
|
||||
end
|
||||
|
||||
# Instruction to get the delay timer, and store
|
||||
# the value into the given register.
|
||||
class GetDelayTimerInstruction < Instruction
|
||||
property into : Int32
|
||||
|
||||
def initialize(@into)
|
||||
def initialize(@into : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -367,7 +363,7 @@ module Chalk
|
|||
@into.to_s(16, io)
|
||||
end
|
||||
|
||||
def to_bin(i, index)
|
||||
def to_bin(table, stack, index)
|
||||
return 0xf007 | (@into << 8)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ require "lex"
|
|||
|
||||
module Chalk
|
||||
module Compiler
|
||||
# The type of a token that can be lexed.
|
||||
enum TokenType
|
||||
Any,
|
||||
Str,
|
||||
|
@ -29,14 +30,21 @@ module Chalk
|
|||
KwReturn
|
||||
end
|
||||
|
||||
# A class that stores the string it matched and its token type.
|
||||
class Token
|
||||
def initialize(@string : String, @type : TokenType)
|
||||
end
|
||||
|
||||
# Gets the string this token represents.
|
||||
getter string : String
|
||||
# Gets the type of this token.
|
||||
getter type : TokenType
|
||||
end
|
||||
|
||||
# Creates a new Lexer with default token values.
|
||||
# The lexer is backed by liblex. When a string is
|
||||
# matched by several tokens, the longest match is chosen
|
||||
# first, followed by the match with the highest enum value.
|
||||
class Lexer
|
||||
def initialize
|
||||
@lexer = Lex::Lexer.new
|
||||
|
@ -71,6 +79,7 @@ module Chalk
|
|||
@lexer.add_pattern("return", TokenType::KwReturn.value)
|
||||
end
|
||||
|
||||
# Converts a string into tokens.
|
||||
def lex(string)
|
||||
return @lexer.lex(string)
|
||||
.select { |t| !t[0][0].whitespace? }
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
module Chalk
|
||||
module Compiler
|
||||
# Class to optimize instructions.
|
||||
class Optimizer
|
||||
# Checks if *inst* is "dead code",
|
||||
# an instruction that is completely useless.
|
||||
private def check_dead(inst)
|
||||
if inst.is_a?(Ir::LoadRegInstruction)
|
||||
return inst.from == inst.into
|
||||
|
@ -8,6 +11,9 @@ module Chalk
|
|||
return false
|
||||
end
|
||||
|
||||
# Optimizes *instructions* in the basic block given by the *range*,
|
||||
# storing addresses of instructions to be deleted into *deletions*,
|
||||
# and the number of deleted instructions so far into *deletions_at*
|
||||
private def optimize!(instructions, range, deletions, deletions_at)
|
||||
range.each do |index|
|
||||
if check_dead(instructions[index])
|
||||
|
@ -17,14 +23,21 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Optimizes the given list of instructions.
|
||||
# The basic blocks are inferred from the various
|
||||
# jumps and skips.
|
||||
def optimize(instructions)
|
||||
instructions = instructions.dup
|
||||
block_boundaries = [instructions.size]
|
||||
instructions.each_with_index do |inst, i|
|
||||
if inst.is_a?(Ir::JumpRelativeInstruction)
|
||||
block_boundaries << i
|
||||
block_boundaries << (i + 1)
|
||||
block_boundaries << (inst.offset + i)
|
||||
end
|
||||
if inst.is_a?(Ir::SkipNeInstruction | Ir::SkipEqInstruction |
|
||||
Ir::SkipRegEqInstruction | Ir::SkipRegNeInstruction)
|
||||
block_boundaries << (i + 1)
|
||||
end
|
||||
end
|
||||
block_boundaries.uniq!.sort!
|
||||
|
||||
|
|
|
@ -2,13 +2,16 @@ require "./parser_builder.cr"
|
|||
|
||||
module Chalk
|
||||
module ParserCombinators
|
||||
# Parser created out of the various parser combinators.
|
||||
class Parser
|
||||
include ParserBuilder
|
||||
|
||||
# Creates a parser for a type.
|
||||
private def create_type
|
||||
either(type(Compiler::TokenType::KwU0), type(Compiler::TokenType::KwU8), type(Compiler::TokenType::KwU12))
|
||||
end
|
||||
|
||||
# Creates a parser for an integer literal.
|
||||
private def create_lit
|
||||
dec_parser = type(Compiler::TokenType::LitDec).transform &.string.to_i64
|
||||
hex_parser = type(Compiler::TokenType::LitHex).transform &.string.lchop("0x").to_i64(16)
|
||||
|
@ -17,6 +20,8 @@ module Chalk
|
|||
return lit_parser
|
||||
end
|
||||
|
||||
# Creates a parser for an operation with a given *atom* parser
|
||||
# and *op* parser.
|
||||
private def create_op_expr(atom, op)
|
||||
pl = PlaceholderParser(Trees::Tree).new
|
||||
recurse = atom.then(op).then(pl).transform do |arr|
|
||||
|
@ -30,12 +35,16 @@ module Chalk
|
|||
return pl
|
||||
end
|
||||
|
||||
# Creates a parser to parse layers of *ops* with multiple
|
||||
# levels of precedence, specified by their order. The *atom*
|
||||
# is the most basic expression.
|
||||
private def create_op_exprs(atom, ops)
|
||||
ops.reduce(atom) do |previous, current|
|
||||
create_op_expr(previous, current)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a parser for a call, with the given expression parser.
|
||||
private def create_call(expr)
|
||||
call = type(Compiler::TokenType::Id).then(char '(').then(delimited(expr, char ',')).then(char ')').transform do |arr|
|
||||
arr = arr.flatten
|
||||
|
@ -46,6 +55,7 @@ module Chalk
|
|||
return call
|
||||
end
|
||||
|
||||
# Creates a parser for an expression.
|
||||
private def create_expr
|
||||
expr_place = PlaceholderParser(Trees::Tree).new
|
||||
literal = create_lit
|
||||
|
@ -64,6 +74,7 @@ module Chalk
|
|||
return expr
|
||||
end
|
||||
|
||||
# Creates a parser for a var statement.
|
||||
private def create_var(expr)
|
||||
var = type(Compiler::TokenType::KwVar).then(type(Compiler::TokenType::Id)).then(char '=').then(expr).then(char ';').transform do |arr|
|
||||
arr = arr.flatten
|
||||
|
@ -74,6 +85,7 @@ module Chalk
|
|||
return var
|
||||
end
|
||||
|
||||
# Creates a parser for an assignment statement.
|
||||
private def create_assign(expr)
|
||||
assign = type(Compiler::TokenType::Id).then(char '=').then(expr).then(char ';').transform do |arr|
|
||||
arr = arr.flatten
|
||||
|
@ -84,6 +96,7 @@ module Chalk
|
|||
return assign
|
||||
end
|
||||
|
||||
# Creates a parser for a basic statement.
|
||||
private def create_basic(expr)
|
||||
basic = expr.then(char ';').transform do |arr|
|
||||
arr.flatten[0].as(Trees::Tree)
|
||||
|
@ -91,6 +104,7 @@ module Chalk
|
|||
return basic
|
||||
end
|
||||
|
||||
# Creates a parser for an if statement.
|
||||
private def create_if(expr, block)
|
||||
iff = type(Compiler::TokenType::KwIf).then(char '(').then(expr).then(char ')').then(block)
|
||||
.then(optional(type(Compiler::TokenType::KwElse).then(block)))
|
||||
|
@ -104,6 +118,7 @@ module Chalk
|
|||
return iff
|
||||
end
|
||||
|
||||
# Creates a parser for a while loop.
|
||||
private def create_while(expr, block)
|
||||
whilee = type(Compiler::TokenType::KwWhile).then(char '(').then(expr).then(char ')').then(block).transform do |arr|
|
||||
arr = arr.flatten
|
||||
|
@ -114,6 +129,7 @@ module Chalk
|
|||
return whilee
|
||||
end
|
||||
|
||||
# Creates a parser for a return.
|
||||
private def create_return(expr)
|
||||
returnn = type(Compiler::TokenType::KwReturn).then(expr).then(char ';').transform do |arr|
|
||||
arr = arr.flatten
|
||||
|
@ -123,6 +139,7 @@ module Chalk
|
|||
return returnn
|
||||
end
|
||||
|
||||
# Creates a parser for a block of statements.
|
||||
private def create_block(statement)
|
||||
block = char('{').then(many(statement)).then(char '}').transform do |arr|
|
||||
arr = arr.flatten
|
||||
|
@ -132,6 +149,7 @@ module Chalk
|
|||
return block
|
||||
end
|
||||
|
||||
# Creates a statement and block parser, returning both.
|
||||
private def create_statement_block
|
||||
statement_place = PlaceholderParser(Trees::Tree).new
|
||||
expr = create_expr
|
||||
|
@ -147,6 +165,7 @@ module Chalk
|
|||
return {statement, block}
|
||||
end
|
||||
|
||||
# Creates a parser for a function declaration.
|
||||
private def create_func(block, type)
|
||||
func = type(Compiler::TokenType::KwFun).then(type(Compiler::TokenType::Id))
|
||||
.then(char '(').then(delimited(type(Compiler::TokenType::Id), char ',')).then(char ')')
|
||||
|
@ -167,6 +186,7 @@ module Chalk
|
|||
@parser = many(create_func(block, create_type)).as(BasicParser(Array(Trees::TreeFunction)))
|
||||
end
|
||||
|
||||
# Parses the given tokens into a tree.
|
||||
def parse?(tokens)
|
||||
return @parser.parse?(tokens, 0).try &.[0]
|
||||
end
|
||||
|
|
|
@ -4,34 +4,42 @@ require "./parsers.cr"
|
|||
module Chalk
|
||||
module ParserCombinators
|
||||
module ParserBuilder
|
||||
# Creates a parser for a given token type.
|
||||
def type(type) : BasicParser(Compiler::Token)
|
||||
return TypeParser.new(type).as(BasicParser(Compiler::Token))
|
||||
end
|
||||
|
||||
# Creates a parser for a specific character.
|
||||
def char(type) : BasicParser(Compiler::Token)
|
||||
return CharParser.new(type).as(BasicParser(Compiler::Token))
|
||||
end
|
||||
|
||||
# Creates a parser that transforms a value according to a block.
|
||||
def transform(parser : BasicParser(T), &transform : T -> R) forall T, R
|
||||
return TransformParser.new(parser, &transform).as(BasicParser(R))
|
||||
end
|
||||
|
||||
# Creates a parser that allows for failure to match.
|
||||
def optional(parser : BasicParser(T)) : BasicParser(T?) forall T
|
||||
return OptionalParser.new(parser).as(BasicParser(T?))
|
||||
end
|
||||
|
||||
# Creates a parser that tries several parsers in sequence until one succeeds.
|
||||
def either(*args : BasicParser(T)) : BasicParser(T) forall T
|
||||
return EitherParser.new(args.to_a).as(BasicParser(T))
|
||||
end
|
||||
|
||||
# Creates a parser that parses one or more of the given parsers.
|
||||
def many(parser : BasicParser(T)) : BasicParser(Array(T)) forall T
|
||||
return ManyParser.new(parser).as(BasicParser(Array(T)))
|
||||
end
|
||||
|
||||
# Creates a parser that parses one parser delimited by another.
|
||||
def delimited(parser : BasicParser(T), delimiter : BasicParser(R)) : BasicParser(Array(T)) forall T, R
|
||||
return DelimitedParser.new(parser, delimiter).as(BasicParser(Array(T)))
|
||||
end
|
||||
|
||||
# Creates a parser that parses one parser, then the next.
|
||||
def then(first : BasicParser(T), second : BasicParser(R)) forall T, R
|
||||
return NextParser.new(first, second).as(BasicParser(Array(T | R)))
|
||||
end
|
||||
|
|
|
@ -1,22 +1,33 @@
|
|||
module Chalk
|
||||
module ParserCombinators
|
||||
# Abstract class for a parser function,
|
||||
# as used in parser combinators. This is basically
|
||||
# a building block of parsing.
|
||||
abstract class BasicParser(T)
|
||||
# Attempts to parse the given *tokens*, starting at the given *index*.
|
||||
abstract def parse?(tokens : Array(Compiler::Token),
|
||||
index : Int64) : Tuple(T, Int64)?
|
||||
|
||||
# Attempts to parse the given tokens like `#parse?`, but throws
|
||||
# on error.
|
||||
def parse(tokens, index)
|
||||
return parse?(tokens, index).not_nil!
|
||||
end
|
||||
|
||||
# Applies the given transformation to this parser,
|
||||
# creating a new parser.
|
||||
def transform(&transform : T -> R) forall R
|
||||
return TransformParser.new(self, &transform).as(BasicParser(R))
|
||||
end
|
||||
|
||||
# Creates a sequence with the given parser,
|
||||
# creating a new parser.
|
||||
def then(other : BasicParser(R)) : BasicParser(Array(T | R)) forall R
|
||||
return NextParser.new(self, other).as(BasicParser(Array(T | R)))
|
||||
end
|
||||
end
|
||||
|
||||
# Parser that expects a specific token type.
|
||||
class TypeParser < BasicParser(Compiler::Token)
|
||||
def initialize(@type : Compiler::TokenType)
|
||||
end
|
||||
|
@ -28,6 +39,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Parser that expects a specific character.
|
||||
class CharParser < BasicParser(Compiler::Token)
|
||||
def initialize(@char : Char)
|
||||
end
|
||||
|
@ -40,6 +52,8 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Parser that applies a transformation to the output
|
||||
# of its child parser.
|
||||
class TransformParser(T, R) < BasicParser(R)
|
||||
def initialize(@parser : BasicParser(T), &@block : T -> R)
|
||||
end
|
||||
|
@ -52,6 +66,8 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Parser that attempts to use its child parser,
|
||||
# and successfully returns nil if the child parser fails.
|
||||
class OptionalParser(T) < BasicParser(T?)
|
||||
def initialize(@parser : BasicParser(T))
|
||||
end
|
||||
|
@ -64,6 +80,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Parser that tries all of its children until one succeeds.
|
||||
class EitherParser(T) < BasicParser(T)
|
||||
def initialize(@parsers : Array(BasicParser(T)))
|
||||
end
|
||||
|
@ -78,6 +95,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Parser that parses at least one of a given type.
|
||||
class ManyParser(T) < BasicParser(Array(T))
|
||||
def initialize(@parser : BasicParser(T))
|
||||
end
|
||||
|
@ -92,6 +110,8 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Parser that parses at least 0 of its child parser,
|
||||
# delimited with its other child parser.
|
||||
class DelimitedParser(T, R) < BasicParser(Array(T))
|
||||
def initialize(@parser : BasicParser(T), @delimiter : BasicParser(R))
|
||||
end
|
||||
|
@ -114,6 +134,8 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Parser that parses using the first parser, and, if it succeeds,
|
||||
# parses using the second parses.
|
||||
class NextParser(T, R) < BasicParser(Array(T | R))
|
||||
def initialize(@first : BasicParser(T), @second : BasicParser(R))
|
||||
end
|
||||
|
@ -133,6 +155,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# Parser used to declare recursive grammars.
|
||||
class PlaceholderParser(T) < BasicParser(T)
|
||||
property parser : BasicParser(T)?
|
||||
|
||||
|
|
|
@ -2,12 +2,13 @@ require "./tree.cr"
|
|||
|
||||
module Chalk
|
||||
module Trees
|
||||
# Visitor that prints a `Tree`.
|
||||
class PrintVisitor < Visitor
|
||||
def initialize(@stream : IO)
|
||||
@indent = 0
|
||||
end
|
||||
|
||||
def print_indent
|
||||
private def print_indent
|
||||
@indent.times do
|
||||
@stream << " "
|
||||
end
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
module Chalk
|
||||
module Compiler
|
||||
# An entry in the symbol table.
|
||||
class Entry
|
||||
end
|
||||
|
||||
# An entry that represents a function in the symbol table.
|
||||
class FunctionEntry < Entry
|
||||
property function : Trees::TreeFunction | Builtin::BuiltinFunction | Builtin::InlineFunction
|
||||
property addr : Int32
|
||||
# Gets the function stored in this entry.
|
||||
getter function
|
||||
# Gets the address in code of this function.
|
||||
getter addr
|
||||
# Sets the address in code of this function.
|
||||
setter addr
|
||||
|
||||
def initialize(@function, @addr = -1)
|
||||
def initialize(@function : Trees::TreeFunction | Builtin::BuiltinFunction | Builtin::InlineFunction,
|
||||
@addr = -1)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -15,10 +22,13 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# An entry that represents a variable in the symbol table.
|
||||
class VarEntry < Entry
|
||||
property register : Int32
|
||||
# Gets the register occupied by the variable
|
||||
# in this entry.
|
||||
getter register
|
||||
|
||||
def initialize(@register)
|
||||
def initialize(@register : Int32)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
|
@ -26,13 +36,17 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A symbol table.
|
||||
class Table
|
||||
property parent : Table?
|
||||
# Gets the parent of this table.
|
||||
getter parent
|
||||
|
||||
def initialize(@parent = nil)
|
||||
def initialize(@parent : Table? = nil)
|
||||
@data = {} of String => Entry
|
||||
end
|
||||
|
||||
# Looks up the given *key* first in this table,
|
||||
# then in its parent, continuing recursively.
|
||||
def []?(key)
|
||||
if entry = @data[key]?
|
||||
return entry
|
||||
|
@ -40,6 +54,7 @@ module Chalk
|
|||
return @parent.try &.[key]?
|
||||
end
|
||||
|
||||
# Stores an *entry* under the given *key* into this table.
|
||||
def []=(key, entry)
|
||||
@data[key] = entry
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module Chalk
|
||||
module Trees
|
||||
# A class used to visit nodes of a tree.
|
||||
class Visitor
|
||||
def visit(tree)
|
||||
end
|
||||
|
@ -8,12 +9,16 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A class used to transform a tree, bottom up.
|
||||
# "Modern Compiler Design" refers to this technique
|
||||
# as BURS.
|
||||
class Transformer
|
||||
def transform(tree)
|
||||
return tree
|
||||
end
|
||||
end
|
||||
|
||||
# The base class of a tree.
|
||||
class Tree
|
||||
def accept(v)
|
||||
v.visit(self)
|
||||
|
@ -25,6 +30,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents an ID.
|
||||
class TreeId < Tree
|
||||
property id : String
|
||||
|
||||
|
@ -32,6 +38,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents an integer literal.
|
||||
class TreeLit < Tree
|
||||
property lit : Int64
|
||||
|
||||
|
@ -39,6 +46,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents a function call.
|
||||
class TreeCall < Tree
|
||||
property name : String
|
||||
property params : Array(Tree)
|
||||
|
@ -60,6 +68,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents an operation on two values.
|
||||
class TreeOp < Tree
|
||||
property op : Compiler::TokenType
|
||||
property left : Tree
|
||||
|
@ -82,6 +91,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents a block of statements.
|
||||
class TreeBlock < Tree
|
||||
property children : Array(Tree)
|
||||
|
||||
|
@ -102,6 +112,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents a function declaration.
|
||||
class TreeFunction < Tree
|
||||
property name : String
|
||||
property params : Array(String)
|
||||
|
@ -126,6 +137,8 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents the declaration of
|
||||
# a new variable.
|
||||
class TreeVar < Tree
|
||||
property name : String
|
||||
property expr : Tree
|
||||
|
@ -145,6 +158,8 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents the assignment
|
||||
# to an existing variable.
|
||||
class TreeAssign < Tree
|
||||
property name : String
|
||||
property expr : Tree
|
||||
|
@ -164,6 +179,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents an if statement.
|
||||
class TreeIf < Tree
|
||||
property condition : Tree
|
||||
property block : Tree
|
||||
|
@ -188,6 +204,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents a while loop.
|
||||
class TreeWhile < Tree
|
||||
property condition : Tree
|
||||
property block : Tree
|
||||
|
@ -209,6 +226,7 @@ module Chalk
|
|||
end
|
||||
end
|
||||
|
||||
# A tree that represents a return statement.
|
||||
class TreeReturn < Tree
|
||||
property rvalue : Tree
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user