├── README.md ├── awesome ├── bytecode_compiler.rb ├── compiler ├── bytecode_compiler.rb └── llvm_compiler.rb ├── example.awm ├── interpreter.rb ├── interpreter └── interpreter.rb ├── lexer ├── lexer.rb └── tokenizer │ ├── constant_tokenizer.rb │ ├── explicit_indent_tokenizer.rb │ ├── identifier_tokenizer.rb │ ├── indent_tokenizer.rb │ ├── long_operator_tokenizer.rb │ ├── number_tokenizer.rb │ ├── operator_tokenizer.rb │ ├── string_tokenizer.rb │ ├── tokenizer.rb │ └── whitespace_tokenizer.rb ├── llvm_compiler.rb ├── parser.rb ├── parser ├── grammar.y ├── nodes.rb └── parser.rb ├── runtime.rb ├── runtime ├── bootstrap.rb ├── class.rb ├── context.rb ├── method.rb └── object.rb ├── test ├── bytecode_compiler_test.rb ├── interpreter_test.rb ├── lexer_test.rb ├── llvm_compiler_test.rb ├── parser_test.rb ├── runtime_test.rb ├── test_helper.rb └── vm_test.rb ├── vm.rb └── vm ├── bytecode.rb └── vm.rb /README.md: -------------------------------------------------------------------------------- 1 | # How To Create Your Own Freaking Awesome Programming Language 2 | 3 | Code examples and exercises from the 'How To Create Your Own Freaking Awesome 4 | Programming Language' book by Marc-Andre Cournoyer. 5 | -------------------------------------------------------------------------------- /awesome: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -I. 2 | # The Awesome language 3 | # 4 | # usage: 5 | # ./awesome example.awm # to eval a file 6 | # ./awesome # to start the REPL 7 | # 8 | # on Windows run with: ruby -I. awesome [options] 9 | 10 | require 'interpreter' 11 | require 'readline' 12 | 13 | interpreter = Interpreter.new 14 | 15 | if file = ARGV.first 16 | interpreter.eval File.read(file) 17 | 18 | else 19 | puts 'Awesome REPL. Ctrl+C to quit.' 20 | loop do 21 | line = Readline::readline('>> ') # 1. Read 22 | Readline::HISTORY.push(line) 23 | value = interpreter.eval(line) # 2. Eval 24 | puts "=> #{value.ruby_value.inspect}" # 3. Print 25 | end # 4. Loop 26 | end 27 | 28 | -------------------------------------------------------------------------------- /bytecode_compiler.rb: -------------------------------------------------------------------------------- 1 | require 'compiler/bytecode_compiler' 2 | -------------------------------------------------------------------------------- /compiler/bytecode_compiler.rb: -------------------------------------------------------------------------------- 1 | require 'parser' 2 | 3 | class BytecodeCompiler 4 | 5 | def initialize 6 | @parser = Parser.new 7 | @bytecode = [] 8 | end 9 | 10 | def compile(code) 11 | @parser.parse(code).compile(self) 12 | emit RETURN 13 | @bytecode 14 | end 15 | 16 | # Usage: emit OPCODE, operand1, operand2, ..., operandX 17 | def emit(opcode, *operands) 18 | @bytecode << opcode 19 | @bytecode.concat operands 20 | end 21 | 22 | end 23 | 24 | class Nodes 25 | 26 | def compile(compiler) 27 | nodes.each do |node| 28 | node.compile(compiler) 29 | end 30 | end 31 | 32 | end 33 | 34 | class NumberNode 35 | 36 | def compile(compiler) 37 | compiler.emit PUSH_NUMBER, value 38 | end 39 | 40 | end 41 | 42 | class CallNode 43 | 44 | def compile(compiler) 45 | if receiver 46 | receiver.compile(compiler) 47 | else 48 | compiler.emit PUSH_SELF # Default to self if no receiver 49 | end 50 | 51 | arguments.each do |argument| 52 | argument.compile(compiler) 53 | end 54 | 55 | compiler.emit CALL, method, arguments.size 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /compiler/llvm_compiler.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'parser' 3 | # require 'nodes' 4 | 5 | require 'llvm/core' 6 | require 'llvm/execution_engine' 7 | require 'llvm/transforms/scalar' 8 | 9 | LLVM.init_x86 10 | 11 | class LLVMCompiler 12 | 13 | PCHAR = LLVM.Pointer(LLVM::Int8) 14 | INT = LLVM::Int 15 | 16 | attr_reader :locals 17 | 18 | def initialize(mod = nil, function = nil) 19 | @module = mod || LLVM::Module.new("awesome") 20 | 21 | @locals = {} 22 | 23 | @function = function || 24 | @module.functions.named('main') || 25 | @module.functions.add('main', [], LLVM.Void) 26 | 27 | @builder = LLVM::Builder.new 28 | @builder.position_at_end(@function.basic_blocks.append) 29 | 30 | @engine = LLVM::JITCompiler.new(@module) 31 | end 32 | 33 | def preamble 34 | fun = @module.functions.add('puts', [PCHAR], INT) 35 | fun.linkage = :external 36 | end 37 | 38 | def finish 39 | @builder.ret_void 40 | end 41 | 42 | def new_string(value) 43 | @builder.global_string_pointer(value) 44 | end 45 | 46 | def new_number(value) 47 | LLVM::Int(value) 48 | end 49 | 50 | def call(name, args = []) 51 | function = @module.functions.named(name) 52 | @builder.call(function, *args) 53 | end 54 | 55 | def assign(name, value) 56 | ptr = @builder.alloca(value.type) 57 | @builder.store(value, ptr) 58 | @locals[name] = ptr 59 | end 60 | 61 | def load(name) 62 | ptr = @locals[name] 63 | @builder.load(ptr, name) 64 | end 65 | 66 | def function(name) 67 | func = @module.functions.add(name, [], LLVM.Void) 68 | compiler = LLVMCompiler.new(@module, func) 69 | yield compiler 70 | compiler.finish 71 | end 72 | 73 | def optimize 74 | @module.verify! 75 | pass_manager = LLVM::PassManager.new(@engine) 76 | pass_manager.mem2reg! 77 | end 78 | 79 | def run 80 | @engine.run_function(@function) 81 | end 82 | 83 | def dump 84 | @module.dump 85 | end 86 | 87 | end 88 | 89 | 90 | class Nodes 91 | def llvm_compile(compiler) 92 | nodes.map { |node| node.llvm_compile(compiler) }.last 93 | end 94 | end 95 | 96 | class NumberNode 97 | def llvm_compile(compiler) 98 | compiler.new_number(value) 99 | end 100 | end 101 | 102 | class StringNode 103 | def llvm_compile(compiler) 104 | compiler.new_string(value) 105 | end 106 | end 107 | 108 | class CallNode 109 | def llvm_compile(compiler) 110 | raise 'Reciever not supported for compilation' if receiver 111 | 112 | compiled_arguments = arguments.map { |arg| arg.llvm_compile(compiler) } 113 | compiler.call(method, compiled_arguments) 114 | end 115 | end 116 | 117 | class GetLocalNode 118 | def llvm_compile(compiler) 119 | compiler.load(name) 120 | end 121 | end 122 | 123 | class SetLocalNode 124 | def llvm_compile(compiler) 125 | compiler.assign(name, value.llvm_compile(compiler)) 126 | end 127 | end 128 | 129 | class DefNode 130 | def llvm_compile(compiler) 131 | raise 'Parameters not supported for compilation' if !params.empty? 132 | 133 | compiler.function(name) do |function| 134 | body.llvm_compile(function) 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /example.awm: -------------------------------------------------------------------------------- 1 | class MyFirstAwesomeClass: 2 | def does_it_work(yes_string): 3 | yes_string 4 | 5 | awesome_class = MyFirstAwesomeClass.new 6 | print(awesome_class.does_it_work("Yeaaah buddy!")) 7 | -------------------------------------------------------------------------------- /interpreter.rb: -------------------------------------------------------------------------------- 1 | require 'parser/parser' 2 | require 'runtime' 3 | require 'interpreter/interpreter' 4 | -------------------------------------------------------------------------------- /interpreter/interpreter.rb: -------------------------------------------------------------------------------- 1 | class Interpreter 2 | 3 | def initialize 4 | @parser = Parser.new 5 | end 6 | 7 | def eval(code) 8 | @parser.parse(code).eval(RootContext) 9 | end 10 | 11 | end 12 | 13 | class Nodes 14 | def eval(context) 15 | return_value = nil 16 | nodes.each do |node| 17 | return_value = node.eval(context) 18 | end 19 | return_value || Constants['nil'] 20 | end 21 | end 22 | 23 | class NumberNode 24 | def eval(context) 25 | Constants['Number'].new_with_value(value) 26 | end 27 | end 28 | 29 | class StringNode 30 | def eval(context) 31 | Constants['String'].new_with_value(value) 32 | end 33 | end 34 | 35 | class TrueNode 36 | def eval(context) 37 | Constants['true'] 38 | end 39 | end 40 | 41 | class FalseNode 42 | def eval(context) 43 | Constants['false'] 44 | end 45 | end 46 | 47 | class NilNode 48 | def eval(context) 49 | Constants['nil'] 50 | end 51 | end 52 | 53 | class GetConstantNode 54 | def eval(context) 55 | Constants[name] 56 | end 57 | end 58 | 59 | class GetLocalNode 60 | def eval(context) 61 | context.locals[name] 62 | end 63 | end 64 | 65 | class SetConstantNode 66 | def eval(context) 67 | Constants[name] = value.eval(context) 68 | end 69 | end 70 | 71 | class SetLocalNode 72 | def eval(context) 73 | context.locals[name] = value.eval(context) 74 | end 75 | end 76 | 77 | class CallNode 78 | def eval(context) 79 | 80 | if receiver 81 | value = receiver.eval(context) 82 | else 83 | value = context.current_self 84 | end 85 | 86 | evaluated_arguments = arguments.map { |arg| arg.eval(context) } 87 | value.call(method, evaluated_arguments) 88 | end 89 | end 90 | 91 | class DefNode 92 | def eval(context) 93 | method = AwesomeMethod.new(params, body) 94 | context.current_class.runtime_methods[name] = method 95 | end 96 | end 97 | 98 | class ClassNode 99 | def eval(context) 100 | awesome_class = Constants[name] 101 | unless awesome_class 102 | awesome_class = AwesomeClass.new 103 | Constants[name] = awesome_class 104 | end 105 | 106 | class_context = Context.new(awesome_class, awesome_class) 107 | body.eval(class_context) 108 | 109 | awesome_class 110 | end 111 | end 112 | 113 | class IfNode 114 | def eval(context) 115 | if condition.eval(context).ruby_value 116 | body.eval(context) 117 | else 118 | Constants['nil'] 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lexer/lexer.rb: -------------------------------------------------------------------------------- 1 | class Lexer 2 | 3 | IDENTIFIER = /\A([a-z]\w*)/ 4 | CONSTANT = /\A([A-Z]\w*)/ 5 | NUMBER = /\A([0-9]+)/ 6 | STRING = /\A"([^"]*)"/ 7 | LONG_OPERATOR = /\A(\|\||&&|==|!=|<=|>=)/ 8 | WHITESPACE = /\A / 9 | INDENT = /\A\n( *)/m # Matches " " 10 | EXPLICIT_INDENT = /\A\:\n( +)/m # Matches ": " 11 | 12 | 13 | def tokenize(code) 14 | code.chomp! 15 | tokenizer = Tokenizer.new 16 | 17 | while tokenizer.position < code.size 18 | chunk = code[tokenizer.position..-1] 19 | 20 | tokenizer = if identifier = chunk[IDENTIFIER, 1] 21 | IdentifierTokenizer.new(identifier, tokenizer).tokenize 22 | elsif constant = chunk[CONSTANT, 1] 23 | ConstantTokenizer.new(constant, tokenizer).tokenize 24 | elsif number = chunk[NUMBER, 1] 25 | NumberTokenizer.new(number, tokenizer).tokenize 26 | elsif string = chunk[STRING, 1] 27 | StringTokenizer.new(string, tokenizer).tokenize 28 | elsif indent = chunk[EXPLICIT_INDENT, 1] 29 | ExplicitIndentTokenizer.new(indent, tokenizer).tokenize 30 | elsif indent = chunk[INDENT, 1] 31 | IndentTokenizer.new(indent, tokenizer).tokenize 32 | elsif operator = chunk[LONG_OPERATOR, 1] 33 | LongOperatorTokenizer.new(operator, tokenizer).tokenize 34 | elsif chunk.match(WHITESPACE) 35 | WhitespaceTokenizer.new('', tokenizer).tokenize 36 | else 37 | OperatorTokenizer.new(chunk[0,1], tokenizer).tokenize 38 | end 39 | end 40 | 41 | tokenizer.decrease_indents.tokens 42 | end 43 | 44 | end 45 | 46 | require_relative 'tokenizer/tokenizer' 47 | require_relative 'tokenizer/indent_tokenizer' 48 | require_relative 'tokenizer/identifier_tokenizer' 49 | require_relative 'tokenizer/constant_tokenizer' 50 | require_relative 'tokenizer/number_tokenizer' 51 | require_relative 'tokenizer/string_tokenizer' 52 | require_relative 'tokenizer/explicit_indent_tokenizer' 53 | require_relative 'tokenizer/whitespace_tokenizer' 54 | require_relative 'tokenizer/long_operator_tokenizer' 55 | require_relative 'tokenizer/operator_tokenizer' 56 | -------------------------------------------------------------------------------- /lexer/tokenizer/constant_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class ConstantTokenizer < Struct.new(:constant, :tokenizer) 2 | 3 | def tokenize 4 | tokenizer. 5 | move_position_right_by(steps). 6 | add_tokens(tokens) 7 | end 8 | 9 | def steps 10 | constant.size 11 | end 12 | 13 | def tokens 14 | [:CONSTANT, constant] 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lexer/tokenizer/explicit_indent_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class ExplicitIndentTokenizer < Struct.new(:indent, :tokenizer) 2 | 3 | def tokenize 4 | if indent.size <= tokenizer.current_indent 5 | raise "Bad indent level, got #{indent.size} indents, " + 6 | "expected > #{tokenizer.current_indent}" 7 | end 8 | 9 | tokenizer. 10 | move_position_right_by(steps). 11 | add_tokens(tokens). 12 | increase_indent(indent.size) 13 | end 14 | 15 | def steps 16 | indent.size + 2 17 | end 18 | 19 | def tokens 20 | [:INDENT, indent.size] 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lexer/tokenizer/identifier_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class IdentifierTokenizer < Struct.new(:identifier, :tokenizer) 2 | 3 | KEYWORDS = ['while', 'def', 'class', 'if', 'true', 'false', 'nil'] 4 | 5 | def tokenize 6 | tokenizer. 7 | move_position_right_by(steps). 8 | add_tokens(tokens) 9 | end 10 | 11 | def steps 12 | identifier.size 13 | end 14 | 15 | def tokens 16 | if KEYWORDS.include? identifier 17 | [identifier.upcase.to_sym, identifier] 18 | else 19 | [:IDENTIFIER, identifier] 20 | end 21 | end 22 | 23 | end 24 | 25 | -------------------------------------------------------------------------------- /lexer/tokenizer/indent_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class IndentTokenizer < Struct.new(:indent, :tokenizer) 2 | 3 | def tokenize 4 | unless indent.size <= tokenizer.current_indent 5 | raise "Missing ':'" 6 | end 7 | 8 | tokenizer. 9 | move_position_right_by(steps). 10 | decrease_indents(indent.size). 11 | add_tokens(tokens) 12 | end 13 | 14 | def steps 15 | indent.size + 1 16 | end 17 | 18 | def tokens 19 | [:NEWLINE, "\n"] 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /lexer/tokenizer/long_operator_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class LongOperatorTokenizer < Struct.new(:operator, :tokenizer) 2 | 3 | def tokenize 4 | tokenizer. 5 | move_position_right_by(steps). 6 | add_tokens(tokens) 7 | end 8 | 9 | def steps 10 | operator.size 11 | end 12 | 13 | def tokens 14 | [operator, operator] 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lexer/tokenizer/number_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class NumberTokenizer < Struct.new(:number, :tokenizer) 2 | 3 | def tokenize 4 | tokenizer. 5 | move_position_right_by(steps). 6 | add_tokens(tokens) 7 | end 8 | 9 | def steps 10 | number.size 11 | end 12 | 13 | def tokens 14 | [:NUMBER, number.to_i] 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lexer/tokenizer/operator_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class OperatorTokenizer < Struct.new(:operator, :tokenizer) 2 | 3 | def tokenize 4 | tokenizer. 5 | move_position_right_by(steps). 6 | add_tokens(tokens) 7 | end 8 | 9 | def steps 10 | 1 11 | end 12 | 13 | def tokens 14 | [operator, operator] 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lexer/tokenizer/string_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class StringTokenizer < Struct.new(:string, :tokenizer) 2 | 3 | def tokenize 4 | tokenizer. 5 | move_position_right_by(steps). 6 | add_tokens(tokens) 7 | end 8 | 9 | def steps 10 | string.size + 2 11 | end 12 | 13 | def tokens 14 | [:STRING, string] 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lexer/tokenizer/tokenizer.rb: -------------------------------------------------------------------------------- 1 | class Tokenizer 2 | 3 | attr_reader :tokens, :indent_stack, :position 4 | 5 | def initialize(args = {}) 6 | @tokens = args.fetch(:tokens, []) 7 | @indent_stack = args.fetch(:indent_stack, []) 8 | @position = args.fetch(:position, 0) 9 | end 10 | 11 | def arguments 12 | { 13 | tokens: tokens, 14 | indent_stack: indent_stack, 15 | position: position 16 | } 17 | end 18 | 19 | def move_position_right_by(steps) 20 | Tokenizer.new(arguments.merge({ 21 | position: position + steps 22 | })) 23 | end 24 | 25 | def add_tokens(new_tokens) 26 | Tokenizer.new(arguments.merge({ 27 | tokens: tokens + [new_tokens].compact, 28 | })) 29 | end 30 | 31 | def increase_indent(indent_size) 32 | Tokenizer.new(arguments.merge({ 33 | indent_stack: indent_stack + [indent_size], 34 | })) 35 | end 36 | 37 | def decrease_indents(new_indent = 0) 38 | (new_indent...current_indent).reduce(self, :decrease_indent) 39 | end 40 | 41 | def decrease_indent(new_indent) 42 | return self if new_indent > current_indent 43 | 44 | new_indent_stack = indent_stack[0..-2] 45 | new_current_indent = new_indent_stack.last || 0 46 | 47 | Tokenizer.new(arguments.merge({ 48 | tokens: tokens + [[:DEDENT, new_current_indent]], 49 | indent_stack: new_indent_stack, 50 | })) 51 | end 52 | 53 | def current_indent 54 | indent_stack.last || 0 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /lexer/tokenizer/whitespace_tokenizer.rb: -------------------------------------------------------------------------------- 1 | class WhitespaceTokenizer < Struct.new(:whitespace, :tokenizer) 2 | 3 | def tokenize 4 | tokenizer. 5 | move_position_right_by(steps). 6 | add_tokens(tokens) 7 | end 8 | 9 | def steps 10 | 1 11 | end 12 | 13 | def tokens 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /llvm_compiler.rb: -------------------------------------------------------------------------------- 1 | require 'compiler/llvm_compiler' 2 | -------------------------------------------------------------------------------- /parser.rb: -------------------------------------------------------------------------------- 1 | require 'parser/parser' 2 | -------------------------------------------------------------------------------- /parser/grammar.y: -------------------------------------------------------------------------------- 1 | class Parser 2 | 3 | token IF 4 | token DEF 5 | token CLASS 6 | token NEWLINE 7 | token NUMBER 8 | token STRING 9 | token TRUE FALSE NIL 10 | token IDENTIFIER 11 | token CONSTANT 12 | token INDENT DEDENT 13 | 14 | prechigh 15 | left '.' 16 | right '!' 17 | left '*' '/' 18 | left '+' '-' 19 | left '>' '>=' '<' '<=' 20 | left '==' '!=' 21 | left '&&' 22 | left '||' 23 | right '=' 24 | left ',' 25 | preclow 26 | 27 | rule 28 | 29 | Program: 30 | /* nothing */ { result = Nodes.new([]) } 31 | | Expressions { result = val[0] } 32 | ; 33 | 34 | Expressions: 35 | Expression { result = Nodes.new(val) } 36 | | Expressions Terminator Expression { result = val[0] << val[2] } 37 | | Expressions Terminator { result = val[0] } 38 | | Terminator { result = Nodes.new([]) } 39 | ; 40 | 41 | Expression: 42 | Literal 43 | | Call 44 | | Operator 45 | | GetConstant 46 | | SetConstant 47 | | GetLocal 48 | | SetLocal 49 | | Def 50 | | Class 51 | | If 52 | | '(' Expression ')' { result = val[1] } 53 | ; 54 | 55 | Terminator: 56 | NEWLINE 57 | | ';' 58 | ; 59 | 60 | Literal: 61 | NUMBER { result = NumberNode.new(val[0]) } 62 | | STRING { result = StringNode.new(val[0]) } 63 | | TRUE { result = TrueNode.new } 64 | | FALSE { result = Falsenew } 65 | | NIL { result = NilNode.new } 66 | ; 67 | 68 | Call: 69 | IDENTIFIER Arguments { result = CallNode.new(nil, val[0], val[1]) } 70 | | Expression '.' IDENTIFIER 71 | Arguments { result = CallNode.new(val[0], val[2], val[3]) } 72 | | Expression '.' IDENTIFIER { result = CallNode.new(val[0], val[2], []) } 73 | ; 74 | 75 | Arguments: 76 | '(' ')' { result = [] } 77 | | '(' ArgList ')' { result = val[1] } 78 | ; 79 | 80 | ArgList: 81 | Expression { result = val } 82 | | ArgList ',' Expression { result = val[0] << val[2] } 83 | ; 84 | 85 | Operator: 86 | Expression '||' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 87 | | Expression '&&' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 88 | | Expression '==' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 89 | | Expression '!=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 90 | | Expression '>' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 91 | | Expression '>=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 92 | | Expression '<' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 93 | | Expression '<=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 94 | | Expression '+' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 95 | | Expression '-' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 96 | | Expression '*' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 97 | | Expression '/' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 98 | ; 99 | 100 | GetConstant: 101 | CONSTANT { result = GetConstantNode.new(val[0])} 102 | ; 103 | 104 | SetConstant: 105 | CONSTANT '=' Expression { result = SetConstantNode.new(val[0], val[2]) } 106 | ; 107 | 108 | GetLocal: 109 | IDENTIFIER { result = GetLocalNode.new(val[0]) } 110 | ; 111 | 112 | SetLocal: 113 | IDENTIFIER '=' Expression { result = SetLocalNode.new(val[0], val[2]) } 114 | ; 115 | 116 | Block: 117 | INDENT Expressions DEDENT { result = val[1] } 118 | ; 119 | 120 | Def: 121 | DEF IDENTIFIER Block { result = DefNode.new(val[1], [], val[2]) } 122 | | DEF IDENTIFIER 123 | '(' ParamList ')' Block { result = DefNode.new(val[1], val[3], val[5]) } 124 | ; 125 | 126 | ParamList: 127 | /* nothing */ { result = [] } 128 | | IDENTIFIER { result = val } 129 | | ParamList ',' IDENTIFIER { result = val[0] << val[2] } 130 | ; 131 | 132 | Class: 133 | CLASS CONSTANT Block { result = ClassNode.new(val[1], val[2]) } 134 | ; 135 | 136 | If: 137 | IF Expression Block { result = IfNode.new(val[1], val[2]) } 138 | ; 139 | 140 | ---- header 141 | require 'lexer/lexer' 142 | require 'parser/nodes' 143 | 144 | 145 | ---- inner 146 | def parse(code, show_tokens = false) 147 | @tokens = Lexer.new.tokenize(code) 148 | puts @tokens.inspect if show_tokens 149 | do_parse 150 | end 151 | 152 | def next_token 153 | @tokens.shift 154 | end 155 | -------------------------------------------------------------------------------- /parser/nodes.rb: -------------------------------------------------------------------------------- 1 | class Nodes < Struct.new(:nodes) 2 | 3 | def <<(node) 4 | nodes << node 5 | self 6 | end 7 | 8 | end 9 | 10 | class LiteralNode < Struct.new(:value); end 11 | class NumberNode < LiteralNode; end 12 | class StringNode < LiteralNode; end 13 | 14 | class TrueNode < LiteralNode 15 | def initialize 16 | super(true) 17 | end 18 | end 19 | 20 | class FalseNode < LiteralNode 21 | def initialize 22 | super(false) 23 | end 24 | end 25 | 26 | class NilNode < LiteralNode 27 | def initialize 28 | super(nil) 29 | end 30 | end 31 | 32 | class CallNode < Struct.new(:receiver, :method, :arguments); end 33 | class GetConstantNode < Struct.new(:name); end 34 | class SetConstantNode < Struct.new(:name, :value); end 35 | class GetLocalNode < Struct.new(:name); end 36 | class SetLocalNode < Struct.new(:name, :value); end 37 | class DefNode < Struct.new(:name, :params, :body); end 38 | class ClassNode < Struct.new(:name, :body); end 39 | class IfNode < Struct.new(:condition, :body); end 40 | -------------------------------------------------------------------------------- /parser/parser.rb: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT MODIFY!!!! 3 | # This file is automatically generated by Racc 1.4.6 4 | # from Racc grammer file "". 5 | # 6 | 7 | require 'racc/parser.rb' 8 | 9 | require 'lexer/lexer' 10 | require 'parser/nodes' 11 | 12 | 13 | class Parser < Racc::Parser 14 | 15 | module_eval(<<'...end grammar.y/module_eval...', 'grammar.y', 146) 16 | def parse(code, show_tokens = false) 17 | @tokens = Lexer.new.tokenize(code) 18 | puts @tokens.inspect if show_tokens 19 | do_parse 20 | end 21 | 22 | def next_token 23 | @tokens.shift 24 | end 25 | ...end grammar.y/module_eval... 26 | ##### State transition tables begin ### 27 | 28 | racc_action_table = [ 29 | 27, 25, 26, 16, 18, 19, 20, 21, 22, 23, 30 | 24, 27, 25, 26, 16, 18, 19, 20, 21, 22, 31 | 23, 24, 30, 72, 41, 42, 39, 40, 53, 15, 32 | 30, 17, 41, 42, 39, 40, 30, 16, 16, 30, 33 | 15, 74, 17, 27, 25, 26, 84, 18, 19, 20, 34 | 21, 22, 23, 24, 27, 25, 26, 45, 18, 19, 35 | 20, 21, 22, 23, 24, 17, 17, 30, 72, 41, 36 | 42, 47, 15, 68, 30, 82, 41, 42, 39, 40, 37 | 27, 25, 26, 15, 18, 19, 20, 21, 22, 23, 38 | 24, 27, 25, 26, 48, 18, 19, 20, 21, 22, 39 | 23, 24, 30, 28, 41, 42, 27, 25, 26, 15, 40 | 18, 19, 20, 21, 22, 23, 24, 27, 25, 26, 41 | 15, 18, 19, 20, 21, 22, 23, 24, 86, 79, 42 | 85, 78, 27, 25, 26, 15, 18, 19, 20, 21, 43 | 22, 23, 24, 27, 25, 26, 15, 18, 19, 20, 44 | 21, 22, 23, 24, 46, 49, 45, 51, 27, 25, 45 | 26, 15, 18, 19, 20, 21, 22, 23, 24, 27, 46 | 25, 26, 15, 18, 19, 20, 21, 22, 23, 24, 47 | 72, 88, nil, nil, 27, 25, 26, 15, 18, 19, 48 | 20, 21, 22, 23, 24, 27, 25, 26, 15, 18, 49 | 19, 20, 21, 22, 23, 24, nil, nil, nil, nil, 50 | 27, 25, 26, 15, 18, 19, 20, 21, 22, 23, 51 | 24, 27, 25, 26, 15, 18, 19, 20, 21, 22, 52 | 23, 24, nil, nil, nil, nil, 27, 25, 26, 15, 53 | 18, 19, 20, 21, 22, 23, 24, 27, 25, 26, 54 | 15, 18, 19, 20, 21, 22, 23, 24, nil, nil, 55 | nil, nil, 27, 25, 26, 15, 18, 19, 20, 21, 56 | 22, 23, 24, 27, 25, 26, 15, 18, 19, 20, 57 | 21, 22, 23, 24, nil, nil, nil, nil, 27, 25, 58 | 26, 15, 18, 19, 20, 21, 22, 23, 24, nil, 59 | nil, 30, 15, 41, 42, 39, 40, 35, 36, 37, 60 | 38, 33, 34, 32, 31, nil, nil, 15, 66, 72, 61 | nil, 30, nil, 41, 42, 39, 40, 35, 36, 37, 62 | 38, 33, 34, 32, 31, 30, nil, 41, 42, 39, 63 | 40, 35, 36, 37, 38, 33, 34, 32, 31, 30, 64 | nil, 41, 42, 39, 40, 35, 36, 37, 38, 33, 65 | 34, 32, 31, 30, nil, 41, 42, 39, 40, 35, 66 | 36, 37, 38, 33, 34, 32, 31, 30, nil, 41, 67 | 42, 39, 40, 35, 36, 37, 38, 33, 34, 32, 68 | 31, 30, nil, 41, 42, 39, 40, 35, 36, 37, 69 | 38, 33, 34, 32, 31, 30, nil, 41, 42, 39, 70 | 40, 35, 36, 37, 38, 33, 34, 32, 31, 30, 71 | nil, 41, 42, 39, 40, 35, 36, 37, 38, 33, 72 | 34, 32, 30, nil, 41, 42, 39, 40, 35, 36, 73 | 37, 38, 33, 34, 30, nil, 41, 42, 39, 40, 74 | 35, 36, 37, 38, 30, nil, 41, 42, 39, 40, 75 | 35, 36, 37, 38, 30, nil, 41, 42, 39, 40 ] 76 | 77 | racc_action_check = [ 78 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79 | 0, 72, 72, 72, 72, 72, 72, 72, 72, 72, 80 | 72, 72, 61, 48, 61, 61, 61, 61, 30, 0, 81 | 60, 0, 60, 60, 60, 60, 64, 80, 2, 65, 82 | 72, 48, 72, 45, 45, 45, 80, 45, 45, 45, 83 | 45, 45, 45, 45, 29, 29, 29, 53, 29, 29, 84 | 29, 29, 29, 29, 29, 80, 2, 62, 49, 62, 85 | 62, 24, 45, 45, 58, 74, 58, 58, 58, 58, 86 | 15, 15, 15, 29, 15, 15, 15, 15, 15, 15, 87 | 15, 40, 40, 40, 25, 40, 40, 40, 40, 40, 88 | 40, 40, 63, 1, 63, 63, 41, 41, 41, 15, 89 | 41, 41, 41, 41, 41, 41, 41, 47, 47, 47, 90 | 40, 47, 47, 47, 47, 47, 47, 47, 81, 69, 91 | 81, 69, 46, 46, 46, 41, 46, 46, 46, 46, 92 | 46, 46, 46, 27, 27, 27, 47, 27, 27, 27, 93 | 27, 27, 27, 27, 23, 26, 23, 28, 39, 39, 94 | 39, 46, 39, 39, 39, 39, 39, 39, 39, 79, 95 | 79, 79, 27, 79, 79, 79, 79, 79, 79, 79, 96 | 85, 86, nil, nil, 42, 42, 42, 39, 42, 42, 97 | 42, 42, 42, 42, 42, 38, 38, 38, 79, 38, 98 | 38, 38, 38, 38, 38, 38, nil, nil, nil, nil, 99 | 32, 32, 32, 42, 32, 32, 32, 32, 32, 32, 100 | 32, 33, 33, 33, 38, 33, 33, 33, 33, 33, 101 | 33, 33, nil, nil, nil, nil, 34, 34, 34, 32, 102 | 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 103 | 33, 35, 35, 35, 35, 35, 35, 35, nil, nil, 104 | nil, nil, 36, 36, 36, 34, 36, 36, 36, 36, 105 | 36, 36, 36, 37, 37, 37, 35, 37, 37, 37, 106 | 37, 37, 37, 37, nil, nil, nil, nil, 31, 31, 107 | 31, 36, 31, 31, 31, 31, 31, 31, 31, nil, 108 | nil, 43, 37, 43, 43, 43, 43, 43, 43, 43, 109 | 43, 43, 43, 43, 43, nil, nil, 31, 43, 50, 110 | nil, 50, nil, 50, 50, 50, 50, 50, 50, 50, 111 | 50, 50, 50, 50, 50, 3, nil, 3, 3, 3, 112 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 52, 113 | nil, 52, 52, 52, 52, 52, 52, 52, 52, 52, 114 | 52, 52, 52, 83, nil, 83, 83, 83, 83, 83, 115 | 83, 83, 83, 83, 83, 83, 83, 70, nil, 70, 116 | 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 117 | 70, 71, nil, 71, 71, 71, 71, 71, 71, 71, 118 | 71, 71, 71, 71, 71, 67, nil, 67, 67, 67, 119 | 67, 67, 67, 67, 67, 67, 67, 67, 67, 54, 120 | nil, 54, 54, 54, 54, 54, 54, 54, 54, 54, 121 | 54, 54, 55, nil, 55, 55, 55, 55, 55, 55, 122 | 55, 55, 55, 55, 57, nil, 57, 57, 57, 57, 123 | 57, 57, 57, 57, 56, nil, 56, 56, 56, 56, 124 | 56, 56, 56, 56, 59, nil, 59, 59, 59, 59 ] 125 | 126 | racc_action_pointer = [ 127 | -2, 103, 33, 320, nil, nil, nil, nil, nil, nil, 128 | nil, nil, nil, nil, nil, 78, nil, nil, nil, nil, 129 | nil, nil, nil, 125, 42, 83, 143, 141, 157, 52, 130 | 17, 286, 208, 219, 234, 245, 260, 271, 193, 156, 131 | 89, 104, 182, 286, nil, 41, 130, 115, 10, 55, 132 | 306, nil, 334, 26, 404, 417, 439, 429, 59, 449, 133 | 15, 7, 52, 87, 21, 24, nil, 390, nil, 99, 134 | 362, 376, 9, nil, 64, nil, nil, nil, nil, 167, 135 | 32, 98, nil, 348, nil, 167, 170, nil, nil ] 136 | 137 | racc_action_default = [ 138 | -1, -56, -2, -3, -6, -7, -8, -9, -10, -11, 139 | -12, -13, -14, -15, -16, -56, -18, -19, -20, -21, 140 | -22, -23, -24, -46, -44, -56, -56, -56, -56, -5, 141 | -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, 142 | -56, -56, -56, -56, -25, -56, -56, -56, -56, -56, 143 | -56, 89, -4, -27, -32, -33, -34, -35, -36, -37, 144 | -38, -39, -40, -41, -42, -43, -17, -30, -28, -56, 145 | -47, -45, -56, -49, -51, -54, -55, -26, -29, -56, 146 | -56, -56, -52, -31, -48, -56, -56, -50, -53 ] 147 | 148 | racc_goto_table = [ 149 | 29, 2, 43, 73, 75, 76, 44, 1, 69, 81, 150 | nil, nil, nil, nil, 50, nil, 52, nil, 54, 55, 151 | 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 152 | nil, nil, 67, 70, 71, nil, 77, nil, nil, nil, 153 | 87, nil, nil, nil, nil, nil, nil, nil, nil, nil, 154 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 155 | nil, nil, nil, nil, nil, nil, 83, nil, nil, nil, 156 | nil, nil, nil, 80, nil, nil, nil, nil, 29 ] 157 | 158 | racc_goto_check = [ 159 | 4, 2, 3, 17, 17, 17, 15, 1, 16, 18, 160 | nil, nil, nil, nil, 3, nil, 3, nil, 3, 3, 161 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 162 | nil, nil, 3, 3, 3, nil, 15, nil, nil, nil, 163 | 17, nil, nil, nil, nil, nil, nil, nil, nil, nil, 164 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 165 | nil, nil, nil, nil, nil, nil, 3, nil, nil, nil, 166 | nil, nil, nil, 2, nil, nil, nil, nil, 4 ] 167 | 168 | racc_goto_pointer = [ 169 | nil, 7, 1, -13, -2, nil, nil, nil, nil, nil, 170 | nil, nil, nil, nil, nil, -17, -37, -45, -65 ] 171 | 172 | racc_goto_default = [ 173 | nil, nil, nil, 3, 4, 5, 6, 7, 8, 9, 174 | 10, 11, 12, 13, 14, nil, nil, nil, nil ] 175 | 176 | racc_reduce_table = [ 177 | 0, 0, :racc_error, 178 | 0, 35, :_reduce_1, 179 | 1, 35, :_reduce_2, 180 | 1, 36, :_reduce_3, 181 | 3, 36, :_reduce_4, 182 | 2, 36, :_reduce_5, 183 | 1, 36, :_reduce_6, 184 | 1, 37, :_reduce_none, 185 | 1, 37, :_reduce_none, 186 | 1, 37, :_reduce_none, 187 | 1, 37, :_reduce_none, 188 | 1, 37, :_reduce_none, 189 | 1, 37, :_reduce_none, 190 | 1, 37, :_reduce_none, 191 | 1, 37, :_reduce_none, 192 | 1, 37, :_reduce_none, 193 | 1, 37, :_reduce_none, 194 | 3, 37, :_reduce_17, 195 | 1, 38, :_reduce_none, 196 | 1, 38, :_reduce_none, 197 | 1, 39, :_reduce_20, 198 | 1, 39, :_reduce_21, 199 | 1, 39, :_reduce_22, 200 | 1, 39, :_reduce_23, 201 | 1, 39, :_reduce_24, 202 | 2, 40, :_reduce_25, 203 | 4, 40, :_reduce_26, 204 | 3, 40, :_reduce_27, 205 | 2, 49, :_reduce_28, 206 | 3, 49, :_reduce_29, 207 | 1, 50, :_reduce_30, 208 | 3, 50, :_reduce_31, 209 | 3, 41, :_reduce_32, 210 | 3, 41, :_reduce_33, 211 | 3, 41, :_reduce_34, 212 | 3, 41, :_reduce_35, 213 | 3, 41, :_reduce_36, 214 | 3, 41, :_reduce_37, 215 | 3, 41, :_reduce_38, 216 | 3, 41, :_reduce_39, 217 | 3, 41, :_reduce_40, 218 | 3, 41, :_reduce_41, 219 | 3, 41, :_reduce_42, 220 | 3, 41, :_reduce_43, 221 | 1, 42, :_reduce_44, 222 | 3, 43, :_reduce_45, 223 | 1, 44, :_reduce_46, 224 | 3, 45, :_reduce_47, 225 | 3, 51, :_reduce_48, 226 | 3, 46, :_reduce_49, 227 | 6, 46, :_reduce_50, 228 | 0, 52, :_reduce_51, 229 | 1, 52, :_reduce_52, 230 | 3, 52, :_reduce_53, 231 | 3, 47, :_reduce_54, 232 | 3, 48, :_reduce_55 ] 233 | 234 | racc_reduce_n = 56 235 | 236 | racc_shift_n = 89 237 | 238 | racc_token_table = { 239 | false => 0, 240 | :error => 1, 241 | :IF => 2, 242 | :DEF => 3, 243 | :CLASS => 4, 244 | :NEWLINE => 5, 245 | :NUMBER => 6, 246 | :STRING => 7, 247 | :TRUE => 8, 248 | :FALSE => 9, 249 | :NIL => 10, 250 | :IDENTIFIER => 11, 251 | :CONSTANT => 12, 252 | :INDENT => 13, 253 | :DEDENT => 14, 254 | "." => 15, 255 | "!" => 16, 256 | "*" => 17, 257 | "/" => 18, 258 | "+" => 19, 259 | "-" => 20, 260 | ">" => 21, 261 | ">=" => 22, 262 | "<" => 23, 263 | "<=" => 24, 264 | "==" => 25, 265 | "!=" => 26, 266 | "&&" => 27, 267 | "||" => 28, 268 | "=" => 29, 269 | "," => 30, 270 | "(" => 31, 271 | ")" => 32, 272 | ";" => 33 } 273 | 274 | racc_nt_base = 34 275 | 276 | racc_use_result_var = true 277 | 278 | Racc_arg = [ 279 | racc_action_table, 280 | racc_action_check, 281 | racc_action_default, 282 | racc_action_pointer, 283 | racc_goto_table, 284 | racc_goto_check, 285 | racc_goto_default, 286 | racc_goto_pointer, 287 | racc_nt_base, 288 | racc_reduce_table, 289 | racc_token_table, 290 | racc_shift_n, 291 | racc_reduce_n, 292 | racc_use_result_var ] 293 | 294 | Racc_token_to_s_table = [ 295 | "$end", 296 | "error", 297 | "IF", 298 | "DEF", 299 | "CLASS", 300 | "NEWLINE", 301 | "NUMBER", 302 | "STRING", 303 | "TRUE", 304 | "FALSE", 305 | "NIL", 306 | "IDENTIFIER", 307 | "CONSTANT", 308 | "INDENT", 309 | "DEDENT", 310 | "\".\"", 311 | "\"!\"", 312 | "\"*\"", 313 | "\"/\"", 314 | "\"+\"", 315 | "\"-\"", 316 | "\">\"", 317 | "\">=\"", 318 | "\"<\"", 319 | "\"<=\"", 320 | "\"==\"", 321 | "\"!=\"", 322 | "\"&&\"", 323 | "\"||\"", 324 | "\"=\"", 325 | "\",\"", 326 | "\"(\"", 327 | "\")\"", 328 | "\";\"", 329 | "$start", 330 | "Program", 331 | "Expressions", 332 | "Expression", 333 | "Terminator", 334 | "Literal", 335 | "Call", 336 | "Operator", 337 | "GetConstant", 338 | "SetConstant", 339 | "GetLocal", 340 | "SetLocal", 341 | "Def", 342 | "Class", 343 | "If", 344 | "Arguments", 345 | "ArgList", 346 | "Block", 347 | "ParamList" ] 348 | 349 | Racc_debug_parser = false 350 | 351 | ##### State transition tables end ##### 352 | 353 | # reduce 0 omitted 354 | 355 | module_eval(<<'.,.,', 'grammar.y', 29) 356 | def _reduce_1(val, _values, result) 357 | result = Nodes.new([]) 358 | result 359 | end 360 | .,., 361 | 362 | module_eval(<<'.,.,', 'grammar.y', 30) 363 | def _reduce_2(val, _values, result) 364 | result = val[0] 365 | result 366 | end 367 | .,., 368 | 369 | module_eval(<<'.,.,', 'grammar.y', 34) 370 | def _reduce_3(val, _values, result) 371 | result = Nodes.new(val) 372 | result 373 | end 374 | .,., 375 | 376 | module_eval(<<'.,.,', 'grammar.y', 35) 377 | def _reduce_4(val, _values, result) 378 | result = val[0] << val[2] 379 | result 380 | end 381 | .,., 382 | 383 | module_eval(<<'.,.,', 'grammar.y', 36) 384 | def _reduce_5(val, _values, result) 385 | result = val[0] 386 | result 387 | end 388 | .,., 389 | 390 | module_eval(<<'.,.,', 'grammar.y', 37) 391 | def _reduce_6(val, _values, result) 392 | result = Nodes.new([]) 393 | result 394 | end 395 | .,., 396 | 397 | # reduce 7 omitted 398 | 399 | # reduce 8 omitted 400 | 401 | # reduce 9 omitted 402 | 403 | # reduce 10 omitted 404 | 405 | # reduce 11 omitted 406 | 407 | # reduce 12 omitted 408 | 409 | # reduce 13 omitted 410 | 411 | # reduce 14 omitted 412 | 413 | # reduce 15 omitted 414 | 415 | # reduce 16 omitted 416 | 417 | module_eval(<<'.,.,', 'grammar.y', 51) 418 | def _reduce_17(val, _values, result) 419 | result = val[1] 420 | result 421 | end 422 | .,., 423 | 424 | # reduce 18 omitted 425 | 426 | # reduce 19 omitted 427 | 428 | module_eval(<<'.,.,', 'grammar.y', 60) 429 | def _reduce_20(val, _values, result) 430 | result = NumberNode.new(val[0]) 431 | result 432 | end 433 | .,., 434 | 435 | module_eval(<<'.,.,', 'grammar.y', 61) 436 | def _reduce_21(val, _values, result) 437 | result = StringNode.new(val[0]) 438 | result 439 | end 440 | .,., 441 | 442 | module_eval(<<'.,.,', 'grammar.y', 62) 443 | def _reduce_22(val, _values, result) 444 | result = TrueNode.new 445 | result 446 | end 447 | .,., 448 | 449 | module_eval(<<'.,.,', 'grammar.y', 63) 450 | def _reduce_23(val, _values, result) 451 | result = Falsenew 452 | result 453 | end 454 | .,., 455 | 456 | module_eval(<<'.,.,', 'grammar.y', 64) 457 | def _reduce_24(val, _values, result) 458 | result = NilNode.new 459 | result 460 | end 461 | .,., 462 | 463 | module_eval(<<'.,.,', 'grammar.y', 68) 464 | def _reduce_25(val, _values, result) 465 | result = CallNode.new(nil, val[0], val[1]) 466 | result 467 | end 468 | .,., 469 | 470 | module_eval(<<'.,.,', 'grammar.y', 70) 471 | def _reduce_26(val, _values, result) 472 | result = CallNode.new(val[0], val[2], val[3]) 473 | result 474 | end 475 | .,., 476 | 477 | module_eval(<<'.,.,', 'grammar.y', 71) 478 | def _reduce_27(val, _values, result) 479 | result = CallNode.new(val[0], val[2], []) 480 | result 481 | end 482 | .,., 483 | 484 | module_eval(<<'.,.,', 'grammar.y', 75) 485 | def _reduce_28(val, _values, result) 486 | result = [] 487 | result 488 | end 489 | .,., 490 | 491 | module_eval(<<'.,.,', 'grammar.y', 76) 492 | def _reduce_29(val, _values, result) 493 | result = val[1] 494 | result 495 | end 496 | .,., 497 | 498 | module_eval(<<'.,.,', 'grammar.y', 80) 499 | def _reduce_30(val, _values, result) 500 | result = val 501 | result 502 | end 503 | .,., 504 | 505 | module_eval(<<'.,.,', 'grammar.y', 81) 506 | def _reduce_31(val, _values, result) 507 | result = val[0] << val[2] 508 | result 509 | end 510 | .,., 511 | 512 | module_eval(<<'.,.,', 'grammar.y', 85) 513 | def _reduce_32(val, _values, result) 514 | result = CallNode.new(val[0], val[1], [val[2]]) 515 | result 516 | end 517 | .,., 518 | 519 | module_eval(<<'.,.,', 'grammar.y', 86) 520 | def _reduce_33(val, _values, result) 521 | result = CallNode.new(val[0], val[1], [val[2]]) 522 | result 523 | end 524 | .,., 525 | 526 | module_eval(<<'.,.,', 'grammar.y', 87) 527 | def _reduce_34(val, _values, result) 528 | result = CallNode.new(val[0], val[1], [val[2]]) 529 | result 530 | end 531 | .,., 532 | 533 | module_eval(<<'.,.,', 'grammar.y', 88) 534 | def _reduce_35(val, _values, result) 535 | result = CallNode.new(val[0], val[1], [val[2]]) 536 | result 537 | end 538 | .,., 539 | 540 | module_eval(<<'.,.,', 'grammar.y', 89) 541 | def _reduce_36(val, _values, result) 542 | result = CallNode.new(val[0], val[1], [val[2]]) 543 | result 544 | end 545 | .,., 546 | 547 | module_eval(<<'.,.,', 'grammar.y', 90) 548 | def _reduce_37(val, _values, result) 549 | result = CallNode.new(val[0], val[1], [val[2]]) 550 | result 551 | end 552 | .,., 553 | 554 | module_eval(<<'.,.,', 'grammar.y', 91) 555 | def _reduce_38(val, _values, result) 556 | result = CallNode.new(val[0], val[1], [val[2]]) 557 | result 558 | end 559 | .,., 560 | 561 | module_eval(<<'.,.,', 'grammar.y', 92) 562 | def _reduce_39(val, _values, result) 563 | result = CallNode.new(val[0], val[1], [val[2]]) 564 | result 565 | end 566 | .,., 567 | 568 | module_eval(<<'.,.,', 'grammar.y', 93) 569 | def _reduce_40(val, _values, result) 570 | result = CallNode.new(val[0], val[1], [val[2]]) 571 | result 572 | end 573 | .,., 574 | 575 | module_eval(<<'.,.,', 'grammar.y', 94) 576 | def _reduce_41(val, _values, result) 577 | result = CallNode.new(val[0], val[1], [val[2]]) 578 | result 579 | end 580 | .,., 581 | 582 | module_eval(<<'.,.,', 'grammar.y', 95) 583 | def _reduce_42(val, _values, result) 584 | result = CallNode.new(val[0], val[1], [val[2]]) 585 | result 586 | end 587 | .,., 588 | 589 | module_eval(<<'.,.,', 'grammar.y', 96) 590 | def _reduce_43(val, _values, result) 591 | result = CallNode.new(val[0], val[1], [val[2]]) 592 | result 593 | end 594 | .,., 595 | 596 | module_eval(<<'.,.,', 'grammar.y', 100) 597 | def _reduce_44(val, _values, result) 598 | result = GetConstantNode.new(val[0]) 599 | result 600 | end 601 | .,., 602 | 603 | module_eval(<<'.,.,', 'grammar.y', 104) 604 | def _reduce_45(val, _values, result) 605 | result = SetConstantNode.new(val[0], val[2]) 606 | result 607 | end 608 | .,., 609 | 610 | module_eval(<<'.,.,', 'grammar.y', 108) 611 | def _reduce_46(val, _values, result) 612 | result = GetLocalNode.new(val[0]) 613 | result 614 | end 615 | .,., 616 | 617 | module_eval(<<'.,.,', 'grammar.y', 112) 618 | def _reduce_47(val, _values, result) 619 | result = SetLocalNode.new(val[0], val[2]) 620 | result 621 | end 622 | .,., 623 | 624 | module_eval(<<'.,.,', 'grammar.y', 116) 625 | def _reduce_48(val, _values, result) 626 | result = val[1] 627 | result 628 | end 629 | .,., 630 | 631 | module_eval(<<'.,.,', 'grammar.y', 120) 632 | def _reduce_49(val, _values, result) 633 | result = DefNode.new(val[1], [], val[2]) 634 | result 635 | end 636 | .,., 637 | 638 | module_eval(<<'.,.,', 'grammar.y', 122) 639 | def _reduce_50(val, _values, result) 640 | result = DefNode.new(val[1], val[3], val[5]) 641 | result 642 | end 643 | .,., 644 | 645 | module_eval(<<'.,.,', 'grammar.y', 126) 646 | def _reduce_51(val, _values, result) 647 | result = [] 648 | result 649 | end 650 | .,., 651 | 652 | module_eval(<<'.,.,', 'grammar.y', 127) 653 | def _reduce_52(val, _values, result) 654 | result = val 655 | result 656 | end 657 | .,., 658 | 659 | module_eval(<<'.,.,', 'grammar.y', 128) 660 | def _reduce_53(val, _values, result) 661 | result = val[0] << val[2] 662 | result 663 | end 664 | .,., 665 | 666 | module_eval(<<'.,.,', 'grammar.y', 132) 667 | def _reduce_54(val, _values, result) 668 | result = ClassNode.new(val[1], val[2]) 669 | result 670 | end 671 | .,., 672 | 673 | module_eval(<<'.,.,', 'grammar.y', 136) 674 | def _reduce_55(val, _values, result) 675 | result = IfNode.new(val[1], val[2]) 676 | result 677 | end 678 | .,., 679 | 680 | def _reduce_none(val, _values, result) 681 | val[0] 682 | end 683 | 684 | end # class Parser 685 | -------------------------------------------------------------------------------- /runtime.rb: -------------------------------------------------------------------------------- 1 | require 'runtime/object' 2 | require 'runtime/class' 3 | require 'runtime/method' 4 | require 'runtime/context' 5 | require 'runtime/bootstrap' 6 | -------------------------------------------------------------------------------- /runtime/bootstrap.rb: -------------------------------------------------------------------------------- 1 | Constants = {} 2 | 3 | Constants['Class'] = AwesomeClass.new 4 | Constants['Class'].runtime_class = Constants['Class'] 5 | Constants['Object'] = AwesomeClass.new 6 | Constants['Number'] = AwesomeClass.new 7 | Constants['String'] = AwesomeClass.new 8 | 9 | root_self = Constants['Object'].new 10 | RootContext = Context.new(root_self) 11 | 12 | Constants['TrueClass'] = AwesomeClass.new 13 | Constants['FalseClass'] = AwesomeClass.new 14 | Constants['NilClass'] = AwesomeClass.new 15 | 16 | Constants['true'] = Constants['TrueClass'].new_with_value(true) 17 | Constants['false'] = Constants['FalseClass'].new_with_value(false) 18 | Constants['nil'] = Constants['NilClass'].new_with_value(nil) 19 | 20 | Constants['Class'].def :new do |receiver, arguments| 21 | receiver.new 22 | end 23 | 24 | Constants['Object'].def :print do |receiver, arguments| 25 | puts arguments.first.ruby_value 26 | Constants['nil'] 27 | end 28 | 29 | Constants['Number'].def :+ do |receiver, arguments| 30 | result = arguments.reduce(0) { |sum, arg| sum + arg.ruby_value } 31 | Constants['Number'].new_with_value(receiver.ruby_value + result) 32 | end 33 | -------------------------------------------------------------------------------- /runtime/class.rb: -------------------------------------------------------------------------------- 1 | class AwesomeClass < AwesomeObject 2 | 3 | attr_reader :runtime_methods 4 | 5 | def initialize 6 | @runtime_methods = {} 7 | @runtime_class = Constants['Class'] 8 | end 9 | 10 | def lookup(method_name) 11 | method = @runtime_methods[method_name] 12 | raise "Method not found: #{method_name}" if method.nil? 13 | method 14 | end 15 | 16 | def def(name, &block) 17 | @runtime_methods[name.to_s] = block 18 | end 19 | 20 | def new 21 | AwesomeObject.new(self) 22 | end 23 | 24 | def new_with_value(value) 25 | AwesomeObject.new(self, value) 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /runtime/context.rb: -------------------------------------------------------------------------------- 1 | class Context 2 | 3 | attr_reader :locals, :current_self, :current_class 4 | 5 | def initialize(current_self, 6 | current_class = current_self.runtime_class) 7 | @locals = {} 8 | @current_self = current_self 9 | @current_class = current_class 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /runtime/method.rb: -------------------------------------------------------------------------------- 1 | class AwesomeMethod 2 | 3 | def initialize(params, body) 4 | @params = params 5 | @body = body 6 | end 7 | 8 | def call(receiver, arguments) 9 | context = Context.new(receiver) 10 | 11 | @params.each_with_index do |param, index| 12 | context.locals[param] = arguments[index] 13 | end 14 | 15 | @body.eval(context) 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /runtime/object.rb: -------------------------------------------------------------------------------- 1 | class AwesomeObject 2 | 3 | attr_accessor :runtime_class, :ruby_value 4 | 5 | def initialize(runtime_class, ruby_value = self) 6 | @runtime_class = runtime_class 7 | @ruby_value = ruby_value 8 | end 9 | 10 | def call(method, arguments = []) 11 | @runtime_class.lookup(method).call(self, arguments) 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /test/bytecode_compiler_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'runtime' 3 | require 'vm' 4 | require 'bytecode_compiler' 5 | 6 | class BytecodeCompilerTest < Test::Unit::TestCase 7 | 8 | def test_compiling 9 | bytecode = BytecodeCompiler.new.compile("print(1+2)") 10 | 11 | expected_bytecode = [ 12 | PUSH_SELF, 13 | PUSH_NUMBER, 1, 14 | PUSH_NUMBER, 2, 15 | CALL, '+', 1, 16 | CALL, 'print', 1, 17 | RETURN 18 | ] 19 | 20 | assert_equal expected_bytecode, bytecode 21 | assert_prints("3\n") { VM.new.run(bytecode) } 22 | 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /test/interpreter_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'interpreter' 3 | 4 | class InterpreterTest < Test::Unit::TestCase 5 | 6 | def test_code 7 | code = <<-CODE 8 | class Awesome: 9 | def does_it_work: 10 | "yeah!" 11 | 12 | awesome_object = Awesome.new 13 | if awesome_object: 14 | print(awesome_object.does_it_work) 15 | CODE 16 | 17 | assert_prints("yeah!\n") { Interpreter.new.eval(code) } 18 | end 19 | 20 | def test_calling_other_method 21 | code = <<-CODE 22 | class Awesome: 23 | def does_it_call_another_method: 24 | does_it_work() 25 | def does_it_work: 26 | "yeah!" 27 | 28 | awesome_object = Awesome.new 29 | if awesome_object: 30 | print(awesome_object.does_it_call_another_method) 31 | CODE 32 | 33 | assert_prints("yeah!\n") { Interpreter.new.eval(code) } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/lexer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'lexer/lexer' 3 | 4 | class TestLexer < Test::Unit::TestCase 5 | 6 | def test_number 7 | assert_equal [[:NUMBER, 1]], Lexer.new.tokenize("1") 8 | end 9 | 10 | def test_string 11 | assert_equal [[:STRING, "hi"]], Lexer.new.tokenize('"hi"') 12 | end 13 | 14 | def test_identifier 15 | assert_equal [[:IDENTIFIER, "name"]], Lexer.new.tokenize('name') 16 | end 17 | 18 | def test_constant 19 | assert_equal [[:CONSTANT, "Name"]], Lexer.new.tokenize('Name') 20 | end 21 | 22 | def test_operator 23 | assert_equal [["+", "+"]], Lexer.new.tokenize('+') 24 | assert_equal [["||", "||"]], Lexer.new.tokenize('||') 25 | end 26 | 27 | def test_lexer_produces_right_tokens 28 | code = <<-CODE 29 | if 1: 30 | if 2: 31 | print("...") 32 | if false: 33 | pass 34 | print("done!") 35 | 2 36 | 37 | print "The End" 38 | CODE 39 | 40 | tokens = [ 41 | [:IF, 'if'], [:NUMBER, 1], 42 | [:INDENT, 2], 43 | [:IF, 'if'], [:NUMBER, 2], 44 | [:INDENT, 4], 45 | [:IDENTIFIER, 'print'], ['(', '('], 46 | [:STRING, '...'], 47 | [')', ')'], 48 | [:NEWLINE, "\n"], 49 | [:IF, 'if'], [:FALSE, 'false'], 50 | [:INDENT, 6], 51 | [:IDENTIFIER, 'pass'], 52 | 53 | [:DEDENT, 4], [:NEWLINE, "\n"], 54 | [:IDENTIFIER, 'print'], ['(', '('], 55 | [:STRING, 'done!'], 56 | [')', ')'], 57 | [:DEDENT, 2], [:NEWLINE, "\n"], 58 | [:NUMBER, 2], 59 | [:DEDENT, 0], [:NEWLINE, "\n"], 60 | [:NEWLINE, "\n"], 61 | [:IDENTIFIER, 'print'], [:STRING, 'The End'] 62 | ] 63 | 64 | assert_equal tokens, Lexer.new.tokenize(code) 65 | end 66 | 67 | def test_while 68 | code = <<-CODE 69 | while true: 70 | print("...") 71 | CODE 72 | tokens = [ 73 | [:WHILE, 'while'], [:TRUE, 'true'], 74 | [:INDENT, 2], 75 | [:IDENTIFIER, 'print'], ['(', '('], 76 | [:STRING, '...'], 77 | [')', ')'], 78 | [:DEDENT, 0], 79 | ] 80 | assert_equal tokens, Lexer.new.tokenize(code) 81 | end 82 | 83 | end 84 | -------------------------------------------------------------------------------- /test/llvm_compiler_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'runtime' 3 | require 'vm' 4 | require 'llvm_compiler' 5 | 6 | class LLVMCompilerTest < Test::Unit::TestCase 7 | 8 | def test_compiling 9 | code = <<-CODE 10 | def say_it: 11 | x = "This is compiled!" 12 | puts(x) 13 | say_it() 14 | CODE 15 | 16 | node = Parser.new.parse(code) 17 | 18 | compiler = LLVMCompiler.new 19 | compiler.preamble 20 | node.llvm_compile(compiler) 21 | compiler.finish 22 | 23 | compiler.dump 24 | 25 | compiler.optimize 26 | compiler.run 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /test/parser_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'parser/parser' 3 | 4 | class ParserTest < Test::Unit::TestCase 5 | 6 | def test_method_with_param 7 | code = <<-CODE 8 | def method(a, b): 9 | true 10 | CODE 11 | 12 | nodes = Nodes.new([ 13 | DefNode.new( 14 | 'method', 15 | ['a', 'b'], 16 | Nodes.new([TrueNode.new]) 17 | ) 18 | ]) 19 | 20 | assert_equal nodes, Parser.new.parse(code) 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /test/runtime_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'runtime' 3 | 4 | class RuntimeTest < Test::Unit::TestCase 5 | 6 | def test_mimicing_object_new 7 | object = Constants['Object'].call('new') 8 | assert_equal Constants['Object'], object.runtime_class 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path("../../", __FILE__) 2 | require 'test/unit' 3 | require 'stringio' 4 | 5 | class Test::Unit::TestCase 6 | 7 | def capture_streams 8 | out = StringIO.new 9 | $stdout = out 10 | $stderr = out 11 | yield 12 | out.rewind 13 | out.read 14 | ensure 15 | $stdout = STDOUT 16 | $stderr = STDERR 17 | end 18 | 19 | def assert_prints(expected, &block) 20 | assert_equal expected, capture_streams(&block) 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /test/vm_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'runtime' 3 | require 'vm' 4 | 5 | class VmTest < Test::Unit::TestCase 6 | 7 | def test_simple_addition 8 | bytecode = [ 9 | # opcode operands stack after description 10 | # ------------------------------------------------------------------------ 11 | PUSH_NUMBER, 1, # stack = [1] push 1, the receiver of "+" 12 | PUSH_NUMBER, 2, # stack = [1, 2] push 2, the argument for "+" 13 | CALL, "+", 1, # stack = [3] cal 1.+(2) and push the result 14 | RETURN # stack = [] 15 | ] 16 | 17 | result = VM.new.run(bytecode) 18 | 19 | assert_equal 3, result.ruby_value 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /vm.rb: -------------------------------------------------------------------------------- 1 | require 'vm/bytecode' 2 | require 'vm/vm' 3 | -------------------------------------------------------------------------------- /vm/bytecode.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Bytecode format 3 | # 4 | # Opcode Operands Stack before / after 5 | # ------------------------------------------------------------------------------ 6 | PUSH_NUMBER = 0 # Number to push on the stack [] [result] 7 | PUSH_SELF = 1 # [] [self] 8 | CALL = 2 # Method, Number of arguments [receiver, args] [result] 9 | RETURN = 3 10 | -------------------------------------------------------------------------------- /vm/vm.rb: -------------------------------------------------------------------------------- 1 | class VM 2 | 3 | def run(bytecode) 4 | 5 | stack = [] 6 | ip = 0 7 | 8 | while true 9 | case bytecode[ip] 10 | 11 | when PUSH_NUMBER 12 | ip += 1 13 | value = bytecode[ip] 14 | stack.push Constants['Number'].new_with_value(value) 15 | 16 | when PUSH_SELF 17 | stack.push RootContext.current_self 18 | 19 | when CALL 20 | ip += 1 21 | method = bytecode[ip] 22 | 23 | ip += 1 24 | argc = bytecode[ip] 25 | 26 | args = [] 27 | argc.times do 28 | args << stack.pop 29 | end 30 | receiver = stack.pop 31 | 32 | stack.push receiver.call(method, args) 33 | 34 | when RETURN 35 | return stack.pop 36 | 37 | end 38 | 39 | ip += 1 40 | end 41 | 42 | end 43 | 44 | end # class VM 45 | --------------------------------------------------------------------------------