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