├── Book ├── book │ ├── code │ │ ├── nano.save │ │ ├── Gemfile │ │ ├── runtime.rb │ │ ├── Gemfile.lock │ │ ├── test │ │ │ ├── mio │ │ │ │ ├── boolean.mio │ │ │ │ ├── if.mio │ │ │ │ └── oop.mio │ │ │ ├── test_helper.rb │ │ │ ├── mio_test.rb │ │ │ ├── llvm_compiler_test.rb │ │ │ ├── vm_test.rb │ │ │ ├── runtime_test.rb │ │ │ ├── bytecode_compiler_test.rb │ │ │ ├── interpreter_test.rb │ │ │ ├── parser_test.rb │ │ │ └── lexer_test.rb │ │ ├── Rakefile │ │ ├── example.awm │ │ ├── mio │ │ │ ├── if.mio │ │ │ ├── boolean.mio │ │ │ ├── bootstrap.rb │ │ │ ├── method.rb │ │ │ ├── object.rb │ │ │ └── message.rb │ │ ├── runtime │ │ │ ├── context.rb │ │ │ ├── method.rb │ │ │ ├── object.rb │ │ │ ├── class.rb │ │ │ └── bootstrap.rb │ │ ├── bytecode.rb │ │ ├── mio.rb │ │ ├── awesome │ │ ├── bracket_lexer.rb │ │ ├── vm.rb │ │ ├── bytecode_compiler.rb │ │ ├── nodes.rb │ │ ├── interpreter.rb │ │ ├── lexer.rb │ │ ├── llvm_compiler.rb │ │ ├── grammar.y │ │ ├── LICENSE │ │ └── parser.rb │ ├── Create Your Own Programming Language.epub │ ├── Create Your Own Programming Language.mobi │ └── Create Your Own Programming Language.pdf ├── jvm_lang │ ├── bin │ │ ├── yourlang.bat │ │ └── yourlang │ ├── test │ │ ├── eval.yl │ │ ├── while.yl │ │ ├── if.yl │ │ ├── number.yl │ │ ├── require.yl │ │ ├── string.yl │ │ ├── call.yl │ │ ├── method.yl │ │ ├── literals.yl │ │ ├── constant.yl │ │ ├── logic.yl │ │ ├── class.yl │ │ ├── exception.yl │ │ └── runner.rb │ ├── vendor │ │ └── antlr-3.1.1.jar │ ├── .gitignore │ ├── src │ │ └── yourlang │ │ │ ├── lang │ │ │ ├── nodes │ │ │ │ ├── Node.java │ │ │ │ ├── SelfNode.java │ │ │ │ ├── LiteralNode.java │ │ │ │ ├── InstanceVariableNode.java │ │ │ │ ├── ConstantNode.java │ │ │ │ ├── WhileNode.java │ │ │ │ ├── NotNode.java │ │ │ │ ├── LocalAssignNode.java │ │ │ │ ├── ConstantAssignNode.java │ │ │ │ ├── InstanceVariableAssignNode.java │ │ │ │ ├── AndNode.java │ │ │ │ ├── OrNode.java │ │ │ │ ├── IfNode.java │ │ │ │ ├── Nodes.java │ │ │ │ ├── MethodDefinitionNode.java │ │ │ │ ├── ClassDefinitionNode.java │ │ │ │ ├── TryNode.java │ │ │ │ └── CallNode.java │ │ │ ├── Evaluable.java │ │ │ ├── MethodNotFound.java │ │ │ ├── ArgumentError.java │ │ │ ├── TypeError.java │ │ │ ├── Method.java │ │ │ ├── OperatorMethod.java │ │ │ ├── YourLangRuntime.java │ │ │ ├── InterpretedMethod.java │ │ │ ├── ExceptionHandler.java │ │ │ ├── ValueObject.java │ │ │ ├── YourLangException.java │ │ │ ├── YourLangLexer.g │ │ │ ├── YourLangObject.java │ │ │ ├── Context.java │ │ │ ├── YourLangClass.java │ │ │ ├── Bootstrapper.java │ │ │ └── YourLangParser.g │ │ │ └── Main.java │ ├── setup.rb │ ├── README │ ├── build.xml │ └── LICENSE ├── CHANGELOG └── README ├── src ├── Gemfile ├── parser.rb ├── main ├── Specification.md ├── extend_core.rb ├── bracket_lexer.rb ├── lexer.rb └── shunting_yard.rb ├── README.md └── tests ├── test_shunting_yard.rb ├── tests ├── Unit_Test_Helper.rb └── test_lexer.rb /Book/book/code/nano.save: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Book/jvm_lang/bin/yourlang.bat: -------------------------------------------------------------------------------- 1 | @java -jar build/yourlang.jar %* -------------------------------------------------------------------------------- /Book/jvm_lang/test/eval.yl: -------------------------------------------------------------------------------- 1 | x = eval("1 + 2") 2 | 3 | print(x) 4 | # => 3 -------------------------------------------------------------------------------- /src/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'racc' 3 | gem 'readline' -------------------------------------------------------------------------------- /Book/book/code/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem "racc", "1.4.6" -------------------------------------------------------------------------------- /Book/jvm_lang/bin/yourlang: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | java -jar build/yourlang.jar "$@" -------------------------------------------------------------------------------- /Book/jvm_lang/vendor/antlr-3.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theideasmith/Awzum/HEAD/Book/jvm_lang/vendor/antlr-3.1.1.jar -------------------------------------------------------------------------------- /Book/jvm_lang/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | src/yourlang/lang/*Lexer.java 3 | src/yourlang/lang/*Parser.java 4 | src/yourlang/lang/*.tokens -------------------------------------------------------------------------------- /Book/jvm_lang/test/while.yl: -------------------------------------------------------------------------------- 1 | # While loop 2 | 3 | x = 0 4 | 5 | while x < 10 6 | x = x + 1 7 | end 8 | 9 | print(x) 10 | # => 10 -------------------------------------------------------------------------------- /Book/book/Create Your Own Programming Language.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theideasmith/Awzum/HEAD/Book/book/Create Your Own Programming Language.epub -------------------------------------------------------------------------------- /Book/book/Create Your Own Programming Language.mobi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theideasmith/Awzum/HEAD/Book/book/Create Your Own Programming Language.mobi -------------------------------------------------------------------------------- /Book/book/Create Your Own Programming Language.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theideasmith/Awzum/HEAD/Book/book/Create Your Own Programming Language.pdf -------------------------------------------------------------------------------- /Book/jvm_lang/test/if.yl: -------------------------------------------------------------------------------- 1 | if true 2 | print("ok") 3 | end 4 | # => ok 5 | 6 | if false 7 | print("uho...") 8 | else 9 | print("ok") 10 | end 11 | # => ok -------------------------------------------------------------------------------- /Book/book/code/runtime.rb: -------------------------------------------------------------------------------- 1 | require "runtime/object" 2 | require "runtime/class" 3 | require "runtime/method" 4 | require "runtime/context" 5 | require "runtime/bootstrap" 6 | -------------------------------------------------------------------------------- /Book/book/code/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | racc (1.4.6) 5 | 6 | PLATFORMS 7 | ruby 8 | 9 | DEPENDENCIES 10 | racc (= 1.4.6) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Awzum 2 | Reading a book on how to implement an interpreter in Ruby and adding my own spin to things. A place for me to experiment as I learn about how languages are formed. 3 | -------------------------------------------------------------------------------- /Book/book/code/test/mio/boolean.mio: -------------------------------------------------------------------------------- 1 | "yo" or("hi") print 2 | # => yo 3 | 4 | nil or("hi") print 5 | # => hi 6 | 7 | "yo" and("hi") print 8 | # => hi 9 | 10 | 1 and(2 or(3)) print 11 | # => 2 -------------------------------------------------------------------------------- /src/parser.rb: -------------------------------------------------------------------------------- 1 | #For this I will follow the book and use racc. Maybe later I can change to treetop or something a bit more powerful. Maybe I will one day actually be able to create AxiomLangrequire 'racc' -------------------------------------------------------------------------------- /Book/book/code/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new(:test) do |t| 4 | t.libs << '.' 5 | t.libs << 'test' 6 | t.pattern = 'test/**_test.rb' 7 | end 8 | 9 | task :default => :test -------------------------------------------------------------------------------- /Book/jvm_lang/test/number.yl: -------------------------------------------------------------------------------- 1 | # Numeric operations 2 | 3 | x = -0 4 | x = 1 5 | x = x + 2 * 3 6 | 7 | print(x) 8 | # => 7 9 | 10 | print(1.0) 11 | # => 1.0 12 | 13 | print(0.1) 14 | # => 0.1 15 | -------------------------------------------------------------------------------- /Book/book/code/example.awm: -------------------------------------------------------------------------------- 1 | class Awesome: 2 | def does_it_work: 3 | "yeah!" 4 | 5 | awesome_object = Awesome.new 6 | if awesome_object: 7 | print("awesome_object.does_it_work = ") 8 | print(awesome_object.does_it_work) 9 | -------------------------------------------------------------------------------- /Book/jvm_lang/test/require.yl: -------------------------------------------------------------------------------- 1 | if !LOADED 2 | print("File loaded!") 3 | LOADED = true 4 | require("test/require.yl") 5 | else 6 | print("File required!") 7 | end 8 | 9 | # => File loaded! 10 | # => File required! 11 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/Node.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | /** 6 | A node in the AST. Each node can be evaluated. 7 | */ 8 | public abstract class Node implements Evaluable { 9 | } -------------------------------------------------------------------------------- /tests/test_shunting_yard.rb: -------------------------------------------------------------------------------- 1 | require "./Unit_Test_Helper.rb" 2 | require_relative '../src/shunting_yard.rb' 3 | 4 | class LexerTest < Test::Unit::TestCase 5 | def test_simpleaddition 6 | assert_equal [3,4,"+"], Shunt.eval("3+4") 7 | end 8 | end -------------------------------------------------------------------------------- /Book/jvm_lang/test/string.yl: -------------------------------------------------------------------------------- 1 | # String operations 2 | 3 | print("hello" + " world") 4 | # => hello world 5 | 6 | print("hi".size) 7 | # => 2 8 | 9 | print("hello".substring(2)) 10 | # => llo 11 | 12 | print("hello".substring(2, 5)) 13 | # => llo -------------------------------------------------------------------------------- /Book/book/code/test/mio/if.mio: -------------------------------------------------------------------------------- 1 | if(true, 2 | "condition is true" print, 3 | # else 4 | "nope" print 5 | ) 6 | # => condition is true 7 | 8 | if(false, 9 | "nope" print, 10 | # else 11 | "condition is false" print 12 | ) 13 | # => condition is false -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/SelfNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class SelfNode extends Node { 6 | public YourLangObject eval(Context context) throws YourLangException { 7 | return context.getCurrentSelf(); 8 | } 9 | } -------------------------------------------------------------------------------- /Book/book/code/mio/if.mio: -------------------------------------------------------------------------------- 1 | # Implement if using boolean logic 2 | 3 | set_slot("if", method( 4 | # eval condition 5 | set_slot("condition", eval_arg(0)) 6 | condition and( # if true 7 | eval_arg(1) 8 | ) 9 | condition or( # if false (else) 10 | eval_arg(2) 11 | ) 12 | )) 13 | -------------------------------------------------------------------------------- /Book/book/code/runtime/context.rb: -------------------------------------------------------------------------------- 1 | class Context 2 | attr_reader :locals, :current_self, :current_class 3 | 4 | def initialize(current_self, current_class=current_self.runtime_class) 5 | @locals = {} 6 | @current_self = current_self 7 | @current_class = current_class 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /tests/tests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | #A more automated approached to unit tests 4 | Dir.foreach(Dir.pwd) do |filename| 5 | if filename.match /test_[a-z]+/ 6 | puts "Adding #{filename.capitalize.gsub("_"," ")} to test queue" 7 | file = filename 8 | system "ruby #{file}" if File.exists? file 9 | end 10 | end -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/Evaluable.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | import yourlang.lang.nodes.Node; 4 | 5 | /** 6 | Anything that can be evaluated inside a context must implement this interface. 7 | */ 8 | public interface Evaluable { 9 | YourLangObject eval(Context context) throws YourLangException; 10 | } 11 | -------------------------------------------------------------------------------- /Book/jvm_lang/test/call.yl: -------------------------------------------------------------------------------- 1 | # Method calls 2 | 3 | # No arguments, we can skip the parenthesis 4 | print 5 | # => 6 | 7 | # ... or not 8 | print() 9 | # => 10 | 11 | # multiple arguments do require parenthesis 12 | print(1, 2, 3) 13 | # => 1 14 | # => 2 15 | # => 3 16 | 17 | # self is implicit here 18 | self.print("selfing") 19 | # => selfing -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/MethodNotFound.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | /** 4 | Exception thrown when a unknown method is called. 5 | */ 6 | public class MethodNotFound extends YourLangException { 7 | public MethodNotFound(String method) { 8 | super(method + " not found"); 9 | setRuntimeClass("MethodNotFound"); 10 | } 11 | } -------------------------------------------------------------------------------- /Book/jvm_lang/test/method.yl: -------------------------------------------------------------------------------- 1 | # Methoc definition 2 | 3 | # Method names can end with ? or ! 4 | # If no parameters, parenthesis can be skipped. 5 | def no_args? 6 | print("indeed") 7 | end 8 | 9 | no_args? 10 | # => indeed 11 | 12 | def call_me!(name, calling) 13 | print(name, "driiiing!", calling) 14 | end 15 | 16 | call_me!("bob", "bill") 17 | # => bob 18 | # => driiiing! 19 | # => bill -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/LiteralNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class LiteralNode extends Node { 6 | YourLangObject value; 7 | 8 | public LiteralNode(YourLangObject value) { 9 | this.value = value; 10 | } 11 | 12 | public YourLangObject eval(Context context) throws YourLangException { 13 | return value; 14 | } 15 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/ArgumentError.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | /** 4 | Exception thrown when a unknown method is called. 5 | */ 6 | public class ArgumentError extends YourLangException { 7 | public ArgumentError(String method, int expected, int actual) { 8 | super("Expected " + expected + " arguments for " + method + ", got " + actual); 9 | setRuntimeClass("ArgumentError"); 10 | } 11 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/TypeError.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | /** 4 | Exception raised when an unexpected object type is passed to as a method argument. 5 | */ 6 | public class TypeError extends YourLangException { 7 | public TypeError(String expected, Object actual) { 8 | super("Expected type " + expected + ", got " + actual.getClass().getName()); 9 | setRuntimeClass("TypeError"); 10 | } 11 | } -------------------------------------------------------------------------------- /Book/book/code/mio/boolean.mio: -------------------------------------------------------------------------------- 1 | # An object is always truthy 2 | 3 | Object set_slot("and", method( 4 | eval_arg(0) 5 | )) 6 | Object set_slot("or", method( 7 | self 8 | )) 9 | 10 | # ... except nil and false which are false 11 | 12 | nil set_slot("and", nil) 13 | nil set_slot("or", method( 14 | eval_arg(0) 15 | )) 16 | 17 | false set_slot("and", false) 18 | false set_slot("or", method( 19 | eval_arg(0) 20 | )) 21 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/Method.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | /** 4 | A method attached to a YourLangClass. 5 | */ 6 | public abstract class Method { 7 | /** 8 | Calls the method. 9 | @param receiver Instance on which to call the method (self). 10 | @param arguments Arguments passed to the method. 11 | */ 12 | public abstract YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException; 13 | } 14 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/InstanceVariableNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class InstanceVariableNode extends Node { 6 | private String name; 7 | 8 | public InstanceVariableNode(String name) { 9 | this.name = name; 10 | } 11 | 12 | public YourLangObject eval(Context context) throws YourLangException { 13 | return context.getCurrentSelf().getInstanceVariable(name); 14 | } 15 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/ConstantNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | /** 6 | Get the value of a constant. 7 | */ 8 | public class ConstantNode extends Node { 9 | private String name; 10 | 11 | public ConstantNode(String name) { 12 | this.name = name; 13 | } 14 | 15 | public YourLangObject eval(Context context) throws YourLangException { 16 | return context.getCurrentClass().getConstant(name); 17 | } 18 | } -------------------------------------------------------------------------------- /Book/book/code/bytecode.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Bytecode format 3 | # 4 | # Opcode Operands Stack before / after 5 | # ------------------------------------------------------------------------ 6 | PUSH_NUMBER = 0 # Number to push on the stack [] [number] 7 | PUSH_SELF = 1 # [] [self] 8 | CALL = 2 # Method, Number of arguments [receiver, args] [result] 9 | RETURN = 3 10 | -------------------------------------------------------------------------------- /Book/jvm_lang/test/literals.yl: -------------------------------------------------------------------------------- 1 | # Literals of the language, each are stored in a ValueObject instance. 2 | 3 | print(true) 4 | # => true 5 | print(true.class.name) 6 | # => TrueClass 7 | 8 | print(nil) 9 | # => null 10 | print(nil.class.name) 11 | # => NilClass 12 | 13 | print(false) 14 | # => false 15 | print(false.class.name) 16 | # => FalseClass 17 | 18 | print("hi".class.name) 19 | # => String 20 | 21 | print(1.class.name) 22 | # => Integer 23 | 24 | print(1.2.class.name) 25 | # => Float -------------------------------------------------------------------------------- /Book/book/code/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path("../../", __FILE__) 2 | require "test/unit" 3 | require "stringio" 4 | 5 | class Test::Unit::TestCase 6 | def capture_streams 7 | out = StringIO.new 8 | $stdout = out 9 | $stderr = out 10 | yield 11 | out.rewind 12 | out.read 13 | ensure 14 | $stdout = STDOUT 15 | $stderr = STDERR 16 | end 17 | 18 | def assert_prints(expected, &block) 19 | assert_equal expected, capture_streams(&block) 20 | end 21 | end -------------------------------------------------------------------------------- /Book/jvm_lang/test/constant.yl: -------------------------------------------------------------------------------- 1 | # Constants start with a capital letter. 2 | X = 1 3 | 4 | print(X) 5 | # => 1 6 | 7 | class ConstantTest 8 | Y = 2 9 | 10 | def get_y 11 | # Constants are stored at class-level and shared by all instances. 12 | Y 13 | end 14 | 15 | def get_x 16 | # Constants are looked up in parent classes. 17 | X 18 | end 19 | end 20 | 21 | constant_test = ConstantTest.new 22 | 23 | print(constant_test.get_x) 24 | # => 1 25 | 26 | print(constant_test.get_y) 27 | # => 2 -------------------------------------------------------------------------------- /src/main: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | if file = ARGV.first && File.exists(ARGV.first) 4 | interpreter.eval File.read(file) 5 | 6 | else 7 | puts "Awzum REPL, CTRL+C to quit" 8 | loop do 9 | line = puts ">>"; gets.chomp # Until Readline is implemented 10 | # Readline::HISTORY.push(line) # Fancy stuff for later. Focus on building core lang 11 | value = interpreter.eval(line) # 2. Eval 12 | puts "=> #{value.ruby_value.inspect}" # 3. Print 13 | end # 4. Loop 14 | 15 | end -------------------------------------------------------------------------------- /Book/jvm_lang/test/logic.yl: -------------------------------------------------------------------------------- 1 | # Logical operators 2 | 3 | print(true && false) 4 | # => false 5 | 6 | print(true && true) 7 | # => true 8 | 9 | print("hi" && "there") 10 | # => there 11 | 12 | print(true || false) 13 | # => true 14 | 15 | print(false || 1) 16 | # => 1 17 | 18 | print(false || 3 && nil) 19 | # => null 20 | 21 | print(!true) 22 | # => false 23 | 24 | print(!"hi") 25 | # => false 26 | 27 | print(!false) 28 | # => true 29 | 30 | print(!!1) 31 | # => true 32 | 33 | print(1 < 4) 34 | # => true 35 | 36 | print(5 < 4) 37 | # => false -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/WhileNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class WhileNode extends Node { 6 | private Node condition; 7 | private Node body; 8 | 9 | public WhileNode(Node condition, Node body) { 10 | this.condition = condition; 11 | this.body = body; 12 | } 13 | 14 | public YourLangObject eval(Context context) throws YourLangException { 15 | while (condition.eval(context).isTrue()) { 16 | body.eval(context); 17 | } 18 | return YourLangRuntime.getNil(); 19 | } 20 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/NotNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | /** 6 | Negate a value. 7 | */ 8 | public class NotNode extends Node { 9 | private Node receiver; 10 | 11 | /** 12 | !receiver 13 | */ 14 | public NotNode(Node receiver) { 15 | this.receiver = receiver; 16 | } 17 | 18 | public YourLangObject eval(Context context) throws YourLangException { 19 | if (receiver.eval(context).isTrue()) 20 | return YourLangRuntime.getFalse(); 21 | return YourLangRuntime.getTrue(); 22 | } 23 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/LocalAssignNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class LocalAssignNode extends Node { 6 | private String name; 7 | private Node expression; 8 | 9 | public LocalAssignNode(String name, Node expression) { 10 | this.name = name; 11 | this.expression = expression; 12 | } 13 | 14 | public YourLangObject eval(Context context) throws YourLangException { 15 | YourLangObject value = expression.eval(context); 16 | context.setLocal(name, value); 17 | return value; 18 | } 19 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/ConstantAssignNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class ConstantAssignNode extends Node { 6 | private String name; 7 | private Node expression; 8 | 9 | public ConstantAssignNode(String name, Node expression) { 10 | this.name = name; 11 | this.expression = expression; 12 | } 13 | 14 | public YourLangObject eval(Context context) throws YourLangException { 15 | YourLangObject value = expression.eval(context); 16 | context.getCurrentClass().setConstant(name, value); 17 | return value; 18 | } 19 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/OperatorMethod.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | /** 4 | Specialized method of operators (+, -, *, /, etc.) 5 | */ 6 | public abstract class OperatorMethod extends Method { 7 | @SuppressWarnings("unchecked") 8 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 9 | T self = (T) receiver.as(ValueObject.class).getValue(); 10 | T arg = (T) arguments[0].as(ValueObject.class).getValue(); 11 | return perform(self, arg); 12 | } 13 | 14 | public abstract YourLangObject perform(T receiver, T argument) throws YourLangException; 15 | } 16 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/InstanceVariableAssignNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class InstanceVariableAssignNode extends Node { 6 | private String name; 7 | private Node expression; 8 | 9 | public InstanceVariableAssignNode(String name, Node expression) { 10 | this.name = name; 11 | this.expression = expression; 12 | } 13 | 14 | public YourLangObject eval(Context context) throws YourLangException { 15 | YourLangObject value = expression.eval(context); 16 | context.getCurrentSelf().setInstanceVariable(name, value); 17 | return value; 18 | } 19 | } -------------------------------------------------------------------------------- /Book/book/code/test/mio/oop.mio: -------------------------------------------------------------------------------- 1 | # Create a new object, by cloning the master Object 2 | set_slot("dude", Object clone) 3 | # Set a slot on it 4 | dude set_slot("name", "Bob") 5 | # Call the slot to retrieve its value 6 | dude name print 7 | # => Bob 8 | 9 | # Define a method 10 | dude set_slot("say_name", method( 11 | # Print unevaluated arguments (messages) 12 | arguments print 13 | # => 14 | 15 | # Eval the first argument 16 | eval_arg(0) print 17 | # => hello... 18 | 19 | # Access the receiver via `self` 20 | self name print 21 | # => Bob 22 | )) 23 | 24 | # Call that method 25 | dude say_name("hello...") 26 | -------------------------------------------------------------------------------- /Book/book/code/mio.rb: -------------------------------------------------------------------------------- 1 | $:.unshift "." 2 | require "mio/object" 3 | require "mio/message" 4 | require "mio/method" 5 | require "mio/bootstrap" 6 | 7 | module Mio 8 | class Error < RuntimeError 9 | attr_accessor :current_message 10 | 11 | def message 12 | super + " in message `#{@current_message.to_s}` at line #{@current_message.line}" 13 | end 14 | end 15 | 16 | def self.eval(code) 17 | # Parse 18 | message = Message.parse(code) 19 | # Eval 20 | message.call(Lobby) 21 | end 22 | 23 | def self.load(file) 24 | eval File.read(file) 25 | end 26 | 27 | load "mio/boolean.mio" 28 | load "mio/if.mio" 29 | end 30 | -------------------------------------------------------------------------------- /Book/jvm_lang/test/class.yl: -------------------------------------------------------------------------------- 1 | # Class definition 2 | 3 | print(self.class.name) 4 | # => Object 5 | 6 | # Same syntax as Ruby, "< Object" is implicit is no superclass is specified. 7 | class Language < Object 8 | def initialize(name) 9 | # Instance variable start with an @ 10 | @name = name 11 | end 12 | 13 | def name 14 | @name 15 | end 16 | end 17 | 18 | # Calling "new" allocated a new object of that class and calls "initialize" 19 | # passing all the arguments. 20 | lang = Language.new("YourLang") 21 | 22 | print(lang.name) 23 | # => YourLang 24 | print(lang.class.name) 25 | # => Language 26 | print(Language.superclass.name) 27 | # => Object -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/AndNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class AndNode extends Node { 6 | private Node receiver; 7 | private Node argument; 8 | 9 | /** 10 | receiver && argument 11 | */ 12 | public AndNode(Node receiver, Node argument) { 13 | this.receiver = receiver; 14 | this.argument = argument; 15 | } 16 | 17 | public YourLangObject eval(Context context) throws YourLangException { 18 | YourLangObject receiverEvaled = receiver.eval(context); 19 | if (receiverEvaled.isTrue()) 20 | return argument.eval(context); 21 | return receiverEvaled; 22 | } 23 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/OrNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class OrNode extends Node { 6 | private Node receiver; 7 | private Node argument; 8 | 9 | /** 10 | receiver || argument 11 | */ 12 | public OrNode(Node receiver, Node argument) { 13 | this.receiver = receiver; 14 | this.argument = argument; 15 | } 16 | 17 | public YourLangObject eval(Context context) throws YourLangException { 18 | YourLangObject receiverEvaled = receiver.eval(context); 19 | if (receiverEvaled.isTrue()) 20 | return receiverEvaled; 21 | return argument.eval(context); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/Unit_Test_Helper.rb: -------------------------------------------------------------------------------- 1 | #I didn't write this testing suite helper. 2 | #I've never done real unit testing in ruby and as it is 1am right now am too tired to write my own unit testing class 3 | $:.unshift File.expand_path("../../", __FILE__) 4 | require "test/unit" 5 | require "stringio" 6 | 7 | class Test::Unit::TestCase 8 | def capture_streams 9 | out = StringIO.new 10 | $stdout = out 11 | $stderr = out 12 | yield 13 | out.rewind 14 | out.read 15 | ensure 16 | $stdout = STDOUT 17 | $stderr = STDERR 18 | end 19 | 20 | def assert_prints(expected, &block) 21 | assert_equal expected, capture_streams(&block) 22 | end 23 | end -------------------------------------------------------------------------------- /Book/book/code/runtime/method.rb: -------------------------------------------------------------------------------- 1 | class AwesomeMethod 2 | def initialize(params, body) 3 | @params = params 4 | @body = body 5 | end 6 | 7 | def call(receiver, arguments) 8 | # Create a context of evaluation in which the method will execute. 9 | context = Context.new(receiver) 10 | 11 | # Assign passed arguments to local variables. 12 | @params.each_with_index do |param, index| 13 | context.locals[param] = arguments[index] 14 | end 15 | 16 | # The body is a node (created in the parser). 17 | # We'll talk in details about the `eval` method in the interpreter chapter. 18 | @body.eval(context) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /Book/book/code/test/mio_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "mio" 3 | 4 | class MioTest < Test::Unit::TestCase 5 | Dir["test/mio/*.mio"].each do |file| 6 | name = File.basename(file, ".mio") 7 | 8 | # Define a test method for each .mio file under test/mio. 9 | # The test will assert the output of the program is the same as the concatenation of all `# => ` 10 | # markers in the code. 11 | define_method "test_#{name}" do 12 | expected = File.read(file).split("\n").map { |l| l[/^ *# => (.*)$/, 1] }.compact.join("\n") 13 | actual = capture_streams { Mio.load file }.chomp 14 | assert_equal expected, actual 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /Book/jvm_lang/test/exception.yl: -------------------------------------------------------------------------------- 1 | # Exception handling 2 | 3 | # Any exception raised in the try block, will by catched by the appropriate 4 | # catch block, is present, or else, reraised. 5 | try 6 | # To raise an exception, call "raise!" on its instance. 7 | Exception.new("catch this").raise! 8 | print("This is never executed, NEVER!") 9 | # Store the exception in the variable e 10 | catch Exception : e 11 | print(e.class.name, e.message) 12 | end 13 | # => Exception 14 | # => catch this 15 | 16 | # We can specify several catch blocks. 17 | try 18 | ouch! 19 | catch MethodNotFound : e 20 | print(e.class.name, "ok") 21 | catch Exception : e 22 | print("oop") 23 | end 24 | # => MethodNotFound 25 | # => ok -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/IfNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | 5 | public class IfNode extends Node { 6 | private Node condition; 7 | private Node ifBody; 8 | private Node elseBody; 9 | 10 | public IfNode(Node condition, Node ifBody, Node elseBody) { 11 | this.condition = condition; 12 | this.ifBody = ifBody; 13 | this.elseBody = elseBody; 14 | } 15 | 16 | public YourLangObject eval(Context context) throws YourLangException { 17 | if (condition.eval(context).isTrue()) { 18 | return ifBody.eval(context); 19 | } else if (elseBody != null) { 20 | return elseBody.eval(context); 21 | } 22 | return YourLangRuntime.getNil(); 23 | } 24 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/Nodes.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | import java.util.ArrayList; 5 | 6 | /** 7 | Collection of nodes. 8 | */ 9 | public class Nodes extends Node { 10 | private ArrayList nodes; 11 | 12 | public Nodes() { 13 | nodes = new ArrayList(); 14 | } 15 | 16 | public void add(Node n) { 17 | nodes.add(n); 18 | } 19 | 20 | /** 21 | Eval all the nodes and return the last returned value. 22 | */ 23 | public YourLangObject eval(Context context) throws YourLangException { 24 | YourLangObject lastEval = YourLangRuntime.getNil(); 25 | for (Node n : nodes) { 26 | lastEval = n.eval(context); 27 | } 28 | return lastEval; 29 | } 30 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/Main.java: -------------------------------------------------------------------------------- 1 | package yourlang; 2 | 3 | import java.io.Reader; 4 | import java.io.StringReader; 5 | import java.io.FileReader; 6 | 7 | import yourlang.lang.Bootstrapper; 8 | 9 | public class Main { 10 | public static void main(String[] args) throws Exception { 11 | Reader reader = null; 12 | boolean debug = false; 13 | 14 | for (int i = 0; i < args.length; i++) { 15 | if (args[i].equals("-e")) reader = new StringReader(args[++i]); 16 | else if (args[i].equals("-d")) debug = true; 17 | else reader = new FileReader(args[i]); 18 | } 19 | 20 | if (reader == null) { 21 | System.out.println("usage: yourlang [-d] < -e code | file.yl >"); 22 | System.exit(1); 23 | } 24 | 25 | Bootstrapper.run().eval(reader); 26 | } 27 | } -------------------------------------------------------------------------------- /Book/book/code/test/llvm_compiler_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | begin 4 | require "llvm_compiler" 5 | 6 | class LLVMCompilerTest < Test::Unit::TestCase 7 | def test_compile 8 | code = <<-CODE 9 | def say_it: 10 | x = "This is compiled!" 11 | puts(x) 12 | say_it() 13 | CODE 14 | 15 | # Parse the code 16 | node = Parser.new.parse(code) 17 | 18 | # Compile it 19 | compiler = LLVMCompiler.new 20 | compiler.preamble 21 | node.llvm_compile(compiler) 22 | compiler.finish 23 | 24 | # Uncomment to output LLVM byte-code 25 | # compiler.dump 26 | 27 | # Optimize the LLVM byte-code 28 | compiler.optimize 29 | 30 | # JIT compile & execute 31 | compiler.run 32 | end 33 | end 34 | 35 | rescue LoadError 36 | warn "Skipping compiler tests: gem install ruby-llvm to run" 37 | end -------------------------------------------------------------------------------- /Book/book/code/runtime/object.rb: -------------------------------------------------------------------------------- 1 | class AwesomeObject 2 | # Each object has a class (named runtime_class to prevent conflicts 3 | # with Ruby's class keyword). 4 | # Optionally an object can hold a Ruby value. Eg.: numbers and strings will store their 5 | # number or string Ruby equivalent in that variable. 6 | attr_accessor :runtime_class, :ruby_value 7 | 8 | def initialize(runtime_class, ruby_value=self) 9 | @runtime_class = runtime_class 10 | @ruby_value = ruby_value 11 | end 12 | 13 | # Like a typical Class-based runtime model, we store methods in the class of the 14 | # object. When calling a method on an object, we need to first lookup that 15 | # method in the class, and then call it. 16 | def call(method, arguments=[]) 17 | @runtime_class.lookup(method).call(self, arguments) 18 | end 19 | end -------------------------------------------------------------------------------- /Book/book/code/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 a file is given we eval it. 16 | if file = ARGV.first 17 | interpreter.eval File.read(file) 18 | 19 | # Start the REPL, read-eval-print-loop, or interactive interpreter 20 | else 21 | puts "Awesome REPL, CTRL+C to quit" 22 | loop do 23 | line = Readline::readline(">> ") # 1. Read 24 | Readline::HISTORY.push(line) 25 | value = interpreter.eval(line) # 2. Eval 26 | puts "=> #{value.ruby_value.inspect}" # 3. Print 27 | end # 4. Loop 28 | 29 | end -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/MethodDefinitionNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import java.util.List; 4 | 5 | import yourlang.lang.*; 6 | 7 | public class MethodDefinitionNode extends Node { 8 | private String name; 9 | private Node body; 10 | private List parameters; 11 | 12 | public MethodDefinitionNode(String name, List parameters, Node body) { 13 | this.name = name; 14 | this.parameters = parameters; 15 | this.body = body; 16 | } 17 | 18 | public YourLangObject eval(Context context) throws YourLangException { 19 | String parameterNames[]; 20 | if (parameters == null) { 21 | parameterNames = new String[0]; 22 | } else { 23 | parameterNames = parameters.toArray(new String[0]); 24 | } 25 | 26 | context.getCurrentClass().addMethod(name, new InterpretedMethod(name, parameterNames, body)); 27 | return YourLangRuntime.getNil(); 28 | } 29 | } -------------------------------------------------------------------------------- /Book/book/code/runtime/class.rb: -------------------------------------------------------------------------------- 1 | class AwesomeClass < AwesomeObject 2 | # Classes are objects in Awesome so they inherit from AwesomeObject. 3 | 4 | attr_reader :runtime_methods 5 | 6 | def initialize 7 | @runtime_methods = {} 8 | @runtime_class = Constants["Class"] 9 | end 10 | 11 | # Lookup a method 12 | def lookup(method_name) 13 | method = @runtime_methods[method_name] 14 | raise "Method not found: #{method_name}" if method.nil? 15 | method 16 | end 17 | 18 | # Helper method to define a method on this class from Ruby. 19 | def def(name, &block) 20 | @runtime_methods[name.to_s] = block 21 | end 22 | 23 | # Create a new instance of this class 24 | def new 25 | AwesomeObject.new(self) 26 | end 27 | 28 | # Create an instance of this Awesome class that holds a Ruby value. Like a String, 29 | # Number or true. 30 | def new_with_value(value) 31 | AwesomeObject.new(self, value) 32 | end 33 | end -------------------------------------------------------------------------------- /Book/book/code/test/vm_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "vm" 3 | 4 | # In case you didn't complete exercises in Runtime chapter. 5 | # This is the solution. 6 | Constants["Number"].def :+ do |receiver, arguments| 7 | result = receiver.ruby_value + arguments.first.ruby_value 8 | Constants["Number"].new_with_value(result) 9 | end 10 | 11 | class VMTest < Test::Unit::TestCase 12 | def test_run 13 | bytecode = [ 14 | # opcode operands stack after description 15 | # ------------------------------------------------------------------------ 16 | PUSH_NUMBER, 1, # stack = [1] push 1, the receiver of "+" 17 | PUSH_NUMBER, 2, # stack = [1, 2] push 2, the argument for "+" 18 | CALL, "+", 1, # stack = [3] call 1.+(2) and push the result 19 | RETURN # stack = [] 20 | ] 21 | 22 | result = VM.new.run(bytecode) 23 | 24 | assert_equal 3, result.ruby_value 25 | end 26 | end -------------------------------------------------------------------------------- /Book/book/code/test/runtime_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "runtime" 3 | 4 | class RuntimeTest < Test::Unit::TestCase 5 | def test_get_constant 6 | assert_not_nil Constants["Object"] 7 | end 8 | 9 | def test_create_an_object 10 | assert_equal Constants["Object"], Constants["Object"].new.runtime_class 11 | end 12 | 13 | def test_create_an_object_mapped_to_ruby_value 14 | assert_equal 32, Constants["Number"].new_with_value(32).ruby_value 15 | end 16 | 17 | def test_lookup_method_in_class 18 | assert_not_nil Constants["Object"].lookup("print") 19 | assert_raise(RuntimeError) { Constants["Object"].lookup("non-existant") } 20 | end 21 | 22 | def test_call_method 23 | # Mimic Object.new in the language 24 | object = Constants["Object"].call("new") 25 | 26 | assert_equal Constants["Object"], object.runtime_class # assert object is an Object 27 | end 28 | 29 | def test_a_class_is_a_class 30 | assert_equal Constants["Class"], Constants["Number"].runtime_class 31 | end 32 | end -------------------------------------------------------------------------------- /Book/jvm_lang/setup.rb: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | include FileUtils 3 | 4 | puts "Your Language setup script" 5 | puts "==========================" 6 | 7 | print "Enter the desired name of your language (no space, CamelCase, eg.: YourLang): " 8 | NAME = gets.chomp 9 | exit if NAME.empty? 10 | DIRNAME = NAME.downcase 11 | 12 | print "Enter the desired extension of a language file, without the dot (eg.: yl): " 13 | EXT = gets.chomp 14 | exit if EXT.empty? 15 | 16 | puts "Installing files, this can take a while ..." 17 | 18 | def rename(content) 19 | content.gsub("YourLang", NAME).gsub("yourlang", DIRNAME).gsub(".yl", ".#{EXT}") 20 | end 21 | 22 | mv "src/yourlang", "src/#{DIRNAME}" 23 | files = (Dir["*"] + Dir["{bin,src,test}/**/*"].sort_by { |f| f.size } - ["setup.rb"]).select { |f| File.file?(f) } 24 | 25 | files.each do |file| 26 | file_content = rename(File.read(file)) 27 | File.open(file, 'w') { |f| f << file_content } 28 | mv file, rename(file) unless file == rename(file) 29 | end 30 | 31 | puts "Setup complete. Your language '#{NAME}' is ready!" 32 | puts 33 | 34 | puts File.read("README") -------------------------------------------------------------------------------- /Book/book/code/mio/bootstrap.rb: -------------------------------------------------------------------------------- 1 | module Mio 2 | # Bootstrap 3 | object = Object.new 4 | 5 | object.def "clone" do |receiver, context| 6 | receiver.clone 7 | end 8 | object.def "set_slot" do |receiver, context, name, value| 9 | receiver[name.call(context).value] = value.call(context) 10 | end 11 | object.def "print" do |receiver, context| 12 | puts receiver.value 13 | Lobby["nil"] 14 | end 15 | 16 | # Introducing the Lobby! Where all the fantastic objects live and also the root 17 | # context of evaluation. 18 | Lobby = object.clone 19 | 20 | Lobby["Lobby"] = Lobby 21 | Lobby["Object"] = object 22 | Lobby["nil"] = object.clone(nil) 23 | Lobby["true"] = object.clone(true) 24 | Lobby["false"] = object.clone(false) 25 | Lobby["Number"] = object.clone(0) 26 | Lobby["String"] = object.clone("") 27 | Lobby["List"] = object.clone([]) 28 | Lobby["Message"] = object.clone 29 | Lobby["Method"] = object.clone 30 | 31 | # The method we'll use to define methods. 32 | Lobby.def "method" do |receiver, context, message| 33 | Method.new(context, message) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /Book/book/code/mio/method.rb: -------------------------------------------------------------------------------- 1 | module Mio 2 | class Method < Object 3 | def initialize(context, message) 4 | @definition_context = context 5 | @message = message 6 | super(Lobby["Method"]) 7 | end 8 | 9 | def call(receiver, calling_context, *args) 10 | # Woo... lots of contexts here... lets clear that up: 11 | # @definition_context: where the method was defined 12 | # calling_context: where the method was called 13 | # method_context: where the method body (message) is executing 14 | method_context = @definition_context.clone 15 | method_context["self"] = receiver 16 | method_context["arguments"] = Lobby["List"].clone(args) 17 | # Note: no argument is evaluated here. Our little language only has lazy argument 18 | # evaluation. If you pass args to a method, you have to eval them explicitly, 19 | # using the following method. 20 | method_context["eval_arg"] = proc do |receiver, context, at| 21 | (args[at.call(context).value] || Lobby["nil"]).call(calling_context) 22 | end 23 | @message.call(method_context) 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/ClassDefinitionNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import java.util.List; 4 | 5 | import yourlang.lang.*; 6 | 7 | public class ClassDefinitionNode extends Node { 8 | private String name; 9 | private String superName; 10 | private Node body; 11 | 12 | public ClassDefinitionNode(String name, String superName, Node body) { 13 | this.name = name; 14 | this.superName = superName; 15 | this.body = body; 16 | } 17 | 18 | public YourLangObject eval(Context context) throws YourLangException { 19 | YourLangClass klass; 20 | // Default superclass to Object. 21 | if (superName == null) { 22 | klass = new YourLangClass(name); 23 | } else { 24 | YourLangClass superClass = (YourLangClass) context.getCurrentClass().getConstant(superName); 25 | klass = new YourLangClass(name, superClass); 26 | } 27 | 28 | // Evaluated the body of the class with self=class and class=class. 29 | body.eval(new Context(klass, klass)); 30 | // Add the class as a constant 31 | context.getCurrentClass().setConstant(name, klass); 32 | 33 | return klass; 34 | } 35 | } -------------------------------------------------------------------------------- /Book/CHANGELOG: -------------------------------------------------------------------------------- 1 | Revision 5 2 | * Improve all chapters based on Jeremy Ashkenas' review. 3 | * Pull most code comments into book's copy. 4 | * Expand comments and explanations around code. 5 | * Complete review of the VM and Compilation chapters. 6 | 7 | Revision 4 8 | * VM now works with the same runtime as the interpreter. 9 | * Include some details about Static Typing. 10 | 11 | Revision 3 12 | * Update LLVM code to use latest version of ruby-llvm (3.0.0) 13 | 14 | Revision 2 15 | * Updated LLVM code to use jvoorhis' ruby-llvm. 16 | * Update code to use Unit::Test for testing. 17 | * Add Lex, Rexical and Ragel grammar examples. 18 | * Add Yacc grammar rule example. 19 | * Add operator precedence to grammar.y and explanation in Parsing chapter. 20 | * Restructure runtime classes and expand chapter. 21 | * Move code evaluation to interpreter.rb. 22 | * New appendix: Mio, a minimalist homoiconic language, including a new language. 23 | 24 | Revision 1 25 | * Fixed setup.rb script in JVM language. 26 | * Added section on Operator Precedence in Parser chapter. 27 | * Added chapter on Compilation using LLVM with code example. 28 | * Added chapter on Virtual Machine with code example. -------------------------------------------------------------------------------- /Book/book/code/test/bytecode_compiler_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "bytecode_compiler" 3 | require "vm" 4 | 5 | class BytecodeCompilerTest < Test::Unit::TestCase 6 | def test_compile 7 | bytecode = BytecodeCompiler.new.compile("print(1+2)") 8 | 9 | expected_bytecode = [ 10 | # Generated bytecode 11 | # 12 | # opcode operands stack after description 13 | # ------------------------------------------------------------------------------ 14 | PUSH_SELF, # stack = [self] push the receiver of "print" 15 | PUSH_NUMBER, 1, # stack = [1] 16 | PUSH_NUMBER, 2, # stack = [self, 1, 2] push the argument for "+" 17 | CALL, "+", 1, # stack = [self, 3] call 1.+(2) and push the result 18 | CALL, "print", 1, # stack = [] call self.print(3) 19 | RETURN 20 | ] 21 | 22 | # Make sure the compiler generates the previous bytecode. 23 | assert_equal expected_bytecode, bytecode 24 | 25 | # Make sure the VM can run that bytecode. 26 | assert_prints("3\n") { VM.new.run(bytecode) } 27 | end 28 | end -------------------------------------------------------------------------------- /Book/book/code/mio/object.rb: -------------------------------------------------------------------------------- 1 | module Mio 2 | class Object 3 | attr_accessor :slots, :proto, :value 4 | 5 | def initialize(proto=nil, value=nil) 6 | @proto = proto # Prototype: parent object. Like JavaScript's __proto__. 7 | @value = value # The Ruby equivalent value. 8 | @slots = {} # Slots are where we store methods and attributes of an object. 9 | end 10 | 11 | # Lookup a slot in the current object and proto. 12 | def [](name) 13 | return @slots[name] if @slots.key?(name) 14 | return @proto[name] if @proto # Check if parent prototypes 15 | raise Mio::Error, "Missing slot: #{name.inspect}" 16 | end 17 | 18 | # Store a value in a slot 19 | def []=(name, value) 20 | @slots[name] = value 21 | end 22 | 23 | # Store a method into a slot 24 | def def(name, &block) 25 | @slots[name] = block 26 | end 27 | 28 | # The call method is used to eval an object. 29 | # By default objects eval to themselves. 30 | def call(*args) 31 | self 32 | end 33 | 34 | # The only way to create a new object in Mio is to clone an existing one. 35 | def clone(ruby_value=nil) 36 | Object.new(self, ruby_value) 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/YourLangRuntime.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | /** 4 | Language runtime. Mostly helper methods for retrieving global values. 5 | */ 6 | public class YourLangRuntime { 7 | static YourLangClass objectClass; 8 | static YourLangObject mainObject; 9 | static YourLangObject nilObject; 10 | static YourLangObject trueObject; 11 | static YourLangObject falseObject; 12 | 13 | public static YourLangClass getObjectClass() { 14 | return objectClass; 15 | } 16 | 17 | public static YourLangObject getMainObject() { 18 | return mainObject; 19 | } 20 | 21 | public static YourLangClass getRootClass(String name) { 22 | // objectClass is null when boostrapping 23 | return objectClass == null ? null : (YourLangClass) objectClass.getConstant(name); 24 | } 25 | 26 | public static YourLangClass getExceptionClass() { 27 | return getRootClass("Exception"); 28 | } 29 | 30 | public static YourLangObject getNil() { 31 | return nilObject; 32 | } 33 | 34 | public static YourLangObject getTrue() { 35 | return trueObject; 36 | } 37 | 38 | public static YourLangObject getFalse() { 39 | return falseObject; 40 | } 41 | 42 | public static YourLangObject toBoolean(boolean value) { 43 | return value ? YourLangRuntime.getTrue() : YourLangRuntime.getFalse(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/InterpretedMethod.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | import yourlang.lang.nodes.Node; 4 | 5 | /** 6 | Method defined inside a script. 7 | */ 8 | public class InterpretedMethod extends Method { 9 | private String name; 10 | private Evaluable body; 11 | private String parameters[]; 12 | 13 | /** 14 | Creates a new method. 15 | @param name Name of the method. 16 | @param parameters Name of the method parameters. 17 | @param body Object to eval when the method is called (usually a Node). 18 | */ 19 | public InterpretedMethod(String name, String parameters[], Evaluable body) { 20 | this.name = name; 21 | this.parameters = parameters; 22 | this.body = body; 23 | } 24 | 25 | /** 26 | Calls the method and evaluate the body. 27 | */ 28 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 29 | // Evaluates the method body in the contect of the receiver 30 | Context context = new Context(receiver); 31 | 32 | if (parameters.length != arguments.length) 33 | throw new ArgumentError(name, parameters.length, arguments.length); 34 | 35 | // Puts arguments in locals 36 | for (int i = 0; i < parameters.length; i++) { 37 | context.setLocal(parameters[i], arguments[i]); 38 | } 39 | 40 | return body.eval(context); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | import yourlang.lang.nodes.Node; 4 | 5 | /** 6 | Handle the catching of exception. 7 | */ 8 | public class ExceptionHandler { 9 | private Evaluable handler; 10 | private String localName; 11 | private YourLangClass klass; 12 | 13 | /** 14 | Creates an ExceptionHandler specialized in handling one type of Exception. 15 | @param klass Runtime class of the exception handled 16 | @param localName Name of the local variable in which the exception will 17 | be stored when catched. 18 | @param handler Code to eval when the exception is catched. 19 | */ 20 | public ExceptionHandler(YourLangClass klass, String localName, Evaluable handler) { 21 | this.localName = localName; 22 | this.handler = handler; 23 | this.klass = klass; 24 | } 25 | 26 | /** 27 | Returns true if this handler can take care of this exception. 28 | */ 29 | public boolean handle(YourLangException e) { 30 | return klass.isSubclass(e.getRuntimeClass()); 31 | } 32 | 33 | /** 34 | Called to run a catch block when an exception occured. 35 | */ 36 | public YourLangObject run(Context context, YourLangException e) throws YourLangException { 37 | if (localName != null) { 38 | context.setLocal(localName, e.getRuntimeObject()); 39 | } 40 | return handler.eval(context); 41 | } 42 | } -------------------------------------------------------------------------------- /Book/book/code/bracket_lexer.rb: -------------------------------------------------------------------------------- 1 | class BracketLexer 2 | KEYWORDS = ["def", "class", "if", "true", "false", "nil"] 3 | 4 | def tokenize(code) 5 | code.chomp! 6 | i = 0 7 | tokens = [] 8 | 9 | while i < code.size 10 | chunk = code[i..-1] 11 | 12 | if identifier = chunk[/\A([a-z]\w*)/, 1] 13 | if KEYWORDS.include?(identifier) 14 | tokens << [identifier.upcase.to_sym, identifier] 15 | else 16 | tokens << [:IDENTIFIER, identifier] 17 | end 18 | i += identifier.size 19 | 20 | elsif constant = chunk[/\A([A-Z]\w*)/, 1] 21 | tokens << [:CONSTANT, constant] 22 | i += constant.size 23 | 24 | elsif number = chunk[/\A([0-9]+)/, 1] 25 | tokens << [:NUMBER, number.to_i] 26 | i += number.size 27 | 28 | elsif string = chunk[/\A"(.*?)"/, 1] 29 | tokens << [:STRING, string] 30 | i += string.size + 2 31 | 32 | ###### 33 | # All indentation magic code was removed and only this elsif was added. 34 | elsif chunk.match(/\A\n+/) 35 | tokens << [:NEWLINE, "\n"] 36 | i += 1 37 | ###### 38 | 39 | elsif chunk.match(/\A /) 40 | i += 1 41 | 42 | else 43 | value = chunk[0,1] 44 | tokens << [value, value] 45 | i += 1 46 | 47 | end 48 | 49 | end 50 | 51 | tokens 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /src/Specification.md: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | This is the lexer for Awzum, the new name that I am giving to the language from the book, called Awesome. I intend to write my own code and then check it with the book so I am not just copying and pasting, but actually actively writing a language while being guided by the book. Awzum language is not going to be production ready because of the timeframe of its production and the amount of time I have to spend designing a production ready language, although I believe that if I had enough time, I would be able to create a production ready language, perhaps even with writing my own lexer and parser. I just have this feeling that I will eventually persevere. To create a production ready language, would need to follow the right design patterns and make optimizations, and all the stuff that comes when you make a real language. This is NOT a real production ready language and will just be me finding my way as I follow the book. So here is the lexer. 4 | 5 | Some notes on the parser: I would use my own parsing framework and not Racc a suggested by the book just for the sake of doing things differently, which is what usually do, but because my summer is contingent on me getting things right in a certain timeframe, I dont want to deviate from the path and perhaps run into unexpected problems due the books dependency on a certain lexer. This might change though as I get more comfortable writing my own language. For now, I am going to build using the book's lexer and Racc(which is what I think the book uses. I haven't gotten to it though). 6 | 7 | So wish me luck. -------------------------------------------------------------------------------- /Book/book/code/test/interpreter_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "interpreter" 3 | 4 | class InterpreterTest < Test::Unit::TestCase 5 | def test_number 6 | assert_equal 1, Interpreter.new.eval("1").ruby_value 7 | end 8 | 9 | def test_true 10 | assert_equal true, Interpreter.new.eval("true").ruby_value 11 | end 12 | 13 | def test_assign 14 | assert_equal 2, Interpreter.new.eval("a = 2; 3; a").ruby_value 15 | end 16 | 17 | def test_method 18 | code = <<-CODE 19 | def boo(a): 20 | a 21 | 22 | boo("yah!") 23 | CODE 24 | 25 | assert_equal "yah!", Interpreter.new.eval(code).ruby_value 26 | end 27 | 28 | def test_reopen_class 29 | code = <<-CODE 30 | class Number: 31 | def ten: 32 | 10 33 | 34 | 1.ten 35 | CODE 36 | 37 | assert_equal 10, Interpreter.new.eval(code).ruby_value 38 | end 39 | 40 | def test_define_class 41 | code = <<-CODE 42 | class Pony: 43 | def awesome: 44 | true 45 | 46 | Pony.new.awesome 47 | CODE 48 | 49 | assert_equal true, Interpreter.new.eval(code).ruby_value 50 | end 51 | 52 | def test_if 53 | code = <<-CODE 54 | if true: 55 | "works!" 56 | CODE 57 | 58 | assert_equal "works!", Interpreter.new.eval(code).ruby_value 59 | end 60 | 61 | def test_interpret 62 | code = <<-CODE 63 | class Awesome: 64 | def does_it_work: 65 | "yeah!" 66 | 67 | awesome_object = Awesome.new 68 | if awesome_object: 69 | print(awesome_object.does_it_work) 70 | CODE 71 | 72 | assert_prints("yeah!\n") { Interpreter.new.eval(code) } 73 | end 74 | end -------------------------------------------------------------------------------- /Book/jvm_lang/test/runner.rb: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | 3 | # Simple test runner that compares output of running a script with the comments beginning 4 | # with '=>' in the file. 5 | class TestRunner 6 | include FileUtils 7 | 8 | def initialize(bin, pattern, dir) 9 | @bin = bin 10 | @total = 0 11 | @failures = [] 12 | @pattern = pattern 13 | @dir = dir 14 | end 15 | 16 | def run 17 | cd @dir do 18 | Dir[@pattern].each do |file| 19 | expected = File.read(file).split("\n").map { |l| l[/^# => (.*)$/, 1] }.compact.join("\n") 20 | actual = test(file) 21 | if expected == actual 22 | pass(file) 23 | else 24 | fail(file, expected, actual) 25 | end 26 | end 27 | 28 | puts 29 | 30 | @failures.each do |file, expected, actual| 31 | puts "[%s]" % file 32 | puts " expected #{expected.inspect}" 33 | puts " got #{actual.inspect}" 34 | puts 35 | end 36 | 37 | puts "#{@total} tests, #{@failures.size} failures" 38 | end 39 | end 40 | 41 | def run_and_exit! 42 | run 43 | exit @failures.empty? ? 0 : 1 44 | end 45 | 46 | protected 47 | def test(*args) 48 | cmd = "#{@bin} #{args * ' '}" 49 | `#{cmd}`.chomp 50 | end 51 | 52 | def pass(file) 53 | @total += 1 54 | print "." 55 | end 56 | 57 | def fail(file, expected, actual) 58 | @total += 1 59 | @failures << [file, expected, actual] 60 | print "F" 61 | end 62 | end 63 | 64 | TestRunner.new("bin/yourlang", "test/*.yl", File.dirname(__FILE__) + "/..").run_and_exit! 65 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/ValueObject.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | /** 4 | Object storing a Java value, usualy a literal (String, Integer, Float, nil, 5 | true, false). 6 | */ 7 | public class ValueObject extends YourLangObject { 8 | private Object value; 9 | 10 | public ValueObject(YourLangClass klass, Object value) { 11 | super(klass); 12 | this.value = value; 13 | } 14 | 15 | public ValueObject(String value) { 16 | super("String"); 17 | this.value = value; 18 | } 19 | 20 | public ValueObject(Integer value) { 21 | super("Integer"); 22 | this.value = value; 23 | } 24 | 25 | public ValueObject(Float value) { 26 | super("Float"); 27 | this.value = value; 28 | } 29 | 30 | public ValueObject(Object value) { 31 | super("Object"); 32 | this.value = value; 33 | } 34 | 35 | /** 36 | Returns the Java value of this object. 37 | */ 38 | @Override 39 | public Object toJavaObject() { 40 | return value; 41 | } 42 | 43 | /** 44 | Only nil and false are false. 45 | */ 46 | @Override 47 | public boolean isFalse() { 48 | return value == (Object)false || isNil(); 49 | } 50 | 51 | /** 52 | Only nil is nil. 53 | */ 54 | @Override 55 | public boolean isNil() { 56 | return value == null; 57 | } 58 | 59 | public Object getValue() { 60 | return value; 61 | } 62 | 63 | /** 64 | Cast the value to clazz or throw a TypeError if unexpected type. 65 | */ 66 | public T getValueAs(Class clazz) throws TypeError { 67 | if (clazz.isInstance(value)){ 68 | return clazz.cast(value); 69 | } 70 | throw new TypeError(clazz.getName(), value); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Book/README: -------------------------------------------------------------------------------- 1 | How to Create Your Own Programming Language 2 | =========================================== 3 | 4 | Creating a programming language from scratch is hard. It takes several attempts before 5 | finally coming up with a fully working language. This system will teach you all the 6 | tricks you need to know to make your language come to life. This is a great skill to 7 | put on your resume and it's also fun to learn. 8 | 9 | The system will walk you through each step of language-building. Each section of the 10 | book will introduce a new concept and then apply its principles to a language that 11 | we’ll build together. All technical chapters end with a Do It Yourself section that 12 | suggest some language-extending exercises. You’ll find solutions to those at the end of 13 | this book. 14 | 15 | Our language will be dynamic and very similar to Ruby and Python. All of the code will 16 | be in Ruby, but I’ve put lots of attention to keep the code as simple as possible so 17 | that you can understand what’s happening even if you don’t know Ruby. The focus of this 18 | book is not on how to build a production-ready language. Instead, it should serve as an 19 | introduction in building your first toy language. 20 | 21 | Content of this package 22 | ----------------------- 23 | 24 | - book/Create Your Own Programming Language.* is the ebook in multiple formats. 25 | - book/code contains the code shown in the book and the two Ruby languages. 26 | - jvm_lang contains the JVM language. 27 | - screencast.mp4 is the screencast explaining how to extend the JVM language. 28 | 29 | 30 | Help 31 | ---- 32 | Contact me at macournoyer@gmail.com for suggestions, comments or help. 33 | 34 | (c) Marc-Andre Cournoyer, http://createyourproglang.com -------------------------------------------------------------------------------- /Book/jvm_lang/README: -------------------------------------------------------------------------------- 1 | Create Your Own Language Templates 2 | ================================== 3 | 4 | This project serves as a base for creating a programming language on top of 5 | the JVM. 6 | 7 | 8 | Requirements 9 | ============ 10 | You must have the following tools installed: 11 | * Java 1.5 or greater (http://www.java.com/en/download/index.jsp) 12 | * Ant 1.7 or greater (http://ant.apache.org/) 13 | * Ruby 1.8 or greater (http://www.ruby-lang.org) 14 | 15 | 16 | Getting Started 17 | =============== 18 | To setup the project (do this only once), run: 19 | 20 | ruby setup.rb 21 | 22 | To compile the application, from the root of the project, run: 23 | 24 | ant 25 | 26 | You can then execute code: 27 | 28 | bin/yourlang -e "some code" 29 | bin/yourlang some_file.yl 30 | 31 | 32 | Structure of the language 33 | ========================= 34 | The runtime model of the language is largely inspired by Ruby. See comments in 35 | source files for more details. 36 | 37 | * Everything is on object. 38 | * Each object (YourLangObject) has a class (YourLangClass). 39 | * Objects that relate to Java values (String, Integer, Float, etc) are stored 40 | in a ValueObject instance. 41 | * The lexer and parser grammars (.g files) are compiled by ANTLR 42 | (http://www.antlr.org/) 43 | * The parser creates custom nodes (under src/yourlang/lang/nodes) each one 44 | implementing the eval method. 45 | * Each node is evaled on an instance of the Context class. 46 | * Methods of YourLang objects are created in Boostrapper.java. 47 | 48 | 49 | Language Syntax 50 | =============== 51 | The syntax is, again, inspired by Ruby. See the files under test/ for example 52 | use of the language. 53 | 54 | 55 | (c) 2009 Marc-Andre Cournoyer 56 | See LICENSE for legal information -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/YourLangException.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | /** 4 | Exception that can be catched from inside the runtime. 5 | */ 6 | public class YourLangException extends Exception { 7 | private YourLangClass runtimeClass; 8 | 9 | /** 10 | Creates a new exception from a runtime class. 11 | @param runtimeClass Class of the exception from whitin the language. 12 | */ 13 | public YourLangException(YourLangClass runtimeClass, String message) { 14 | super(message); 15 | this.runtimeClass = runtimeClass; 16 | } 17 | 18 | public YourLangException(YourLangClass runtimeClass) { 19 | super(); 20 | this.runtimeClass = runtimeClass; 21 | } 22 | 23 | public YourLangException(String runtimeClassName, String message) { 24 | super(message); 25 | setRuntimeClass(runtimeClassName); 26 | } 27 | 28 | /** 29 | Creates a new exception from the Exception runtime class. 30 | */ 31 | public YourLangException(String message) { 32 | super(message); 33 | this.runtimeClass = YourLangRuntime.getExceptionClass(); 34 | } 35 | 36 | /** 37 | Wrap an exception to pass it to the runtime. 38 | */ 39 | public YourLangException(Exception inner) { 40 | super(inner); 41 | setRuntimeClass(inner.getClass().getName()); 42 | } 43 | 44 | /** 45 | Returns the runtime instance (the exception inside the language) of this exception. 46 | */ 47 | public YourLangObject getRuntimeObject() { 48 | YourLangObject instance = runtimeClass.newInstance(this); 49 | instance.setInstanceVariable("message", new ValueObject(getMessage())); 50 | return instance; 51 | } 52 | 53 | public YourLangClass getRuntimeClass() { 54 | return runtimeClass; 55 | } 56 | 57 | protected void setRuntimeClass(String name) { 58 | runtimeClass = YourLangRuntime.getExceptionClass().subclass(name); 59 | } 60 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/TryNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import java.util.ArrayList; 4 | 5 | import yourlang.lang.*; 6 | 7 | /** 8 | A try-catch block. 9 | */ 10 | public class TryNode extends Node { 11 | private Node body; 12 | private ArrayList catchBlocks; 13 | 14 | public TryNode(Node body) { 15 | this.body = body; 16 | catchBlocks = new ArrayList(); 17 | } 18 | 19 | /** 20 | Add a block to catch exception of type typeName. Storing the exception in 21 | localName and evaluating body. 22 | */ 23 | public void addCatchBlock(String typeName, String localName, Node body) { 24 | catchBlocks.add(new CatchBlock(typeName, localName, body)); 25 | } 26 | 27 | public YourLangObject eval(Context context) throws YourLangException { 28 | Context tryContext = context.makeChildContext(); 29 | 30 | try { 31 | return body.eval(tryContext); 32 | } catch (YourLangException exception) { 33 | // If there's an exception we run through all exception handler and run 34 | // the first one that can handle the exception. 35 | for (CatchBlock block : catchBlocks) { 36 | ExceptionHandler handler = block.toExceptionHandler(); 37 | if (handler.handle(exception)) return handler.run(tryContext, exception); 38 | } 39 | // No catch block for this exception, rethrow. Can be catched from a parent 40 | // context. 41 | throw exception; 42 | } 43 | } 44 | 45 | /** 46 | One catch block. 47 | */ 48 | private class CatchBlock { 49 | private String typeName; 50 | private String localName; 51 | private Node body; 52 | 53 | public CatchBlock(String typeName, String localName, Node body) { 54 | this.typeName = typeName; 55 | this.localName = localName; 56 | this.body = body; 57 | } 58 | 59 | public ExceptionHandler toExceptionHandler() { 60 | return new ExceptionHandler(YourLangRuntime.getRootClass(typeName), localName, body); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/nodes/CallNode.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang.nodes; 2 | 3 | import yourlang.lang.*; 4 | import java.util.List; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | 8 | /** 9 | Method call. 10 | */ 11 | public class CallNode extends Node { 12 | private Node receiver; 13 | private String method; 14 | private List arguments; 15 | 16 | /** 17 | Call method on receiver with arguments: receiver.method(arguments) 18 | */ 19 | public CallNode(String method, Node receiver, List arguments) { 20 | this.method = method; 21 | this.receiver = receiver; 22 | this.arguments = arguments; 23 | } 24 | 25 | public CallNode(String method, Node receiver, Node argument) { 26 | this(method, receiver, Arrays.asList(new Node[] { argument })); 27 | } 28 | 29 | public CallNode(String method, List arguments) { 30 | this(method, null, arguments); 31 | } 32 | 33 | public CallNode(String method) { 34 | this(method, null); 35 | } 36 | 37 | public void setReceiver(Node receiver) { 38 | this.receiver = receiver; 39 | } 40 | 41 | /** 42 | Make the method call. 43 | */ 44 | public YourLangObject eval(Context context) throws YourLangException { 45 | // If no receiver and not arguments were specied and a local variable or the same 46 | // name exists, then it's a local variable access. 47 | if (receiver == null && arguments == null && context.hasLocal(method)) { 48 | return context.getLocal(method); 49 | } 50 | 51 | // Default receiver to self. 52 | YourLangObject evaledReceiver; 53 | if (receiver == null) { 54 | evaledReceiver = context.getCurrentSelf(); 55 | } else { 56 | evaledReceiver = receiver.eval(context); 57 | } 58 | 59 | // Evaluated each argument in the calling context. 60 | ArrayList evaledArguments = new ArrayList(); 61 | if (arguments != null) { 62 | for (Node arg : arguments) evaledArguments.add(arg.eval(context)); 63 | } 64 | 65 | // Call the method. 66 | return evaledReceiver.call(method, (YourLangObject[])evaledArguments.toArray(new YourLangObject[0])); 67 | } 68 | } -------------------------------------------------------------------------------- /Book/jvm_lang/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Book/book/code/runtime/bootstrap.rb: -------------------------------------------------------------------------------- 1 | # First, we create a Ruby Hash in which we'll store all constants accessible from inside 2 | # our runtime. 3 | # Then, we populate this Hash with the core classes of our language. 4 | Constants = {} 5 | 6 | Constants["Class"] = AwesomeClass.new # Defining the `Class` class. 7 | Constants["Class"].runtime_class = Constants["Class"] # Setting `Class.class = Class`. 8 | Constants["Object"] = AwesomeClass.new # Defining the `Object` class 9 | Constants["Number"] = AwesomeClass.new # Defining the `Number` class 10 | Constants["String"] = AwesomeClass.new 11 | 12 | # The root context will be the starting point where all our programs will 13 | # start their evaluation. This will also set the value of `self` at the root 14 | # of our programs. 15 | root_self = Constants["Object"].new 16 | RootContext = Context.new(root_self) 17 | 18 | # Everything is an object in our language, even `true`, `false` and `nil`. So they need 19 | # to have a class too. 20 | Constants["TrueClass"] = AwesomeClass.new 21 | Constants["FalseClass"] = AwesomeClass.new 22 | Constants["NilClass"] = AwesomeClass.new 23 | 24 | Constants["true"] = Constants["TrueClass"].new_with_value(true) 25 | Constants["false"] = Constants["FalseClass"].new_with_value(false) 26 | Constants["nil"] = Constants["NilClass"].new_with_value(nil) 27 | 28 | # Now that we have injected all the core classes into the runtime, we can define 29 | # methods on those classes. 30 | # 31 | # The first method we'll define will allow us to do `Object.new` or 32 | # `Number.new`. Keep in mind, `Object` or `Number` 33 | # are instances of the `Class` class. By defining the `new` method 34 | # on `Class`, it will be accessible on all its instances. 35 | Constants["Class"].def :new do |receiver, arguments| 36 | receiver.new 37 | end 38 | 39 | # Next, we'll define the `print` method. Since we want to be able to call it 40 | # from everywhere, we'll define it on `Object`. 41 | # Remember from the parser's `Call` rule, methods without any receiver will be 42 | # sent to `self`. So `print()` is the same as `self.print()`, and 43 | # `self` will always be an instance of `Object`. 44 | Constants["Object"].def :print do |receiver, arguments| 45 | puts arguments.first.ruby_value 46 | Constants["nil"] # We always want to return objects from our runtime 47 | end 48 | -------------------------------------------------------------------------------- /src/extend_core.rb: -------------------------------------------------------------------------------- 1 | #Adding functionality to some of the core classes, mostly, as you can see, the String class. 2 | 3 | class String 4 | 5 | NUMBER_PATTERNS = [ 6 | /^[1-9]([0-9]*)?\.[0-9]+/, 7 | /^[1-9]([0-9]*)?/, # decimal 8 | /^0[0-7]+/, # octal 9 | /^0x[0-9A-Fa-f]+/, # hexadecimal 10 | /^0b[01]+/ 11 | ] 12 | 13 | def match_any? array #Does self match any of the regexpressions in the array 14 | return true if shift_match_any array 15 | false 16 | end 17 | 18 | def match_first_in array 19 | begin 20 | array.each do |i| 21 | regexp = if i.class == Regexp 22 | i 23 | elsif i.class == String 24 | Regexp::new i 25 | else 26 | raise "Type mismatch. Required #{String} or #{Regexp}, but got #{i.class}." 27 | end 28 | ind = self =~ regexp 29 | if (match = self[i]) && (ind == 0) 30 | return match 31 | end 32 | 33 | end 34 | rescue => err 35 | puts err 36 | end 37 | end 38 | 39 | def match_first string 40 | if string.class == Regexp 41 | return self.scan(string)[0] 42 | else 43 | return self.scan(Regexp::new(string))[0] 44 | end 45 | 46 | end 47 | def integer? 48 | [ # In descending order of likeliness: 49 | /^[-+]?[1-9]([0-9]*)?$/, # decimal 50 | /^0[0-7]+$/, # octal 51 | /^0x[0-9A-Fa-f]+$/, # hexadecimal 52 | /^0b[01]+$/ # binary 53 | ].each do |match_pattern| 54 | return true if self =~ match_pattern 55 | end 56 | return false 57 | end 58 | 59 | def float? 60 | pat = /^[-+]?[1-9]([0-9]*)?\.[0-9]+$/ 61 | return true if self=~pat 62 | false 63 | end 64 | 65 | #Is self a false or an int. Should later add support for all types of numbers 66 | def number? 67 | return true if self.float? 68 | return true if self.integer? 69 | false 70 | end 71 | 72 | #The numberified version of self. If I'm a float, return me as a float. If I'm a 73 | def to_n 74 | return self.to_f if self.float? 75 | 76 | return self.to_i if self.integer? 77 | end 78 | 79 | #Returns the first number to be found in self. 80 | def shift_number 81 | NUMBER_PATTERNS.each do |match_pattern| 82 | if match = self[match_pattern] #Interesting way of doing if statements. I learned it from the programming language book and am hooked. 83 | return match 84 | end 85 | end 86 | nil 87 | end 88 | 89 | 90 | end 91 | -------------------------------------------------------------------------------- /Book/book/code/vm.rb: -------------------------------------------------------------------------------- 1 | require "bytecode" 2 | require "runtime" 3 | 4 | class VM 5 | def run(bytecode) 6 | # First, we create the stack to pass values between instructions. 7 | # And initialize the Instruction Pointer (`ip`), the index of current instruction 8 | # being executed in `bytecode`. 9 | stack = [] 10 | ip = 0 11 | 12 | # Next, we enter into the VM loop. Inside, we will advance one byte at the time 13 | # in the `bytecode`. The first byte will be an opcode. 14 | while true 15 | case bytecode[ip] # Inspect the current byte, this will be the opcode. 16 | 17 | # Each of the following `when` block handles one type of instruction. 18 | # They are all structured in the same way. 19 | # 20 | # The first instruction will push a number on the stack. 21 | when PUSH_NUMBER 22 | ip += 1 # Advance to next byte, the operand. 23 | value = bytecode[ip] # Read the operand. 24 | 25 | stack.push Constants["Number"].new_with_value(value) 26 | 27 | # Since calling methods on `self` is something we do often we have a special 28 | # instruction for pushing the value of `self` on the stack. 29 | when PUSH_SELF 30 | stack.push RootContext.current_self 31 | 32 | # The most complex instruction of our VM is `CALL`, for calling a method. 33 | # It has two operands and expects several things to be on the stack. 34 | when CALL 35 | ip += 1 # Next byte contains the method name to call. 36 | method = bytecode[ip] 37 | 38 | ip += 1 # Next byte, the number of arguments on the stack. 39 | argc = bytecode[ip] 40 | 41 | # At this point we assume arguments and the receiver of the method call 42 | # have been pushed to the stack by other instructions. For example, if 43 | # we were to call a method on `self` passing a number as an argument, we 44 | # would find those two on the stack. Now pop all of those. 45 | args = [] 46 | argc.times do 47 | args << stack.pop 48 | end 49 | receiver = stack.pop 50 | 51 | # Using those values, we make the call exactly like we did in the interpreter 52 | # (`CallNode`'s `eval`). 53 | stack.push receiver.call(method, args) 54 | 55 | # Here is how we exit the VM loop. Each program must end with this instruction. 56 | when RETURN 57 | return stack.pop 58 | 59 | end 60 | 61 | # Finally, we move forward one more byte to the next operand, to prepare for the 62 | # next turn in the loop. 63 | ip += 1 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /Book/book/code/bytecode_compiler.rb: -------------------------------------------------------------------------------- 1 | require "parser" 2 | 3 | # The following code is structured almost exactly like `interpreter.rb`. 4 | # The difference being that we won't evaluate the code on the spot, 5 | # but generate byte-code that will achieve the same results when run 6 | # inside the virtual machine (which is in fact a byte-code interpreter). 7 | # 8 | # `BytecodeCompiler` here is the same as `Interpreter`, a simple wrapper 9 | # around the parser and the nodes `compile` method, with the addition of 10 | # an `emit` method to help generate the bytecode. 11 | class BytecodeCompiler 12 | def initialize 13 | @parser = Parser.new 14 | @bytecode = [] 15 | end 16 | 17 | def compile(code) 18 | @parser.parse(code).compile(self) 19 | emit RETURN 20 | @bytecode 21 | end 22 | 23 | def emit(opcode, *operands) # Usage: emit OPCODE, operand1, operand2, ..., operandX 24 | @bytecode << opcode 25 | @bytecode.concat operands 26 | end 27 | end 28 | 29 | # Like in the interpreter, we reopen each node class supported by our 30 | # compiler and add a `compile` method. Instead of passing an evaluation context, 31 | # like in the interpreter, we pass the instance of `BytecodeCompiler` so that 32 | # we can call its `emit` method to generate the byte-code. 33 | class Nodes 34 | def compile(compiler) 35 | nodes.each do |node| 36 | node.compile(compiler) 37 | end 38 | end 39 | end 40 | 41 | class NumberNode 42 | def compile(compiler) 43 | compiler.emit PUSH_NUMBER, value 44 | end 45 | end 46 | 47 | # Remember how we implemented the `CALL` instruction in the VM? It expects 48 | # two things on the stack when called: the receiver and the arguments. 49 | # Compiling those will emit the proper bytecode, which will in turn push the 50 | # proper values on the stack. 51 | # 52 | # One important thing to note about our compiler. Although it is very close to 53 | # how a real compiler work, some parts have been simplified. 54 | # Normally, we would not store the method name in the byte-code as is, but instead in a 55 | # literal table. Then we'd refer to that method using its index in the literal table. 56 | # 57 | # Eg.: 58 | # Literal table: `{ [0] 'print' }` 59 | # Instructions: `CALL 0, 1` (first operand being the index of 'print') 60 | # 61 | class CallNode 62 | def compile(compiler) 63 | if receiver 64 | receiver.compile(compiler) 65 | else 66 | compiler.emit PUSH_SELF # Default to self if no receiver 67 | end 68 | 69 | arguments.each do |argument| # Compile the arguments 70 | argument.compile(compiler) 71 | end 72 | 73 | compiler.emit CALL, method, arguments.size 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/YourLangLexer.g: -------------------------------------------------------------------------------- 1 | lexer grammar YourLangLexer; 2 | 3 | // Stuff added on top of the Lexer class. 4 | @header { package yourlang.lang; } 5 | 6 | // Methods added to the Lexer class. 7 | @members { 8 | boolean methodMode = false; // true if we're waiting for a method name 9 | 10 | public Token nextToken() { 11 | Token t = super.nextToken(); 12 | // DEBUG Uncomment to output tokens 13 | // System.out.println("TOKEN> " + t); 14 | return t; 15 | } 16 | 17 | public boolean isNum(int c) { 18 | return c>='0' && c<='9'; 19 | } 20 | 21 | @Override 22 | public void reportError(RecognitionException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | 27 | // Keywords 28 | // {...}?=> to allow keyword as method name 29 | CLASS: {!methodMode}?=> 'class'; 30 | DEF: {!methodMode}?=> 'def'; 31 | IF: {!methodMode}?=> 'if'; 32 | ELSE: {!methodMode}?=> 'else'; 33 | WHILE: {!methodMode}?=> 'while'; 34 | TRY: {!methodMode}?=> 'try'; 35 | CATCH: {!methodMode}?=> 'catch'; 36 | END: {!methodMode}?=> 'end'; 37 | SELF: {!methodMode}?=> 'self'; 38 | NIL: {!methodMode}?=> 'nil'; 39 | TRUE: {!methodMode}?=> 'true'; 40 | FALSE: {!methodMode}?=> 'false'; 41 | 42 | 43 | // Literals 44 | fragment INTEGER:; 45 | fragment FLOAT:; 46 | NUMBER: '-'? DIGIT+ 47 | // Fix ambiguity with dot for message sending (DOT NAME). 48 | ( {isNum(input.LA(2))}?=> '.' DIGIT+ { $type = FLOAT; } 49 | | { $type = INTEGER; } 50 | ); 51 | STRING: '"' ~('\\' | '"')* '"'; 52 | NAME: (LOWER | '_') ID_CHAR* ('!' | '?')? { methodMode = false; }; 53 | CONSTANT: UPPER ID_CHAR*; 54 | 55 | // Operators 56 | SEMICOLON: ';'; 57 | COLON: ':'; 58 | DOT: '.' { methodMode = true; }; 59 | COMMA: ','; 60 | OPEN_PARENT: '('; 61 | CLOSE_PARENT: ')'; 62 | AT: '@'; 63 | EQ: '=='; 64 | LE: '<='; 65 | GE: '>='; 66 | LT: '<'; 67 | GT: '>'; 68 | PLUS: '+'; 69 | MINUS: '-'; 70 | MUL: '*'; 71 | DIV: '/'; 72 | MOD: '%'; 73 | AND: '&&'; 74 | OR: '||'; 75 | NOT: '!'; 76 | ASSIGN: '='; 77 | 78 | COMMENT: '#' ~('\r' | '\n')* (NEWLINE | EOF) { skip(); }; 79 | 80 | NEWLINE: '\r'? '\n'; 81 | WHITESPACE: SPACE+ { $channel = HIDDEN; }; // ignore whitespace 82 | 83 | fragment LETTER: LOWER | UPPER; 84 | fragment ID_CHAR: LETTER | DIGIT | '_'; 85 | fragment LOWER: 'a'..'z'; 86 | fragment UPPER: 'A'..'Z'; 87 | fragment DIGIT: '0'..'9'; 88 | fragment SPACE: ' ' | '\t'; 89 | 90 | -------------------------------------------------------------------------------- /tests/test_lexer.rb: -------------------------------------------------------------------------------- 1 | require "./Unit_Test_Helper.rb" 2 | require "../src/lexer.rb" 3 | 4 | class LexerTest < Test::Unit::TestCase 5 | def test_number 6 | assert_equal [[:NUMBER, 1]], Lexer.new.tokenize("1") 7 | end 8 | 9 | def test_string 10 | assert_equal [[:STRING, "hi"]], Lexer.new.tokenize('"hi"') 11 | end 12 | 13 | def test_identifier 14 | assert_equal [[:IDENTIFIER, "name"]], Lexer.new.tokenize('name') 15 | end 16 | 17 | def test_constant 18 | assert_equal [[:CONSTANT, "Name"]], Lexer.new.tokenize('Name') 19 | end 20 | 21 | def test_operator 22 | assert_equal [["+", "+"]], Lexer.new.tokenize('+') 23 | assert_equal [["||", "||"]], Lexer.new.tokenize('||') 24 | end 25 | 26 | def test_indent 27 | code = <<-CODE 28 | if 1: 29 | if 2: 30 | print("...") 31 | if false: 32 | pass 33 | print("done!") 34 | 2 35 | 36 | print "The End" 37 | CODE 38 | tokens = [ 39 | [:IF, "if"], [:NUMBER, 1], # if 1: 40 | [:INDENT, 2], 41 | [:IF, "if"], [:NUMBER, 2], # if 2: 42 | [:INDENT, 4], 43 | [:IDENTIFIER, "print"], ["(", "("], # print("...") 44 | [:STRING, "..."], 45 | [")", ")"], 46 | [:NEWLINE, "\n"], 47 | [:IF, "if"], [:FALSE, "false"], # if false: 48 | [:INDENT, 6], 49 | [:IDENTIFIER, "pass"], # pass 50 | [:DEDENT, 4], [:NEWLINE, "\n"], 51 | [:IDENTIFIER, "print"], ["(", "("], # print("done!") 52 | [:STRING, "done!"], 53 | [")", ")"], 54 | [:DEDENT, 2], [:NEWLINE, "\n"], 55 | [:NUMBER, 2], # 2 56 | [:DEDENT, 0], [:NEWLINE, "\n"], 57 | [:NEWLINE, "\n"], # 58 | [:IDENTIFIER, "print"], [:STRING, "The End"] # print "The End" 59 | ] 60 | assert_equal tokens, Lexer.new.tokenize(code) 61 | end 62 | 63 | def test_braket_lexer 64 | require '../src/bracket_lexer.rb' 65 | code = <<-CODE 66 | if 1 { 67 | print "..." 68 | if false { 69 | pass 70 | } 71 | print "done!" 72 | } 73 | print "The End" 74 | CODE 75 | 76 | tokens = [ 77 | [:IF, "if"], [:NUMBER, 1], 78 | [:START_BRACKET, "{"], [:NEWLINE, "\n"], 79 | [:IDENTIFIER, "print"], [:STRING, "..."], [:NEWLINE, "\n"], 80 | [:IF, "if"], [:FALSE, "false"], [:START_BRACKET, "{"], [:NEWLINE, "\n"], 81 | [:IDENTIFIER, "pass"], [:NEWLINE, "\n"], 82 | [:END_BRACKET, "}"], [:NEWLINE, "\n"], 83 | [:IDENTIFIER, "print"], [:STRING, "done!"], [:NEWLINE, "\n"], 84 | [:END_BRACKET, "}"], [:NEWLINE, "\n"], 85 | [:IDENTIFIER, "print"], [:STRING, "The End"] 86 | ] 87 | assert_equal tokens, BracketLexer.new.tokenize(code) 88 | end 89 | end -------------------------------------------------------------------------------- /src/bracket_lexer.rb: -------------------------------------------------------------------------------- 1 | 2 | class Token 3 | attr_accessor :token, :value 4 | def initialize t, value 5 | @token, @value = t, v 6 | end 7 | end 8 | class BracketLexer 9 | KEYWORDS = ["true","false","if","elsif","else","while","case","when","do","class","def","nil"] 10 | def tokenize(code) 11 | code.chomp! 12 | tokens = [] # This is where generated tokens go 13 | current_indent = 0 14 | indent_stack = [] 15 | 16 | bracket_start = 0 17 | bracket_end = 0 18 | 19 | index = 0 # Current reading index 20 | while index < code.size 21 | codon = code[index..-1] # A codon of DNA, where DNA is the code and a codon is a chunk of it 22 | =begin 23 | Note: I originally wrote my own code to do this where each matched regex corresponds to a lambda which makes 24 | the code more modular, but in the end I realized it just overcomplicated things 25 | and so removed it. Then, I did a few things to make the code look nicer but they ended up also 26 | causing more complications so I left the code as it was in the book. 27 | From here and onwards, my strategy is to follow the book as closely as I can, learn from what the book does, 28 | and then implement a language COMPLETELY on my own using what I learned from following the book. 29 | Before I lead myself, I must follow someone else. 30 | =end 31 | if identifier = codon[/\A([a-z]\w*)/, 1] 32 | if KEYWORDS.include?(identifier) 33 | tokens << [identifier.upcase.to_sym, identifier] 34 | else 35 | tokens << [:IDENTIFIER, identifier] 36 | end 37 | index += identifier.size 38 | 39 | 40 | elsif constant = codon[/\A([A-Z]\w*)/, 1] 41 | tokens << [:CONSTANT, constant] 42 | index += constant.size 43 | 44 | elsif number = codon[/\A([0-9]+)/, 1] 45 | tokens << [:NUMBER, number.to_i] 46 | index += number.size 47 | 48 | elsif string = codon[/\A"([^"]*)"/, 1] 49 | tokens << [:STRING, string] 50 | index += string.size + 2 51 | elsif newline = codon[/\A(\n)/,1] 52 | tokens << [:NEWLINE, "\n"] 53 | index += 1 54 | elsif forward_brak = codon[/\A(\{)/,1] 55 | bracket_start+=1 56 | tokens << [:START_BRACKET, forward_brak] 57 | index+=1 58 | elsif backward_brak = codon[/\A(\})/,1] 59 | bracket_end +=1 60 | raise "Extraneous closing brace #{code[index-5, index+5]}" if bracket_end > bracket_start 61 | tokens << [:END_BRACKET, backward_brak] 62 | index += 1 63 | 64 | bracket_end-=1 65 | bracket_start-=1 66 | elsif operator = codon[/\A(\|\||&&|==|!=|<=|>=)/, 1] 67 | tokens << [operator, operator] 68 | index += operator.size 69 | 70 | elsif codon.match(/\A /) 71 | index += 1 72 | 73 | else 74 | value = codon[0,1] 75 | tokens << [value, value] 76 | index += 1 77 | 78 | end 79 | 80 | end 81 | 82 | 83 | raise "Extraneous starting brace {"if bracket_start > bracket_end 84 | 85 | tokens 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /Book/book/code/test/parser_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "parser" 3 | 4 | class ParserTest < Test::Unit::TestCase 5 | def test_number 6 | assert_equal Nodes.new([NumberNode.new(1)]), Parser.new.parse("1") 7 | end 8 | 9 | def test_expression 10 | assert_equal Nodes.new([NumberNode.new(1), StringNode.new("hi")]), Parser.new.parse(%{1\n"hi"}) 11 | end 12 | 13 | def test_call 14 | assert_equal Nodes.new([CallNode.new(NumberNode.new(1), "method", [])]), Parser.new.parse("1.method") 15 | end 16 | 17 | def test_call_with_arguments 18 | assert_equal Nodes.new([CallNode.new(nil, "method", [NumberNode.new(1), NumberNode.new(2)])]), Parser.new.parse("method(1, 2)") 19 | end 20 | 21 | def test_assign 22 | assert_equal Nodes.new([SetLocalNode.new("a", NumberNode.new(1))]), Parser.new.parse("a = 1") 23 | assert_equal Nodes.new([SetConstantNode.new("A", NumberNode.new(1))]), Parser.new.parse("A = 1") 24 | end 25 | 26 | def test_def 27 | code = <<-CODE 28 | def method: 29 | true 30 | CODE 31 | 32 | nodes = Nodes.new([ 33 | DefNode.new("method", [], 34 | Nodes.new([TrueNode.new]) 35 | ) 36 | ]) 37 | 38 | assert_equal nodes, Parser.new.parse(code) 39 | end 40 | 41 | def test_def_with_param 42 | code = <<-CODE 43 | def method(a, b): 44 | true 45 | CODE 46 | 47 | nodes = Nodes.new([ 48 | DefNode.new("method", ["a", "b"], 49 | Nodes.new([TrueNode.new]) 50 | ) 51 | ]) 52 | 53 | assert_equal nodes, Parser.new.parse(code) 54 | end 55 | 56 | def test_class 57 | code = <<-CODE 58 | class Muffin: 59 | true 60 | CODE 61 | 62 | nodes = Nodes.new([ 63 | ClassNode.new("Muffin", 64 | Nodes.new([TrueNode.new]) 65 | ) 66 | ]) 67 | 68 | assert_equal nodes, Parser.new.parse(code) 69 | end 70 | 71 | def test_arithmetic 72 | nodes = Nodes.new([ 73 | CallNode.new(NumberNode.new(1), "+", [ 74 | CallNode.new(NumberNode.new(2), "*", [NumberNode.new(3)]) 75 | ]) 76 | ]) 77 | assert_equal nodes, Parser.new.parse("1 + 2 * 3") 78 | assert_equal nodes, Parser.new.parse("1 + (2 * 3)") 79 | end 80 | 81 | def test_binary_operator 82 | assert_equal Nodes.new([ 83 | CallNode.new( 84 | CallNode.new(NumberNode.new(1), "+", [NumberNode.new(2)]), 85 | "||", 86 | [NumberNode.new(3)] 87 | ) 88 | ]), Parser.new.parse("1 + 2 || 3") 89 | end 90 | 91 | ## Exercise: Add a grammar rule to handle the `!` unary operators 92 | # Remove the x in front of the method name to run. 93 | def xtest_unary_operator 94 | assert_equal Nodes.new([ 95 | CallNode.new(NumberNode.new(2), "!", []) 96 | ]), Parser.new.parse("!2") 97 | end 98 | 99 | def test_if 100 | code = <<-CODE 101 | if true: 102 | nil 103 | CODE 104 | 105 | nodes = Nodes.new([ 106 | IfNode.new(TrueNode.new, 107 | Nodes.new([NilNode.new]) 108 | ) 109 | ]) 110 | 111 | assert_equal nodes, Parser.new.parse(code) 112 | end 113 | end -------------------------------------------------------------------------------- /Book/book/code/test/lexer_test.rb: -------------------------------------------------------------------------------- 1 | require "./test_helper.rb" 2 | require "../lexer.rb" 3 | 4 | class LexerTest < Test::Unit::TestCase 5 | def test_number 6 | assert_equal [[:NUMBER, 1]], Lexer.new.tokenize("1") 7 | end 8 | 9 | def test_string 10 | assert_equal [[:STRING, "hi"]], Lexer.new.tokenize('"hi"') 11 | end 12 | 13 | def test_identifier 14 | assert_equal [[:IDENTIFIER, "name"]], Lexer.new.tokenize('name') 15 | end 16 | 17 | def test_constant 18 | assert_equal [[:CONSTANT, "Name"]], Lexer.new.tokenize('Name') 19 | end 20 | 21 | def test_operator 22 | assert_equal [["+", "+"]], Lexer.new.tokenize('+') 23 | assert_equal [["||", "||"]], Lexer.new.tokenize('||') 24 | end 25 | 26 | def test_indent 27 | code = <<-CODE 28 | if 1: 29 | if 2: 30 | print("...") 31 | if false: 32 | pass 33 | print("done!") 34 | 2 35 | 36 | print "The End" 37 | CODE 38 | tokens = [ 39 | [:IF, "if"], [:NUMBER, 1], # if 1: 40 | [:INDENT, 2], 41 | [:IF, "if"], [:NUMBER, 2], # if 2: 42 | [:INDENT, 4], 43 | [:IDENTIFIER, "print"], ["(", "("], # print("...") 44 | [:STRING, "..."], 45 | [")", ")"], 46 | [:NEWLINE, "\n"], 47 | [:IF, "if"], [:FALSE, "false"], # if false: 48 | [:INDENT, 6], 49 | [:IDENTIFIER, "pass"], # pass 50 | [:DEDENT, 4], [:NEWLINE, "\n"], 51 | [:IDENTIFIER, "print"], ["(", "("], # print("done!") 52 | [:STRING, "done!"], 53 | [")", ")"], 54 | [:DEDENT, 2], [:NEWLINE, "\n"], 55 | [:NUMBER, 2], # 2 56 | [:DEDENT, 0], [:NEWLINE, "\n"], 57 | [:NEWLINE, "\n"], # 58 | [:IDENTIFIER, "print"], [:STRING, "The End"] # print "The End" 59 | ] 60 | assert_equal tokens, Lexer.new.tokenize(code) 61 | end 62 | 63 | ## Exercise: Modify the lexer to delimit blocks with { ... } instead of indentation. 64 | # def test_braket_lexer 65 | # require "bracket_lexer" 66 | 67 | # code = <<-CODE 68 | # if 1 { 69 | # print "..." 70 | # if false { 71 | # pass 72 | # } 73 | # print "done!" 74 | # } 75 | # print "The End" 76 | # CODE 77 | 78 | # tokens = [ 79 | # [:IF, "if"], [:NUMBER, 1], 80 | # ["{", "{"], [:NEWLINE, "\n"], 81 | # [:IDENTIFIER, "print"], [:STRING, "..."], [:NEWLINE, "\n"], 82 | # [:IF, "if"], [:FALSE, "false"], ["{", "{"], [:NEWLINE, "\n"], 83 | # [:IDENTIFIER, "pass"], [:NEWLINE, "\n"], 84 | # ["}", "}"], [:NEWLINE, "\n"], 85 | # [:IDENTIFIER, "print"], [:STRING, "done!"], [:NEWLINE, "\n"], 86 | # ["}", "}"], [:NEWLINE, "\n"], 87 | # [:IDENTIFIER, "print"], [:STRING, "The End"] 88 | # ] 89 | # assert_equal tokens, BracketLexer.new.tokenize(code) 90 | # end 91 | end -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/YourLangObject.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | Any object, instance of a class, inside the runtime. 7 | Objects store a class and instance variables. 8 | */ 9 | public class YourLangObject { 10 | private YourLangClass yourLangClass; 11 | private HashMap instanceVariables; 12 | 13 | /** 14 | Creates an instance of class yourLangClass. 15 | */ 16 | public YourLangObject(YourLangClass yourLangClass) { 17 | this.yourLangClass = yourLangClass; 18 | this.instanceVariables = new HashMap(); 19 | } 20 | 21 | public YourLangObject(String className) { 22 | this(YourLangRuntime.getRootClass(className)); 23 | } 24 | 25 | public YourLangObject() { 26 | this(YourLangRuntime.getObjectClass()); 27 | } 28 | 29 | public YourLangClass getYourLangClass() { 30 | return yourLangClass; 31 | } 32 | 33 | public void setYourLangClass(YourLangClass klass) { 34 | yourLangClass = klass; 35 | } 36 | 37 | public YourLangObject getInstanceVariable(String name) { 38 | if (hasInstanceVariable(name)) 39 | return instanceVariables.get(name); 40 | return YourLangRuntime.getNil(); 41 | } 42 | 43 | public boolean hasInstanceVariable(String name) { 44 | return instanceVariables.containsKey(name); 45 | } 46 | 47 | public void setInstanceVariable(String name, YourLangObject value) { 48 | instanceVariables.put(name, value); 49 | } 50 | 51 | /** 52 | Call a method on the object. 53 | */ 54 | public YourLangObject call(String method, YourLangObject arguments[]) throws YourLangException { 55 | return yourLangClass.lookup(method).call(this, arguments); 56 | } 57 | 58 | public YourLangObject call(String method) throws YourLangException { 59 | return call(method, new YourLangObject[0]); 60 | } 61 | 62 | /** 63 | Only false and nil are not true. 64 | */ 65 | public boolean isTrue() { 66 | return !isFalse(); 67 | } 68 | 69 | /** 70 | Only false and nil are false. This is overridden in ValueObject. 71 | */ 72 | public boolean isFalse() { 73 | return false; 74 | } 75 | 76 | /** 77 | Only nil is nil. This is overridden in ValueObject. 78 | */ 79 | public boolean isNil() { 80 | return false; 81 | } 82 | 83 | /** 84 | Convert to a Java object. This is overridden in ValueObject. 85 | */ 86 | public Object toJavaObject() { 87 | return this; 88 | } 89 | 90 | public T as(Class clazz) throws TypeError { 91 | if (clazz.isInstance(this)){ 92 | return clazz.cast(this); 93 | } 94 | throw new TypeError(clazz.getName(), this); 95 | } 96 | 97 | public String asString() throws TypeError { 98 | return as(ValueObject.class).getValueAs(String.class); 99 | } 100 | 101 | public Integer asInteger() throws TypeError { 102 | return as(ValueObject.class).getValueAs(Integer.class); 103 | } 104 | 105 | public Float asFloat() throws TypeError { 106 | return as(ValueObject.class).getValueAs(Float.class); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Book/book/code/nodes.rb: -------------------------------------------------------------------------------- 1 | # The first type is responsible for holding a collection of nodes, 2 | # each one representing an expression. You can think of it as the internal 3 | # representation of a block of code. 4 | # 5 | # Here we define nodes as Ruby classes that inherit from a `Struct`. This is a 6 | # simple way, in Ruby, to create a class that holds some attributes (values). 7 | # It is almost equivalent to: 8 | # 9 | # class Nodes 10 | # def initialize(nodes) 11 | # @nodes = nodes 12 | # end 13 | # 14 | # def nodes 15 | # @nodes 16 | # end 17 | # end 18 | # 19 | # n = Nodes.new("this is stored @nodes") 20 | # n.nodes # => "this is stored @nodes" 21 | # 22 | # But Ruby's `Struct` takes care of overriding the `==` operator for us and a bunch of 23 | # other things that will make testing easier. 24 | class Nodes < Struct.new(:nodes) 25 | def <<(node) # Useful method for adding a node on the fly. 26 | nodes << node 27 | self 28 | end 29 | end 30 | 31 | # Literals are static values that have a Ruby representation. For example, a string, a number, 32 | # `true`, `false`, `nil`, etc. We define a node for each one of those and store their Ruby 33 | # representation inside their `value` attribute. 34 | class LiteralNode < Struct.new(:value); end 35 | 36 | class NumberNode < LiteralNode; end 37 | 38 | class StringNode < LiteralNode; end 39 | 40 | class TrueNode < LiteralNode 41 | def initialize 42 | super(true) 43 | end 44 | end 45 | 46 | class FalseNode < LiteralNode 47 | def initialize 48 | super(false) 49 | end 50 | end 51 | 52 | class NilNode < LiteralNode 53 | def initialize 54 | super(nil) 55 | end 56 | end 57 | 58 | # The node for a method call holds the `receiver`, 59 | # the object on which the method is called, the `method` name and its 60 | # arguments, which are other nodes. 61 | class CallNode < Struct.new(:receiver, :method, :arguments); end 62 | 63 | # Retrieving the value of a constant by its `name` is done by the following node. 64 | class GetConstantNode < Struct.new(:name); end 65 | 66 | # And setting its value is done by this one. The `value` will be a node. If we're 67 | # storing a number inside a constant, for example, `value` would contain an instance 68 | # of `NumberNode`. 69 | class SetConstantNode < Struct.new(:name, :value); end 70 | 71 | # Similar to the previous nodes, the next ones are for dealing with local variables. 72 | class GetLocalNode < Struct.new(:name); end 73 | 74 | class SetLocalNode < Struct.new(:name, :value); end 75 | 76 | # Each method definition will be stored into the following node. It holds the `name` of the method, 77 | # the name of its parameters (`params`) and the `body` to evaluate when the method is called, which 78 | # is a tree of node, the root one being a `Nodes` instance. 79 | class DefNode < Struct.new(:name, :params, :body); end 80 | 81 | # Class definitions are stored into the following node. Once again, the `name` of the class and 82 | # its `body`, a tree of nodes. 83 | class ClassNode < Struct.new(:name, :body); end 84 | 85 | # `if` control structures are stored in a node of their own. The `condition` and `body` will also 86 | # be nodes that need to be evaluated at some point. 87 | # Look at this node if you want to implement other control structures like `while`, `for`, `loop`, etc. 88 | class IfNode < Struct.new(:condition, :body); end -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/Context.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | import java.util.HashMap; 4 | import java.util.ArrayList; 5 | 6 | import java.io.Reader; 7 | import java.io.StringReader; 8 | import java.io.IOException; 9 | 10 | import org.antlr.runtime.ANTLRReaderStream; 11 | import org.antlr.runtime.CommonTokenStream; 12 | import org.antlr.runtime.RecognitionException; 13 | 14 | import yourlang.lang.nodes.Node; 15 | 16 | 17 | /** 18 | Evaluation context. Determines how a node will be evaluated. 19 | A context tracks local variables, self, and the current class under which 20 | methods and constants will be added. 21 | 22 | There are three different types of context: 23 | 1) In the root of the script, self = main object, class = Object 24 | 2) Inside a method body, self = instance of the class, class = method class 25 | 3) Inside a class definition self = the class, class = the class 26 | */ 27 | public class Context { 28 | private YourLangObject currentSelf; 29 | private YourLangClass currentClass; 30 | private HashMap locals; 31 | // A context can share local variables with a parent. For example, in the 32 | // try block. 33 | private Context parent; 34 | 35 | public Context(YourLangObject currentSelf, YourLangClass currentClass, Context parent) { 36 | this.currentSelf = currentSelf; 37 | this.currentClass = currentClass; 38 | this.parent = parent; 39 | if (parent == null) { 40 | locals = new HashMap(); 41 | } else { 42 | locals = parent.locals; 43 | } 44 | } 45 | 46 | public Context(YourLangObject currentSelf, YourLangClass currentClass) { 47 | this(currentSelf, currentClass, null); 48 | } 49 | 50 | public Context(YourLangObject currentSelf) { 51 | this(currentSelf, currentSelf.getYourLangClass()); 52 | } 53 | 54 | public Context() { 55 | this(YourLangRuntime.getMainObject()); 56 | } 57 | 58 | public YourLangObject getCurrentSelf() { 59 | return currentSelf; 60 | } 61 | 62 | public YourLangClass getCurrentClass() { 63 | return currentClass; 64 | } 65 | 66 | public YourLangObject getLocal(String name) { 67 | return locals.get(name); 68 | } 69 | 70 | public boolean hasLocal(String name) { 71 | return locals.containsKey(name); 72 | } 73 | 74 | public void setLocal(String name, YourLangObject value) { 75 | locals.put(name, value); 76 | } 77 | 78 | /** 79 | Creates a context that will share the same attributes (locals, self, class) 80 | as the current one. 81 | */ 82 | public Context makeChildContext() { 83 | return new Context(currentSelf, currentClass, this); 84 | } 85 | 86 | /** 87 | Parse and evaluate the content red from the reader (eg.: FileReader, StringReader). 88 | */ 89 | public YourLangObject eval(Reader reader) throws YourLangException { 90 | try { 91 | YourLangLexer lexer = new YourLangLexer(new ANTLRReaderStream(reader)); 92 | YourLangParser parser = new YourLangParser(new CommonTokenStream(lexer)); 93 | Node node = parser.parse(); 94 | if (node == null) return YourLangRuntime.getNil(); 95 | return node.eval(this); 96 | } catch (YourLangException e) { 97 | throw e; 98 | } catch (Exception e) { 99 | throw new YourLangException(e); 100 | } 101 | } 102 | 103 | public YourLangObject eval(String code) throws YourLangException { 104 | return eval(new StringReader(code)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/YourLangClass.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | Class in the runtime. 7 | Classes store methods and constants. Each object in the runtime has a class. 8 | */ 9 | public class YourLangClass extends YourLangObject { 10 | private String name; 11 | private YourLangClass superClass; 12 | private HashMap methods; 13 | HashMap constants; 14 | 15 | /** 16 | Creates a class inheriting from superClass. 17 | */ 18 | public YourLangClass(String name, YourLangClass superClass) { 19 | super("Class"); 20 | this.name = name; 21 | this.superClass = superClass; 22 | methods = new HashMap(); 23 | constants = new HashMap(); 24 | } 25 | 26 | /** 27 | Creates a class inheriting from Object. 28 | */ 29 | public YourLangClass(String name) { 30 | this(name, YourLangRuntime.getObjectClass()); 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public YourLangClass getSuperClass() { 38 | return superClass; 39 | } 40 | 41 | public void setConstant(String name, YourLangObject value) { 42 | constants.put(name, value); 43 | } 44 | 45 | public YourLangObject getConstant(String name) { 46 | if (constants.containsKey(name)) return constants.get(name); 47 | if (superClass != null) return superClass.getConstant(name); 48 | return YourLangRuntime.getNil(); 49 | } 50 | 51 | public boolean hasConstant(String name) { 52 | if (constants.containsKey(name)) return true; 53 | if (superClass != null) return superClass.hasConstant(name); 54 | return false; 55 | } 56 | 57 | public Method lookup(String name) throws MethodNotFound { 58 | if (methods.containsKey(name)) return methods.get(name); 59 | if (superClass != null) return superClass.lookup(name); 60 | throw new MethodNotFound(name); 61 | } 62 | 63 | public boolean hasMethod(String name) { 64 | if (methods.containsKey(name)) return true; 65 | if (superClass != null) return superClass.hasMethod(name); 66 | return false; 67 | } 68 | 69 | public void addMethod(String name, Method method) { 70 | methods.put(name, method); 71 | } 72 | 73 | /** 74 | Creates a new instance of the class. 75 | */ 76 | public YourLangObject newInstance() { 77 | return new YourLangObject(this); 78 | } 79 | 80 | /** 81 | Creates a new instance of the class, storing the value inside a ValueObject. 82 | */ 83 | public YourLangObject newInstance(Object value) { 84 | return new ValueObject(this, value); 85 | } 86 | 87 | /** 88 | Creates a new subclass of this class. 89 | */ 90 | public YourLangClass newSubclass(String name) { 91 | YourLangClass klass = new YourLangClass(name, this); 92 | YourLangRuntime.getObjectClass().setConstant(name, klass); 93 | return klass; 94 | } 95 | 96 | /** 97 | Creates or returns a subclass if it already exists. 98 | */ 99 | public YourLangClass subclass(String name) { 100 | YourLangClass objectClass = YourLangRuntime.getObjectClass(); 101 | if (objectClass.hasConstant(name)) return (YourLangClass) objectClass.getConstant(name); 102 | return newSubclass(name); 103 | } 104 | 105 | /** 106 | Returns true if klass is a subclass of this class. 107 | */ 108 | public boolean isSubclass(YourLangClass klass) { 109 | if (klass == this) return true; 110 | if (klass.getSuperClass() == null) return false; 111 | if (klass.getSuperClass() == this) return true; 112 | return isSubclass(klass.getSuperClass()); 113 | } 114 | 115 | @Override 116 | public boolean equals(Object other) { 117 | if (other == this) return true; 118 | if ( !(other instanceof YourLangClass) ) return false; 119 | return name == ((YourLangClass)other).getName(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Book/book/code/mio/message.rb: -------------------------------------------------------------------------------- 1 | module Mio 2 | # Message is a tree of tokens produced when parsing. 3 | # 1 print. 4 | # is parsed to: 5 | # Message.new("1", 6 | # Message.new("print")) 7 | # You can then +call+ the top level Message to eval it. 8 | class Message < Object 9 | attr_accessor :next, :name, :args, :line, :cached_value 10 | 11 | def initialize(name, line) 12 | @name = name 13 | @args = [] 14 | @line = line 15 | 16 | # Literals are static values, we can eval them right 17 | # away and cache the value. 18 | @cached_value = case @name 19 | when /^\d+/ 20 | Lobby["Number"].clone(@name.to_i) 21 | when /^"(.*)"$/ 22 | Lobby["String"].clone($1) 23 | end 24 | 25 | @terminator = [".", "\n"].include?(@name) 26 | 27 | super(Lobby["Message"]) 28 | end 29 | 30 | # Call (eval) the message on the +receiver+. 31 | def call(receiver, context=receiver, *args) 32 | if @terminator 33 | # reset receiver to object at beginning of the chain. 34 | # eg.: 35 | # hello there. yo 36 | # ^ ^__ "." resets back to the receiver here 37 | # \________________________________________________/ 38 | value = context 39 | elsif @cached_value 40 | # We already got the value 41 | value = @cached_value 42 | else 43 | # Lookup the slot on the receiver 44 | slot = receiver[name] 45 | 46 | # Eval the object in the slot 47 | value = slot.call(receiver, context, *@args) 48 | end 49 | 50 | # Pass to next message if some 51 | if @next 52 | @next.call(value, context) 53 | else 54 | value 55 | end 56 | rescue Mio::Error => e 57 | # Keep track of the message that caused the error to output 58 | # line number and such. 59 | e.current_message ||= self 60 | raise 61 | end 62 | 63 | def to_s(level=0) 64 | s = " " * level 65 | s << "" 69 | end 70 | alias inspect to_s 71 | 72 | # Parse a string into a tree of messages 73 | def self.parse(code) 74 | parse_all(code, 1).last 75 | end 76 | 77 | private 78 | def self.parse_all(code, line) 79 | code = code.strip 80 | i = 0 81 | message = nil 82 | messages = [] 83 | 84 | # Parsing code. Very similar to the Lexer we built for Awesome. 85 | while i < code.size 86 | case code[i..-1] 87 | when /\A("[^"]*")/, # string 88 | /\A(\d+)/, # number 89 | /\A(\.)+/, # dot 90 | /\A(\n)+/, # line break 91 | /\A(\w+)/ # name 92 | m = Message.new($1, line) 93 | if messages.empty? 94 | messages << m 95 | else 96 | message.next = m 97 | end 98 | line += $1.count("\n") 99 | message = m 100 | i += $1.size - 1 101 | when /\A(\(\s*)/ # arguments 102 | start = i + $1.size 103 | level = 1 104 | while level > 0 && i < code.size 105 | i += 1 106 | level += 1 if code[i] == ?\( 107 | level -= 1 if code[i] == ?\) 108 | end 109 | line += $1.count("\n") 110 | code_chunk = code[start..i-1] 111 | message.args = parse_all(code_chunk, line) 112 | line += code_chunk.count("\n") 113 | when /\A,(\s*)/ 114 | line += $1.count("\n") 115 | messages.concat parse_all(code[i+1..-1], line) 116 | break 117 | when /\A(\s+)/, # ignore whitespace 118 | /\A(#.*$)/ # ignore comments 119 | line += $1.count("\n") 120 | i += $1.size - 1 121 | else 122 | raise "Unknown char #{code[i].inspect} at line #{line}" 123 | end 124 | i += 1 125 | end 126 | messages 127 | end 128 | end 129 | end -------------------------------------------------------------------------------- /src/lexer.rb: -------------------------------------------------------------------------------- 1 | 2 | class Token 3 | attr_accessor :token, :value 4 | def initialize t, value 5 | @token, @value = t, v 6 | end 7 | end 8 | class Lexer 9 | KEYWORDS = ["true","false","if","elsif","else","while","case","when","do","class","def","nil"] 10 | def tokenize(code) 11 | code.chomp! 12 | tokens = [] # This is where generated tokens go 13 | current_indent = 0 14 | indent_stack = [] 15 | 16 | # bracket_start = 0 17 | # bracked_end = 0 18 | 19 | index = 0 # Current reading index 20 | while index < code.size 21 | codon = code[index..-1] # A codon of DNA, where DNA is the code and a codon is a chunk of it 22 | =begin 23 | Note: I originally wrote my own code to do this where each matched regex corresponds to a lambda which makes 24 | the code more modular, but in the end I realized it just overcomplicated things 25 | and so removed it. Then, I did a few things to make the code look nicer but they ended up also 26 | causing more complications so I left the code as it was in the book. 27 | From here and onwards, my strategy is to follow the book as closely as I can, learn from what the book does, 28 | and then implement a language COMPLETELY on my own using what I learned from following the book. 29 | Before I lead myself, I must follow someone else. 30 | 31 | Note: Is it a bad idea to design this language where scope can be defined using both {} and indents, or should it just stick to one? 32 | =end 33 | if identifier = codon[/\A([a-z]\w*)/, 1] 34 | if KEYWORDS.include?(identifier) 35 | tokens << [identifier.upcase.to_sym, identifier] 36 | else 37 | tokens << [:IDENTIFIER, identifier] 38 | end 39 | index += identifier.size 40 | 41 | 42 | elsif constant = codon[/\A([A-Z]\w*)/, 1] 43 | tokens << [:CONSTANT, constant] 44 | index += constant.size 45 | 46 | elsif number = codon[/\A([0-9]+)/, 1] 47 | tokens << [:NUMBER, number.to_i] 48 | index += number.size 49 | 50 | elsif string = codon[/\A"([^"]*)"/, 1] 51 | tokens << [:STRING, string] 52 | index += string.size + 2 53 | 54 | 55 | elsif indent = codon[/\A\:\n( +)/m, 1] 56 | if indent.size <= current_indent 57 | raise "Bad indent level, got #{indent.size} indents, " + 58 | "expected > #{current_indent}" 59 | end 60 | current_indent = indent.size 61 | indent_stack.push(current_indent) 62 | tokens << [:INDENT, indent.size] 63 | index += indent.size + 2 64 | 65 | elsif indent = codon[/\A\n( *)/m, 1] 66 | if indent.size == current_indent 67 | tokens << [:NEWLINE, "\n"] 68 | elsif indent.size < current_indent 69 | while indent.size < current_indent 70 | indent_stack.pop 71 | current_indent = indent_stack.last || 0 72 | tokens << [:DEDENT, indent.size] 73 | end 74 | tokens << [:NEWLINE, "\n"] 75 | else 76 | raise "Missing ':'" 77 | end 78 | index += indent.size + 1 79 | # elsif forward_brak = codon[/\A(\{ *\n *)/,1] 80 | # bracket_start+=1 81 | # token << [:START_BRACKET, forward_brak] 82 | # index+=1 83 | # elsif backward_brak = codon[/\A( *\} *\n})/,1] 84 | # bracket_end +=1 85 | # raise "Extraneous closing brace" if bracket_end > bracket_start 86 | # raised "" 87 | # token << [:END_BRACKET, backward_brak] 88 | # index += 1 89 | 90 | # bracket_end-=1 91 | # bracket_start-=1 92 | elsif operator = codon[/\A(\|\||&&|==|!=|<=|>=)/, 1] 93 | tokens << [operator, operator] 94 | index += operator.size 95 | 96 | elsif codon.match(/\A /) 97 | index += 1 98 | 99 | else 100 | value = codon[0,1] 101 | tokens << [value, value] 102 | index += 1 103 | 104 | end 105 | 106 | end 107 | while indent = indent_stack.pop 108 | tokens << [:DEDENT, indent_stack.first || 0] 109 | end 110 | 111 | # raise "Extraneous starting brace {"if bracket_start > bracket_end 112 | 113 | tokens 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /Book/book/code/interpreter.rb: -------------------------------------------------------------------------------- 1 | require "parser" 2 | require "runtime" 3 | 4 | # First, we create an simple wrapper class to encapsulate the interpretation process. 5 | # All this does is parse the code and call `eval` on the node at the top of the AST. 6 | class Interpreter 7 | def initialize 8 | @parser = Parser.new 9 | end 10 | 11 | def eval(code) 12 | @parser.parse(code).eval(RootContext) 13 | end 14 | end 15 | 16 | # The `Nodes` class will always be at the top of the AST. Its only purpose it to 17 | # contain other nodes. It correspond to a block of code or a series of expressions. 18 | # 19 | # The `eval` method of every node is the "interpreter" part of our language. 20 | # All nodes know how to evalualte themselves and return the result of their evaluation. 21 | # The `context` variable is the `Context` in which the node is evaluated (local 22 | # variables, current self and current class). 23 | class Nodes 24 | def eval(context) 25 | return_value = nil 26 | nodes.each do |node| 27 | return_value = node.eval(context) 28 | end 29 | return_value || Constants["nil"] # Last result is return value (or nil if none). 30 | end 31 | end 32 | 33 | # We're using `Constants` that we created before when bootstrapping the runtime to access 34 | # the objects and classes from inside the runtime. 35 | # 36 | # Next, we implement `eval` on other node types. Think of that `eval` method as how the 37 | # node bring itself to life inside the runtime. 38 | class NumberNode 39 | def eval(context) 40 | Constants["Number"].new_with_value(value) 41 | end 42 | end 43 | 44 | class StringNode 45 | def eval(context) 46 | Constants["String"].new_with_value(value) 47 | end 48 | end 49 | 50 | class TrueNode 51 | def eval(context) 52 | Constants["true"] 53 | end 54 | end 55 | 56 | class FalseNode 57 | def eval(context) 58 | Constants["false"] 59 | end 60 | end 61 | 62 | class NilNode 63 | def eval(context) 64 | Constants["nil"] 65 | end 66 | end 67 | 68 | class GetConstantNode 69 | def eval(context) 70 | Constants[name] 71 | end 72 | end 73 | 74 | class GetLocalNode 75 | def eval(context) 76 | context.locals[name] 77 | end 78 | end 79 | 80 | # When setting the value of a constant or a local variable, the `value` attribute 81 | # is a node, created by the parser. We need to evaluate the node first, to convert 82 | # it to an object, before storing it into a variable or constant. 83 | class SetConstantNode 84 | def eval(context) 85 | Constants[name] = value.eval(context) 86 | end 87 | end 88 | 89 | class SetLocalNode 90 | def eval(context) 91 | context.locals[name] = value.eval(context) 92 | end 93 | end 94 | 95 | # The `CallNode` for calling a method is a little more complex. It needs to set the receiver 96 | # first and then evaluate the arguments before calling the method. 97 | class CallNode 98 | def eval(context) 99 | if receiver 100 | value = receiver.eval(context) 101 | else 102 | value = context.current_self # Default to `self` if no receiver. 103 | end 104 | 105 | evaluated_arguments = arguments.map { |arg| arg.eval(context) } 106 | value.call(method, evaluated_arguments) 107 | end 108 | end 109 | 110 | # Defining a method, using the `def` keyword, is done by adding a method to the current class. 111 | class DefNode 112 | def eval(context) 113 | method = AwesomeMethod.new(params, body) 114 | context.current_class.runtime_methods[name] = method 115 | end 116 | end 117 | 118 | # Defining a class is done in three steps: 119 | # 120 | # 1. Reopen or define the class. 121 | # 2. Create a special context of evaluation (set `current_self` and `current_class` to the new class). 122 | # 3. Evaluate the body of the class inside that context. 123 | # 124 | # Check back how `DefNode` was implemented, adding methods to `context.current_class`. Here is 125 | # where we set the value of `current_class`. 126 | class ClassNode 127 | def eval(context) 128 | awesome_class = Constants[name] # Check if class is already defined 129 | 130 | unless awesome_class # Class doesn't exist yet 131 | awesome_class = AwesomeClass.new 132 | Constants[name] = awesome_class # Define the class in the runtime 133 | end 134 | 135 | class_context = Context.new(awesome_class, awesome_class) 136 | body.eval(class_context) 137 | 138 | awesome_class 139 | end 140 | end 141 | 142 | # Finally, to implement `if` in our language, 143 | # we turn the condition node into a Ruby value to use Ruby's `if`. 144 | class IfNode 145 | def eval(context) 146 | if condition.eval(context).ruby_value 147 | body.eval(context) 148 | else # If no body is evaluated, we return nil. 149 | Constants["nil"] 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /src/shunting_yard.rb: -------------------------------------------------------------------------------- 1 | require 'readline' 2 | require_relative './extend_core.rb' 3 | 4 | # puts "Welcome to Shunting Yard - the algorithm" #If you are using it as a standalone program 5 | 6 | class Operator 7 | attr_accessor :associativity, :precedence 8 | end 9 | 10 | def op asos, prec 11 | x = Operator.new 12 | x.associativity = asos 13 | x.precedence = prec 14 | 15 | x 16 | end 17 | class Shunt 18 | 19 | :left_associative # Using the following two as an "anonymous" enum. 20 | :right_associative 21 | 22 | 23 | 24 | 25 | 26 | 27 | OPERATORS = { 28 | "+" => op(:left_associative,1), 29 | "-" => op(:left_associative,1), 30 | "*" => op(:left_associative,2), 31 | "/" => op(:left_associative,2), 32 | "^" => op(:right_associative,5) 33 | } 34 | OPERATOR = /(^[\+\-\*\\\^])/ 35 | 36 | FUNCTIONS = ["cos","sin","tan","arctan","arccos","arcsin","sqrt","log2","log10","log"] 37 | FUNCTION = /[a-z]+/ 38 | 39 | FUNCTION_ARG_SEPARATOR = /[\,]/ 40 | 41 | 42 | =begin 43 | Thanks to http://en.wikipedia.org/wiki/Shunting-yard_algorithm for helping me out 44 | Shunting yard converts expressiong in infix notation to RPN or Reverse Polish Notation. So 3,^,4,+,5 becomes 3,4,^,5,+ 45 | The time complexity for the shunting yard algorithm is O(n) 46 | 47 | Here's why it works: 48 | 49 | Numbers automatically get pushed to output. 50 | 51 | Let Op1 be the currently being evaluated operator, and Op2 the operator at the top of the operator stack 52 | 53 | Operators with higher precedence need to be evaluated first, so they are immediately pushed onto the output stack in every case. 54 | Left associative operators, Op2, are pushed to the output stack if they are MORE THAN and EQUAL TO the currently being evaluated operator Op1: 55 | because left associative operators are bound to numbers that have previously been pushed to the output stack 56 | - thus these numbers necessitate that their operators be placed near to them 57 | 58 | Right associative operators, Op2 are only pushed to the stack ONLY if they have HIGHER PRECEDANCE than Op1 59 | because right associative operators split an expression in half and can only be pushed if they have a HIGHER precedence. 60 | If they were pushed with an equal precedence, it would betrey the fact that they divide an expression in half: 61 | 62 | Given 3^4^5^6 or 3 ^ (4^(5^6)) (Notice how expression is divided by the "^"?) => RPN: 3(4 (5 6 ^) ^ )^ 63 | If you immediately pushed the ^ operator to the output queue when it has equal precedence, 64 | then the RVM of the expression would be: 3 4 ^ 5 6 ^ ^, which is not correct. 65 | 66 | A right associative operator that comes before another instance of itself in an expression has a higher precedence than the second instance of itself. 67 | Everything after a "^" is included in a different scope(see expression above), which is why the "^" cannot be released to the output queue. It applies to the ENTIRE scope after itself. 68 | 69 | I hope this was a good explanation for the reasons behind which operators go where in the shunting yard algorithm. 70 | 71 | 72 | =end 73 | def self.eval string 74 | operator_stack = [] 75 | output = [] 76 | 77 | i = 0 78 | while i < string.size 79 | 80 | chunk = string[i..-1] 81 | if num = chunk.shift_number 82 | i+=num.size 83 | output << num.to_n 84 | elsif chunk[0] == " " 85 | i+=1 86 | elsif op1_char = chunk[OPERATOR,1] 87 | 88 | op1 = OPERATORS[op1_char] 89 | op2 = OPERATORS[operator_stack.last] 90 | cond1 = ((op1.associativity == :left_associative) && (op1.precedence <= op2.precedence)) if op2 91 | cond2 = ((op1.associativity == :right_associative) && (op1.precedence < op2.precedence)) if op2 92 | while op2 && (cond1 || cond2) #While the first character in array is an operator 93 | output << operator_stack.pop 94 | 95 | op2 = OPERATORS[operator_stack.last] 96 | cond1 = ((op1.associativity == :left_associative) && (op1.precedence <= op2.precedence)) if op2 97 | cond2 = ((op1.associativity == :right_associative) && (op1.precedence < op2.precedence)) if op2 98 | end 99 | 100 | i+=op1_char.size 101 | operator_stack << op1_char #Push op1 onto operator stack for further evaluation 102 | elsif (chunk =~ FUNCTION) ==0 103 | func = chunk.match_first(FUNCTION) 104 | output << func 105 | i+=func.size 106 | elsif (chunk =~ FUNCTION_ARG_SEPARATOR) ==0 107 | j = 0 108 | found = false 109 | until (operator_stack[j] == "(") || j==operator_stack.size 110 | output << operator_stack.pop 111 | j+=1 112 | end 113 | found = true if operator_stack[j] == "(" 114 | raise "Mismatched parenthesis or misplaced function arg separator" unless found 115 | i+=1 116 | elsif paren_left = chunk["("] 117 | i+=1 118 | operator_stack << "(" 119 | elsif chunk[0] == ")" 120 | j = 0 121 | until (j >= operator_stack.size) || (operator_stack.last == "(") 122 | output << operator_stack.pop 123 | j+=1 124 | end 125 | 126 | left_paren = operator_stack.pop 127 | output << operator_stack.pop if operator_stack[0] =~ /[a-z]+/ 128 | raise "Unmatched (" unless left_paren 129 | i+=1 130 | 131 | else 132 | raise "Could not parse operator:#{i}: #{chunk}" 133 | end 134 | 135 | end 136 | 137 | while op = operator_stack.pop 138 | raise "Mismatched parenthesis" if op == ("(" || ")") 139 | output << op 140 | end 141 | output 142 | 143 | end 144 | 145 | def repl 146 | while buf = Readline.readline(">>", true) 147 | puts Shunt.eval(buf).inspect 148 | end 149 | end 150 | 151 | end -------------------------------------------------------------------------------- /Book/book/code/lexer.rb: -------------------------------------------------------------------------------- 1 | 2 | # and will return an array of tokens (a token being a tuple of `[TOKEN_TYPE, TOKEN_VALUE]`). 3 | class Lexer 4 | # First we define the special keywords of our language in a constant. 5 | # It will be used later on in the tokenizing process to disambiguate 6 | # an identifier (method name, local variable, etc.) from a keyword. 7 | KEYWORDS = ["def", "class", "if", "true", "false", "nil"] 8 | 9 | def tokenize(code) 10 | code.chomp! # Remove extra line breaks 11 | tokens = [] # This will hold the generated tokens 12 | 13 | # We need to know how deep we are in the indentation so 14 | # we keep track of the current indentation level we are in, and previous ones in the stack 15 | # so that when we dedent, we can check if we're on the correct level. 16 | current_indent = 0 # number of spaces in the last indent 17 | indent_stack = [] 18 | 19 | # Here is how to implement a very simple scanner. 20 | # Advance one character at the time until you find something to parse. 21 | # We'll use regular expressions to scan from the current position (`i`) 22 | # up to the end of the code. 23 | i = 0 # Current character position 24 | while i < code.size 25 | chunk = code[i..-1] 26 | 27 | # Each of the following `if/elsif`s will test the current code chunk with 28 | # a regular expression. The order is important as we want to match `if` 29 | # as a keyword, and not a method name, we'll need to apply it first. 30 | # 31 | # First, we'll scan for names: method names and variable names, which we'll call identifiers. 32 | # Also scanning for special reserved keywords such as `if`, `def` 33 | # and `true`. 34 | if identifier = chunk[/\A([a-z]\w*)/, 1] 35 | if KEYWORDS.include?(identifier) # keywords will generate [:IF, "if"] 36 | tokens << [identifier.upcase.to_sym, identifier] 37 | else 38 | tokens << [:IDENTIFIER, identifier] 39 | end 40 | i += identifier.size # skip what we just parsed 41 | 42 | # Now scanning for constants, names starting with a capital letter. 43 | # Which means, class names are constants in our language. 44 | elsif constant = chunk[/\A([A-Z]\w*)/, 1] 45 | tokens << [:CONSTANT, constant] 46 | i += constant.size 47 | 48 | # Next, matching numbers. Our language will only support integers. But to add support for floats, 49 | # you'd simply need to add a similar rule and adapt the regular expression accordingly. 50 | elsif number = chunk[/\A([0-9]+)/, 1] 51 | tokens << [:NUMBER, number.to_i] 52 | i += number.size 53 | 54 | # Of course, matching strings too. Anything between `"..."`. 55 | elsif string = chunk[/\A"([^"]*)"/, 1] 56 | tokens << [:STRING, string] 57 | i += string.size + 2 # skip two more to exclude the `"`. 58 | 59 | # And here's the indentation magic! We have to take care of 3 cases: 60 | # 61 | # if true: # 1) The block is created. 62 | # line 1 63 | # line 2 # 2) New line inside a block, at the same level. 64 | # continue # 3) Dedent. 65 | # 66 | # This `elsif` takes care of the first case. The number of spaces will determine 67 | # the indent level. 68 | elsif indent = chunk[/\A\:\n( +)/m, 1] # Matches ": " 69 | if indent.size <= current_indent # indent should go up when creating a block 70 | raise "Bad indent level, got #{indent.size} indents, " + 71 | "expected > #{current_indent}" 72 | end 73 | current_indent = indent.size 74 | indent_stack.push(current_indent) 75 | tokens << [:INDENT, indent.size] 76 | i += indent.size + 2 77 | 78 | # The next `elsif` takes care of the two last cases: 79 | # 80 | # * Case 2: We stay in the same block if the indent level (number of spaces) is the 81 | # same as `current_indent`. 82 | # * Case 3: Close the current block, if indent level is lower than `current_indent`. 83 | elsif indent = chunk[/\A\n( *)/m, 1] # Matches " " 84 | if indent.size == current_indent # Case 2 85 | tokens << [:NEWLINE, "\n"] # Nothing to do, we're still in the same block 86 | elsif indent.size < current_indent # Case 3 87 | while indent.size < current_indent 88 | indent_stack.pop 89 | current_indent = indent_stack.last || 0 90 | tokens << [:DEDENT, indent.size] 91 | end 92 | tokens << [:NEWLINE, "\n"] 93 | else # indent.size > current_indent, error! 94 | raise "Missing ':'" # Cannot increase indent level without using ":" 95 | end 96 | i += indent.size + 1 97 | 98 | # Long operators such as `||`, `&&`, `==`, etc. 99 | # will be matched by the following block. 100 | # One character long operators are matched by the catch all `else` at the bottom. 101 | elsif operator = chunk[/\A(\|\||&&|==|!=|<=|>=)/, 1] 102 | tokens << [operator, operator] 103 | i += operator.size 104 | 105 | # We're ignoring spaces. Contrary to line breaks, spaces are meaningless in our language. 106 | # That's why we don't create tokens for them. They are only used to separate other tokens. 107 | elsif chunk.match(/\A /) 108 | i += 1 109 | 110 | # Finally, catch all single characters, mainly operators. 111 | # We treat all other single characters as a token. Eg.: `( ) , . ! + - <`. 112 | else 113 | value = chunk[0,1] 114 | tokens << [value, value] 115 | i += 1 116 | 117 | end 118 | 119 | end 120 | 121 | # Close all open blocks. If the code ends without dedenting, this will take care of 122 | # balancing the `INDENT`...`DEDENT`s. 123 | while indent = indent_stack.pop 124 | tokens << [:DEDENT, indent_stack.first || 0] 125 | end 126 | 127 | tokens 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /Book/book/code/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 | # First we initialize some data types we'll use during compilation. 13 | # Both correspond to common C types. 14 | PCHAR = LLVM.Pointer(LLVM::Int8) # equivalent to *char in C 15 | INT = LLVM::Int # equivalent to int in C 16 | 17 | # When storing a value in a local variable, LLVM will return back a pointer. 18 | # We need to keep track of the mapping local variable name => pointer. 19 | # This is what the following Hash does. 20 | attr_reader :locals 21 | 22 | # An instance of `LLVMCompiler` is responsible for compiling a given function. 23 | # We pass an LLVM module (`mod`), which is a container in which to store the code, 24 | # and a function to compile the code into. 25 | # 26 | # By default the function will be the standard C entry point: `void main()`. 27 | def initialize(mod=nil, function=nil) 28 | @module = mod || LLVM::Module.new("awesome") 29 | 30 | @locals = {} # To track local names during compilation 31 | 32 | @function = function || 33 | @module.functions.named("main") || # Default the function to `main` 34 | @module.functions.add("main", [], LLVM.Void) 35 | 36 | @builder = LLVM::Builder.new # Prepare a builder to build code. 37 | @builder.position_at_end(@function.basic_blocks.append) 38 | 39 | @engine = LLVM::JITCompiler.new(@module) # The machine code compiler. 40 | end 41 | 42 | # Before compiling our code, we'll declare external C functions we'll call from within 43 | # our program. Here is where our compiler will cheat quite a bit. Instead of reimplementing 44 | # our runtime inside the LLVM module, we won't support any of the OOP features and only allow 45 | # calling basic C functions we declare here, namely `int puts(char*)`. 46 | def preamble 47 | fun = @module.functions.add("puts", [PCHAR], INT) 48 | fun.linkage = :external 49 | end 50 | 51 | # Always finish the function with a `return`. 52 | def finish 53 | @builder.ret_void 54 | end 55 | 56 | # We'll also need to load literal values and be able to call functions. LLVM got us covered there. 57 | def new_string(value) 58 | @builder.global_string_pointer(value) 59 | end 60 | 61 | def new_number(value) 62 | LLVM::Int(value) 63 | end 64 | 65 | def call(name, args=[]) 66 | function = @module.functions.named(name) 67 | @builder.call(function, *args) 68 | end 69 | 70 | # Keep in mind we're compiling to machine code that will run right inside the processor. 71 | # There is no extra layer of abstraction here. When assigning local variables, we 72 | # first need to allocate memory for it. This is what we do here using `alloca` and then 73 | # store the value at that address in memory. 74 | def assign(name, value) 75 | ptr = @builder.alloca(value.type) # Allocate memory. 76 | @builder.store(value, ptr) # Store the value in the allocated memory. 77 | @locals[name] = ptr # Keep track of where we stored the local. 78 | end 79 | 80 | def load(name) 81 | ptr = @locals[name] 82 | @builder.load(ptr, name) # Load back the value stored for that local. 83 | end 84 | 85 | # Methods defined inside our runtime are compiled to functions (like C functions). 86 | # Functions are compiled using their own `LLVMCompiler` instance to scope their 87 | # local variables and code blocks. 88 | def function(name) 89 | func = @module.functions.add(name, [], LLVM.Void) 90 | compiler = LLVMCompiler.new(@module, func) 91 | yield compiler 92 | compiler.finish 93 | end 94 | 95 | # One of the biggest advantage of using LLVM and not rolling our owning machine code 96 | # compiler is that we're able to take advantage of all the optimizations. Compiling 97 | # to machine code is the "easy" (super giant quotes here) part. But by default your 98 | # code will not be that fast, you need to optimize it. This is what the `-O2` 99 | # option of your C compiler does. Here we'll only use one optimization as an example, 100 | # but LLVM has a lot of them. 101 | def optimize 102 | @module.verify! # Verify the code is valid. 103 | pass_manager = LLVM::PassManager.new(@engine) 104 | pass_manager.mem2reg! # Promote memory to machine registers. 105 | end 106 | 107 | # Here is where the magic happens! We JIT compile and run the LLVM code. JIT, for 108 | # just-in-time, because we compile it right before we execute it as opposed to AOT, 109 | # for ahead-of-time where we compile it upfront, like C. 110 | def run 111 | @engine.run_function(@function) 112 | end 113 | 114 | # LLVM doesn't compile directly to machine code but to an intermediate format called IR, 115 | # which is similar to assembly. If you want to inspect the generated IR for this module, 116 | # call the following method. 117 | def dump 118 | @module.dump 119 | end 120 | end 121 | 122 | # Now that we have our compiler ready we use the same approach as before and 123 | # reopen all the supported nodes and implement how each one is compiled. 124 | class Nodes 125 | def llvm_compile(compiler) 126 | nodes.map { |node| node.llvm_compile(compiler) }.last 127 | end 128 | end 129 | 130 | class NumberNode 131 | def llvm_compile(compiler) 132 | compiler.new_number(value) 133 | end 134 | end 135 | 136 | class StringNode 137 | def llvm_compile(compiler) 138 | compiler.new_string(value) 139 | end 140 | end 141 | 142 | # To keep things simple, our compiler only supports a subset of our Awesome 143 | # language. For example, we only support calling methods on `self` and not 144 | # on given receivers. We also don't support method parameters when defining methods. 145 | # This is why we raise an error in the following nodes. 146 | # See at the end of this chapter for more details on what is not supported and where 147 | # to go from here. 148 | class CallNode 149 | def llvm_compile(compiler) 150 | raise "Receiver not supported for compilation" if receiver 151 | 152 | compiled_arguments = arguments.map { |arg| arg.llvm_compile(compiler) } 153 | compiler.call(method, compiled_arguments) 154 | end 155 | end 156 | 157 | class GetLocalNode 158 | def llvm_compile(compiler) 159 | compiler.load(name) 160 | end 161 | end 162 | 163 | class SetLocalNode 164 | def llvm_compile(compiler) 165 | compiler.assign(name, value.llvm_compile(compiler)) 166 | end 167 | end 168 | 169 | class DefNode 170 | def llvm_compile(compiler) 171 | raise "Parameters not supported for compilation" if !params.empty? 172 | compiler.function(name) do |function| 173 | body.llvm_compile(function) 174 | end 175 | end 176 | end 177 | -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/Bootstrapper.java: -------------------------------------------------------------------------------- 1 | package yourlang.lang; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | Bootstrapper.run() is called to initialize the runtime. 7 | Core classes are created and methods are added. 8 | */ 9 | public class Bootstrapper { 10 | static public Context run() { 11 | // Create core classes 12 | YourLangClass objectClass = new YourLangClass("Object"); 13 | YourLangRuntime.objectClass = objectClass; 14 | // Each method sent or added on the root context of a script are evaled on the main object. 15 | YourLangObject main = new YourLangObject(); 16 | YourLangRuntime.mainObject = main; 17 | YourLangClass classClass = new YourLangClass("Class"); 18 | objectClass.setYourLangClass(classClass); // Object is a class 19 | classClass.setYourLangClass(classClass); // Class is a class 20 | main.setYourLangClass(objectClass); 21 | 22 | // Register core classes into the root context 23 | objectClass.setConstant("Object", objectClass); 24 | objectClass.setConstant("Class", classClass); 25 | // There is just one instance of nil, true, false, so we store those in constants. 26 | YourLangRuntime.nilObject = objectClass.newSubclass("NilClass").newInstance(null); 27 | YourLangRuntime.trueObject = objectClass.newSubclass("TrueClass").newInstance(true); 28 | YourLangRuntime.falseObject = objectClass.newSubclass("FalseClass").newInstance(false); 29 | YourLangClass stringClass = objectClass.newSubclass("String"); 30 | YourLangClass numberClass = objectClass.newSubclass("Number"); 31 | YourLangClass integerClass = numberClass.newSubclass("Integer"); 32 | YourLangClass floatClass = numberClass.newSubclass("Float"); 33 | YourLangClass exceptionClass = objectClass.newSubclass("Exception"); 34 | exceptionClass.newSubclass("IOException"); 35 | exceptionClass.newSubclass("TypeError"); 36 | exceptionClass.newSubclass("MethodNotFound"); 37 | exceptionClass.newSubclass("ArgumentError"); 38 | exceptionClass.newSubclass("FileNotFound"); 39 | 40 | // Add methods to core classes. 41 | 42 | //// Object 43 | objectClass.addMethod("print", new Method() { 44 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 45 | for (YourLangObject arg : arguments) System.out.println(arg.toJavaObject()); 46 | return YourLangRuntime.getNil(); 47 | } 48 | }); 49 | objectClass.addMethod("class", new Method() { 50 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 51 | return receiver.getYourLangClass(); 52 | } 53 | }); 54 | objectClass.addMethod("eval", new Method() { 55 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 56 | Context context = new Context(receiver); 57 | String code = arguments[0].asString(); 58 | return context.eval(code); 59 | } 60 | }); 61 | objectClass.addMethod("require", new Method() { 62 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 63 | Context context = new Context(); 64 | String filename = arguments[0].asString(); 65 | try { 66 | return context.eval(new FileReader(filename)); 67 | } catch (FileNotFoundException e) { 68 | throw new YourLangException("FileNotFound", "File not found: " + filename); 69 | } 70 | } 71 | }); 72 | 73 | //// Class 74 | classClass.addMethod("new", new Method() { 75 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 76 | YourLangClass self = (YourLangClass) receiver; 77 | YourLangObject instance = self.newInstance(); 78 | if (self.hasMethod("initialize")) instance.call("initialize", arguments); 79 | return instance; 80 | } 81 | }); 82 | classClass.addMethod("name", new Method() { 83 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 84 | YourLangClass self = (YourLangClass) receiver; 85 | return new ValueObject(self.getName()); 86 | } 87 | }); 88 | classClass.addMethod("superclass", new Method() { 89 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 90 | YourLangClass self = (YourLangClass) receiver; 91 | return self.getSuperClass(); 92 | } 93 | }); 94 | 95 | //// Exception 96 | exceptionClass.addMethod("initialize", new Method() { 97 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 98 | if (arguments.length == 1) receiver.setInstanceVariable("message", arguments[0]); 99 | return YourLangRuntime.getNil(); 100 | } 101 | }); 102 | exceptionClass.addMethod("message", new Method() { 103 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 104 | return receiver.getInstanceVariable("message"); 105 | } 106 | }); 107 | objectClass.addMethod("raise!", new Method() { 108 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 109 | String message = null; 110 | if (receiver.hasInstanceVariable("message")) message = receiver.getInstanceVariable("message").asString(); 111 | throw new YourLangException(receiver.getYourLangClass(), message); 112 | } 113 | }); 114 | 115 | //// Integer 116 | integerClass.addMethod("+", new OperatorMethod() { 117 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 118 | return new ValueObject(receiver + argument); 119 | } 120 | }); 121 | integerClass.addMethod("-", new OperatorMethod() { 122 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 123 | return new ValueObject(receiver + argument); 124 | } 125 | }); 126 | integerClass.addMethod("*", new OperatorMethod() { 127 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 128 | return new ValueObject(receiver * argument); 129 | } 130 | }); 131 | integerClass.addMethod("/", new OperatorMethod() { 132 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 133 | return new ValueObject(receiver / argument); 134 | } 135 | }); 136 | integerClass.addMethod("<", new OperatorMethod() { 137 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 138 | return YourLangRuntime.toBoolean(receiver < argument); 139 | } 140 | }); 141 | integerClass.addMethod(">", new OperatorMethod() { 142 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 143 | return YourLangRuntime.toBoolean(receiver > argument); 144 | } 145 | }); 146 | integerClass.addMethod("<=", new OperatorMethod() { 147 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 148 | return YourLangRuntime.toBoolean(receiver <= argument); 149 | } 150 | }); 151 | integerClass.addMethod(">=", new OperatorMethod() { 152 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 153 | return YourLangRuntime.toBoolean(receiver >= argument); 154 | } 155 | }); 156 | integerClass.addMethod("==", new OperatorMethod() { 157 | public YourLangObject perform(Integer receiver, Integer argument) throws YourLangException { 158 | return YourLangRuntime.toBoolean(receiver == argument); 159 | } 160 | }); 161 | 162 | //// String 163 | stringClass.addMethod("+", new OperatorMethod() { 164 | public YourLangObject perform(String receiver, String argument) throws YourLangException { 165 | return new ValueObject(receiver + argument); 166 | } 167 | }); 168 | stringClass.addMethod("size", new Method() { 169 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 170 | String self = receiver.asString(); 171 | return new ValueObject(self.length()); 172 | } 173 | }); 174 | stringClass.addMethod("substring", new Method() { 175 | public YourLangObject call(YourLangObject receiver, YourLangObject arguments[]) throws YourLangException { 176 | String self = receiver.asString(); 177 | if (arguments.length == 0) throw new ArgumentError("substring", 1, 0); 178 | int start = arguments[0].asInteger(); 179 | int end = self.length(); 180 | if (arguments.length > 1) end = arguments[1].asInteger(); 181 | return new ValueObject(self.substring(start, end)); 182 | } 183 | }); 184 | 185 | // Return the root context on which everything will be evaled. By default, everything is evaled on the 186 | // main object. 187 | return new Context(main); 188 | } 189 | } -------------------------------------------------------------------------------- /Book/jvm_lang/src/yourlang/lang/YourLangParser.g: -------------------------------------------------------------------------------- 1 | parser grammar YourLangParser; 2 | 3 | options { 4 | output = AST; // Produce a tree of node 5 | tokenVocab = YourLangLexer; // Use tokens defined in our lexer. 6 | backtrack=true; // Resolve ambiguities by looking tokens ahead, slower but simpler. 7 | } 8 | 9 | // Stuff added on top of the Parser class. 10 | @header { 11 | package yourlang.lang; 12 | 13 | import yourlang.lang.nodes.*; 14 | import java.util.ArrayList; 15 | } 16 | 17 | // Methods added to the Parser class. 18 | @members { 19 | /** 20 | Run the parsing process and return the root node of the AST. 21 | */ 22 | public Node parse() throws RecognitionException { 23 | root_return result = root(); 24 | if (result == null) return null; 25 | return result.nodes; 26 | } 27 | 28 | // Override to throw exceptions on parse error. 29 | @Override 30 | public void reportError(RecognitionException e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | 35 | // Rethrow parsing error 36 | @rulecatch { 37 | catch(RecognitionException recognitionException) { 38 | throw recognitionException; 39 | } 40 | } 41 | 42 | /* 43 | Format of a rule: 44 | 45 | ruleName returns [TypeOfNode nodeName]: 46 | // refer to values on the left using $. 47 | e=reference_to_other_rules { $nodeName = $e; } // code executed when rule matches 48 | | other rules 49 | ; 50 | 51 | Value stored in $nodeName will be passed to the parent rule. 52 | */ 53 | 54 | // Top-level node of each AST 55 | root returns [Nodes nodes]: 56 | terminator? expressions? EOF! { $nodes = $expressions.nodes; } 57 | ; 58 | 59 | // Collection of nodes, often refered to as a body (methd body, class body, etc.) 60 | expressions returns [Nodes nodes]: 61 | { $nodes = new Nodes(); } 62 | head=expression { $nodes.add($head.node); } 63 | (terminator 64 | tail=expression { $nodes.add($tail.node); } 65 | )* 66 | terminator? 67 | ; 68 | 69 | // A single expression 70 | expression returns [Node node]: 71 | assignExpression { $node = $assignExpression.node; } 72 | ; 73 | 74 | // Anything that can terminate an expression. 75 | terminator: (NEWLINE | SEMICOLON)+; 76 | 77 | // To implement operator precedence, we use order of evaluation in the parser. 78 | // First rules defined here are evaluated last. 79 | 80 | // Assignation has the lowest precedence, evaluated last. 81 | assignExpression returns [Node node]: 82 | assign { $node = $assign.node; } 83 | | e=orExpression { $node = $e.node; } 84 | ; 85 | 86 | orExpression returns [Node node]: 87 | receiver=andExpression 88 | OR arg=orExpression { $node = new OrNode($receiver.node, $arg.node); } 89 | | e=andExpression { $node = $e.node; } 90 | ; 91 | 92 | andExpression returns [Node node]: 93 | receiver=relationalExpression 94 | AND arg=andExpression { $node = new AndNode($receiver.node, $arg.node); } 95 | | e=relationalExpression { $node = $e.node; } 96 | ; 97 | 98 | relationalExpression returns [Node node]: 99 | receiver=additiveExpression 100 | op=(EQ|LE|GE|LT|GT) 101 | arg=relationalExpression { $node = new CallNode($op.text, $receiver.node, $arg.node); } 102 | | e=additiveExpression { $node = $e.node; } 103 | ; 104 | 105 | additiveExpression returns [Node node]: 106 | receiver=multiplicativeExpression 107 | op=(PLUS|MINUS) arg=additiveExpression { $node = new CallNode($op.text, $receiver.node, $arg.node); } 108 | | e=multiplicativeExpression { $node = $e.node; } 109 | ; 110 | 111 | multiplicativeExpression returns [Node node]: 112 | receiver=unaryExpression 113 | op=(MUL|DIV|MOD) arg=multiplicativeExpression { $node = new CallNode($op.text, $receiver.node, $arg.node); } 114 | | e=unaryExpression { $node = $e.node; } 115 | ; 116 | 117 | unaryExpression returns [Node node]: 118 | NOT receiver=unaryExpression { $node = new NotNode($receiver.node); } 119 | | e=primaryExpression { $node = $e.node; } 120 | ; 121 | // Highest precedence, evaluated first. 122 | 123 | primaryExpression returns [Node node]: 124 | literal { $node = $literal.node; } 125 | | call { $node = $call.node; } 126 | | methodDefinition { $node = $methodDefinition.node; } 127 | | classDefinition { $node = $classDefinition.node; } 128 | | ifBlock { $node = $ifBlock.node; } 129 | | whileBlock { $node = $whileBlock.node; } 130 | | tryBlock { $node = $tryBlock.node; } 131 | | OPEN_PARENT 132 | expression 133 | CLOSE_PARENT { $node = $expression.node; } 134 | ; 135 | 136 | // Any static value 137 | literal returns [Node node]: 138 | STRING { $node = new LiteralNode(new ValueObject($STRING.text.substring(1, $STRING.text.length() - 1))); } 139 | | INTEGER { $node = new LiteralNode(new ValueObject(new Integer($INTEGER.text))); } 140 | | FLOAT { $node = new LiteralNode(new ValueObject(new Float($FLOAT.text))); } 141 | | NIL { $node = new LiteralNode(YourLangRuntime.getNil()); } 142 | | TRUE { $node = new LiteralNode(YourLangRuntime.getTrue()); } 143 | | FALSE { $node = new LiteralNode(YourLangRuntime.getFalse()); } 144 | | constant { $node = $constant.node; } 145 | | instanceVariable { $node = $instanceVariable.node; } 146 | | self { $node = $self.node; } 147 | ; 148 | 149 | // self 150 | self returns [SelfNode node]: 151 | SELF { $node = new SelfNode(); } 152 | ; 153 | 154 | // Getting the value of an @instance_variable 155 | instanceVariable returns [InstanceVariableNode node]: 156 | AT NAME { $node = new InstanceVariableNode($NAME.text); } 157 | ; 158 | 159 | // A method call 160 | call returns [Node node]: 161 | (literal DOT { $node = $literal.node; } 162 | )? 163 | (head=message DOT { ((CallNode)$head.node).setReceiver($node); $node = $head.node; } 164 | )* 165 | tail=message { ((CallNode)$tail.node).setReceiver($node); $node = $tail.node; } 166 | ; 167 | 168 | // The tail part of a method call: method name + arguments 169 | message returns [CallNode node]: 170 | NAME { $node = new CallNode($NAME.text); } 171 | | NAME OPEN_PARENT CLOSE_PARENT { $node = new CallNode($NAME.text, new ArrayList()); } 172 | | NAME OPEN_PARENT 173 | arguments 174 | CLOSE_PARENT { $node = new CallNode($NAME.text, $arguments.nodes); } 175 | ; 176 | 177 | // Arguments of a method call. 178 | arguments returns [ArrayList nodes]: 179 | { $nodes = new ArrayList(); } 180 | head=expression { $nodes.add($head.node); } 181 | (COMMA 182 | tail=expression { $nodes.add($tail.node); } 183 | )* 184 | ; 185 | 186 | // Getting the value of a Constant 187 | constant returns [ConstantNode node]: 188 | CONSTANT { $node = new ConstantNode($CONSTANT.text); } 189 | ; 190 | 191 | // Variable of constant assignation 192 | assign returns [Node node]: 193 | NAME ASSIGN expression { $node = new LocalAssignNode($NAME.text, $expression.node); } 194 | | CONSTANT ASSIGN expression { $node = new ConstantAssignNode($CONSTANT.text, $expression.node); } 195 | | AT NAME ASSIGN expression { $node = new InstanceVariableAssignNode($NAME.text, $expression.node); } 196 | ; 197 | 198 | methodDefinition returns [MethodDefinitionNode node]: 199 | DEF NAME (OPEN_PARENT parameters? CLOSE_PARENT)? terminator 200 | expressions 201 | END { $node = new MethodDefinitionNode($NAME.text, $parameters.names, $expressions.nodes); } 202 | ; 203 | 204 | // Parameters in a method definition. 205 | parameters returns [ArrayList names]: 206 | { $names = new ArrayList(); } 207 | head=NAME { $names.add($head.text); } 208 | (COMMA 209 | tail=NAME { $names.add($tail.text); } 210 | )* 211 | ; 212 | 213 | classDefinition returns [ClassDefinitionNode node]: 214 | CLASS name=CONSTANT (LT superClass=CONSTANT)? terminator 215 | expressions 216 | END { $node = new ClassDefinitionNode($name.text, $superClass.text, $expressions.nodes); } 217 | ; 218 | 219 | ifBlock returns [IfNode node]: 220 | IF condition=expression terminator 221 | ifBody=expressions 222 | (ELSE terminator 223 | elseBody=expressions 224 | )? 225 | END { $node = new IfNode($condition.node, $ifBody.nodes, $elseBody.nodes); } 226 | ; 227 | 228 | whileBlock returns [WhileNode node]: 229 | WHILE condition=expression terminator 230 | body=expressions 231 | END { $node = new WhileNode($condition.node, $body.nodes); } 232 | ; 233 | 234 | tryBlock returns [TryNode node]: 235 | TRY terminator 236 | tryBody=expressions { $node = new TryNode($tryBody.nodes); } 237 | (CATCH CONSTANT COLON NAME terminator 238 | catchBody=expressions { $node.addCatchBlock($CONSTANT.text, $NAME.text, $catchBody.nodes); } 239 | )* 240 | END 241 | ; 242 | 243 | -------------------------------------------------------------------------------- /Book/book/code/grammar.y: -------------------------------------------------------------------------------- 1 | class Parser 2 | 3 | # We need to tell the parser what tokens to expect. So each type of token produced 4 | # by our lexer needs to be declared here. 5 | token IF 6 | token DEF 7 | token CLASS 8 | token NEWLINE 9 | token NUMBER 10 | token STRING 11 | token TRUE FALSE NIL 12 | token IDENTIFIER 13 | token CONSTANT 14 | token INDENT DEDENT 15 | 16 | # Here is the Operator Precedence Table. As presented before, it tells the parser in 17 | # which order to parse expressions containing operators. 18 | # This table is based on the [C and C++ Operator Precedence Table](http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence). 19 | prechigh 20 | left '.' 21 | right '!' 22 | left '*' '/' 23 | left '+' '-' 24 | left '>' '>=' '<' '<=' 25 | left '==' '!=' 26 | left '&&' 27 | left '||' 28 | right '=' 29 | left ',' 30 | preclow 31 | 32 | # In the following `rule` section, we define the parsing rules. 33 | # All rules are declared using the following format: 34 | # 35 | # RuleName: 36 | # OtherRule TOKEN AnotherRule { result = Node.new } 37 | # | OtherRule { ... } 38 | # ; 39 | # 40 | # In the action section (inside the `{...}` on the right), you can do the following: 41 | # 42 | # * Assign to `result` the value returned by the rule, usually a node for the AST. 43 | # * Use `val[index of expression]` to get the `result` of a matched 44 | # expressions on the left. 45 | rule 46 | # First, parsers are dumb, we need to explicitly tell it how to handle empty 47 | # programs. This is what the first rule does. Note that everything between `/* ... */` is 48 | # a comment. 49 | Program: 50 | /* nothing */ { result = Nodes.new([]) } 51 | | Expressions { result = val[0] } 52 | ; 53 | 54 | # Next, we define what a list of expressions is. Simply put, it's series of expressions separated by a 55 | # terminator (a new line or `;` as defined later). But once again, we need to explicitly 56 | # define how to handle trailing and orphans line breaks (the last two lines). 57 | # 58 | # One very powerful trick we'll use to define variable rules like this one 59 | # (rules which can match any number of tokens) is *left-recursion*. Which means we reference 60 | # the rule itself, directly or indirectly, on the left side **only**. This is true for the current 61 | # type of parser we're using (LR). For other types of parsers like ANTLR (LL), it's the opposite, 62 | # you can only use right-recursion. 63 | # 64 | # As you'll see bellow, the `Expressions` rule references `Expressions` itself. 65 | # In other words, a list of expressions can be another list of expressions followed by 66 | # another expression. 67 | Expressions: 68 | Expression { result = Nodes.new(val) } 69 | | Expressions Terminator Expression { result = val[0] << val[2] } 70 | | Expressions Terminator { result = val[0] } 71 | | Terminator { result = Nodes.new([]) } 72 | ; 73 | 74 | # Every type of expression supported by our language is defined here. 75 | Expression: 76 | Literal 77 | | Call 78 | | Operator 79 | | GetConstant 80 | | SetConstant 81 | | GetLocal 82 | | SetLocal 83 | | Def 84 | | Class 85 | | If 86 | | '(' Expression ')' { result = val[1] } 87 | ; 88 | 89 | # Notice how we implement support for parentheses using the previous rule. 90 | # `'(' Expression ')'` will force the parsing of `Expression` in its 91 | # entirety first. Parentheses will then be discarded leaving only the fully parsed expression. 92 | # 93 | # Terminators are tokens that can terminate an expression. 94 | # When using tokens to define rules, we simply reference them by their type which we defined in 95 | # the lexer. 96 | Terminator: 97 | NEWLINE 98 | | ";" 99 | ; 100 | 101 | # Literals are the hard-coded values inside the program. If you want to add support 102 | # for other literal types, such as arrays or hashes, this it where you'd do it. 103 | Literal: 104 | NUMBER { result = NumberNode.new(val[0]) } 105 | | STRING { result = StringNode.new(val[0]) } 106 | | TRUE { result = TrueNode.new } 107 | | FALSE { result = FalseNode.new } 108 | | NIL { result = NilNode.new } 109 | ; 110 | 111 | # Method calls can take three forms: 112 | # 113 | # * Without a receiver (`self` is assumed): `method(arguments)`. 114 | # * With a receiver: `receiver.method(arguments)`. 115 | # * And a hint of syntactic sugar so that we can drop 116 | # the `()` if no arguments are given: `receiver.method`. 117 | # 118 | # Each one of those is handled by the following rule. 119 | Call: 120 | IDENTIFIER Arguments { result = CallNode.new(nil, val[0], val[1]) } 121 | | Expression "." IDENTIFIER 122 | Arguments { result = CallNode.new(val[0], val[2], val[3]) } 123 | | Expression "." IDENTIFIER { result = CallNode.new(val[0], val[2], []) } 124 | ; 125 | 126 | Arguments: 127 | "(" ")" { result = [] } 128 | | "(" ArgList ")" { result = val[1] } 129 | ; 130 | 131 | ArgList: 132 | Expression { result = val } 133 | | ArgList "," Expression { result = val[0] << val[2] } 134 | ; 135 | 136 | 137 | # In our language, like in Ruby, operators are converted to method calls. 138 | # So `1 + 2` will be converted to `1.+(2)`. 139 | # `1` is the receiver of the `+` method call, passing `2` 140 | # as an argument. 141 | # Operators need to be defined individually for the Operator Precedence Table to take 142 | # action. 143 | Operator: 144 | Expression '||' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 145 | | Expression '&&' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 146 | | Expression '==' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 147 | | Expression '!=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 148 | | Expression '>' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 149 | | Expression '>=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 150 | | Expression '<' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 151 | | Expression '<=' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 152 | | Expression '+' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 153 | | Expression '-' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 154 | | Expression '*' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 155 | | Expression '/' Expression { result = CallNode.new(val[0], val[1], [val[2]]) } 156 | ; 157 | 158 | # Then we have rules for getting and setting values of constants and local variables. 159 | GetConstant: 160 | CONSTANT { result = GetConstantNode.new(val[0]) } 161 | ; 162 | 163 | SetConstant: 164 | CONSTANT "=" Expression { result = SetConstantNode.new(val[0], val[2]) } 165 | ; 166 | 167 | GetLocal: 168 | IDENTIFIER { result = GetLocalNode.new(val[0]) } 169 | ; 170 | 171 | SetLocal: 172 | IDENTIFIER "=" Expression { result = SetLocalNode.new(val[0], val[2]) } 173 | ; 174 | 175 | # Our language uses indentation to separate blocks of code. But the lexer took care of all 176 | # that complexity for us and wrapped all blocks in `INDENT ... DEDENT`. A block 177 | # is simply an increment in indentation followed by some code and closing with an equivalent 178 | # decrement in indentation. 179 | # 180 | # If you'd like to use curly brackets or `end` to delimit blocks instead, you'd 181 | # simply need to modify this one rule. 182 | # You'll also need to remove the indentation logic from the lexer. 183 | Block: 184 | INDENT Expressions DEDENT { result = val[1] } 185 | ; 186 | 187 | # The `def` keyword is used for defining methods. Once again, we're introducing 188 | # a bit of syntactic sugar here to allow skipping the parentheses when there are no parameters. 189 | Def: 190 | DEF IDENTIFIER Block { result = DefNode.new(val[1], [], val[2]) } 191 | | DEF IDENTIFIER 192 | "(" ParamList ")" Block { result = DefNode.new(val[1], val[3], val[5]) } 193 | ; 194 | 195 | ParamList: 196 | /* nothing */ { result = [] } 197 | | IDENTIFIER { result = val } 198 | | ParamList "," IDENTIFIER { result = val[0] << val[2] } 199 | ; 200 | 201 | # Class definition is similar to method definition. 202 | # Class names are also constants because they start with a capital letter. 203 | Class: 204 | CLASS CONSTANT Block { result = ClassNode.new(val[1], val[2]) } 205 | ; 206 | 207 | # Finally, `if` is similar to `class` but receives a *condition*. 208 | If: 209 | IF Expression Block { result = IfNode.new(val[1], val[2]) } 210 | ; 211 | end 212 | 213 | # The final code at the bottom of this Racc file will be put as-is in the generated `Parser` class. 214 | # You can put some code at the top (`header`) and some inside the class (`inner`). 215 | ---- header 216 | require "lexer" 217 | require "nodes" 218 | 219 | ---- inner 220 | def parse(code, show_tokens=false) 221 | @tokens = Lexer.new.tokenize(code) # Tokenize the code using our lexer 222 | puts @tokens.inspect if show_tokens 223 | do_parse # Kickoff the parsing process 224 | end 225 | 226 | def next_token 227 | @tokens.shift 228 | end -------------------------------------------------------------------------------- /Book/jvm_lang/LICENSE: -------------------------------------------------------------------------------- 1 | SHORT VERSION: 2 | 3 | You CAN use the included code to build a working programming 4 | language and sell it or open source it within the limits of the 5 | license you purchase. You CANNOT redistribute the included code 6 | solely as a starting point for creating a programming language. 7 | 8 | LONG VERSION: 9 | 10 | By purchasing, installing, or otherwise using the enclosed 11 | product, you agree to be bound by the terms and conditions of this 12 | License Agreement. 13 | 14 | IMPORTANT – READ CAREFULLY: This license agreement (“LICENSE”) is 15 | a legal agreement between you (either an individual or a single 16 | entity, also referred to as "LICENSEE", "YOU") and Marc-Andre 17 | Cournoyer, for the software containing this LICENSE which may also 18 | include the software’s source code written in a high-level 19 | computer language, associated media, printed materials, and 20 | "online" or electronic documentation (collectively referred to as 21 | “SOFTWARE”). 22 | 23 | Any earlier license we may have granted to you for the use of 24 | earlier versions of the SOFTWARE is replaced by this LICENSE. 25 | 26 | SOFTWARE PRODUCT LICENSE 27 | 28 | The SOFTWARE is protected by copyright laws and international 29 | copyright treaties, as well as other intellectual property laws 30 | and treaties and contains confidential information and trade 31 | secrets. Marc-Andre Cournoyer retains all rights not expressly 32 | granted to you in this LICENSE. 33 | 34 | I. GRANT OF LICENSE 35 | 36 | Marc-Andre Cournoyer hereby grants to you, and you accept, a non-exclusive, non-transferable license to install, copy, use and modify the SOFTWARE only as authorized below. 37 | 38 | This license grants to the right to use this SOFTWARE for personal use. You cannot redistribute this SOFTWARE unless you purchased the appropriate license (PRO). 39 | 40 | All product licenses are perpetual and royalty-free. 41 | 42 | 1. PERSONAL LICENSE 43 | 44 | You are granted a license to use the SOFTWARE for personal use. 45 | You may not redistribute the SOFTWARE in any form. 46 | 47 | 2. PRO LICENSE 48 | 49 | You are granted a license to use the SOFTWARE as the basis of 50 | one programming language can you can freely redistribute or open 51 | source. You use the license you want unless it grants the right 52 | to redistribute the code solely as a starting point for creating a programming language. 53 | 54 | REDISTRIBUTION RIGHTS 55 | 56 | You may distribute the SOFTWARE provided that: 57 | 58 | You reasonably ensure that the SOFTWARE is not distributed in any 59 | form that allows it to be reused by any application other than 60 | as a fully working programming language. 61 | 62 | SOURCE CODE 63 | 64 | Marc-Andre Cournoyer DOES NOT provide technical support for 65 | modified source code. 66 | The SOFTWARE’s source code is provided as is. In the event you 67 | develop any troubleshooting-related modifications of the SOFTWARE, 68 | either independently or jointly with Marc-Andre Cournoyer, such 69 | modifications and all rights associated therewith will be the 70 | exclusive property of Marc-Andre Cournoyer. You are granted the 71 | right to use such modifications as set forth in this agreement. 72 | You acknowledge that the SOFTWARE’s source code contains valuable 73 | and proprietary trade secrets of Marc-Andre Cournoyer. All 74 | individuals employed by or belonging to your entity 75 | agree to expend every effort to insure its confidentiality. You 76 | agree to assume full responsibility for such employees’ or 77 | contractors’ use, or misuse, of such disclosed source code as if 78 | it was your use. These obligations shall not apply to any 79 | information generally available to the public, independently 80 | developed or obtained without reliance on Marc-Andre Cournoyer 81 | information, or approved in writing for release by Marc-Andre 82 | Cournoyer without restriction. 83 | 84 | II. OTHER RIGHTS AND LIMITATIONS 85 | 86 | At no time may the SOFTWARE be used for development purposes by 87 | other individuals than the licensed developer(s). The SOFTWARE may 88 | not be distributed for use with solutions or PACKAGED PRODUCTS 89 | other than those developed by you. The SOFTWARE may not be 90 | distributed as part of products that have the same or 91 | substantially the same primary functionality. You are not allowed 92 | to resell, transfer, rent, lease, or sublicense the SOFTWARE and 93 | your associated rights. Under no circumstances shall you grant 94 | further redistribution rights to the end-users of your solution. 95 | You may not use the Marc-Andre Cournoyer product names, logos or 96 | trademarks to market YOUR SOFTWARE. You are not allowed to use, 97 | copy, modify, or merge copies of the SOFTWARE and any accompanying 98 | documents except as permitted in this LICENSE. You agree to 99 | indemnify, hold harmless, and defend Marc-Andre Cournoyer and its 100 | resellers from and against any and all claims or lawsuits 101 | including attorney's fees that arise or result from the use or 102 | distribution of YOUR SOFTWARE 103 | 104 | III. DELIVERY 105 | 106 | Marc-Andre Cournoyer shall deliver to LICENSEE a master copy of 107 | the SOFTWARE licensed hereunder in electronic files only. 108 | Documentation shall also be provided in electronic format. 109 | 110 | IV. UPGRADES 111 | 112 | You are eligible for free minor upgrades (e.g. v.1.5 to v.1.8), 113 | patches, and bug-fixes for the SOFTWARE, including source code. 114 | SOFTWARE labeled as an upgrade replaces and/or supplements (and 115 | may disable) the product that formed the basis for your 116 | eligibility for the upgrade. You may use the resulting upgraded 117 | product only in accordance with the terms of this LICENSE. 118 | 119 | You are entitled to receive all version updates for the SOFTWARE 120 | period of 1 (one) year. 121 | 122 | V. TERMINATION 123 | 124 | This LICENSE shall last as long as you use the SOFTWARE in 125 | compliance with this LICENSE. Marc-Andre Cournoyer may terminate 126 | this LICENSE ifyou fail to comply with any of the terms and 127 | conditions herein. In such event you agree to remove and destroy 128 | all copies of the SOFTWARE and any applicable source code. 129 | 130 | Marc-Andre Cournoyer reserves the right to discontinue at any time 131 | any product, shall it be offered individually or as a part of a 132 | product SUITE. However, Marc-Andre Cournoyer is obligated to 133 | provide the proper level of support for all discontinued products 134 | for a period of 1 (one) year after the date of discontinuance. 135 | 136 | VI. COPYRIGHT 137 | 138 | All title and copyrights in and to the SOFTWARE, the accompanying 139 | printed materials, and any copies of the SOFTWARE, and any 140 | trademarks or service marks of Marc-Andre Cournoyer are owned by 141 | Marc-Andre Cournoyer. All title and intellectual property rights 142 | in and to the content that may be accessed through use of the 143 | SOFTWARE is the property of the respective content owner and may 144 | be protected by applicable copyright or other intellectual 145 | property laws and treaties. This LICENSE grants you no rights to 146 | use such content. 147 | 148 | VII. LIMITED WARRANTY 149 | 150 | Marc-Andre Cournoyer warrants solely that the SOFTWARE will 151 | perform substantially in accordance with the accompanying written 152 | materials for a period of ninety (90) days. Marc-Andre Cournoyer 153 | does not warrant the use of the SOFTWARE will be uninterrupted or 154 | error free at all times and in all circumstances, nor that program 155 | errors will be corrected. This limited warranty shall not apply to 156 | any error or failure resulting from (i) machine error, (ii) 157 | LICENSEE's failure to follow operating instructions, (iii) 158 | negligence or accident, or (iv) modifications to the SOFTWARE by 159 | any person or entity other than Marc-Andre Cournoyer. In the event 160 | of a breach of warranty, LICENSEE's sole and exclusive remedy, is 161 | repair of all or any portion of the SOFTWARE. 162 | If such remedy fails of its essential purpose, LICENSEE's sole 163 | remedy and Marc-Andre Cournoyer's maximum liability shall be a 164 | refund of the paid purchase price for the defective SOFTWARE only. 165 | This limited warranty is only valid if Marc-Andre Cournoyer 166 | receives written notice of breach of warranty within thirty days 167 | after the warranty period expires. 168 | 169 | VIII. LIMITATION OF LIABILITY 170 | 171 | TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT 172 | WILL Marc-Andre Cournoyer BE LIABLE FOR ANY INDIRECT, SPECIAL, 173 | INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF OR 174 | INABILITY TO USE THE PRODUCT, INCLUDING, WITHOUT LIMITATION, 175 | DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR 176 | MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, 177 | EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF THE 178 | LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH 179 | THE CLAIM IS BASED. IN ANY CASE, Marc-Andre Cournoyer'S ENTIRE 180 | LIABILITY UNDER ANY PROVISION OF THIS AGREEMENT SHALL NOT EXCEED 181 | IN THE AGGREGATE THE SUM OF THE LICENSE FEES LICENSEE PAID TO 182 | Marc-Andre Cournoyer FOR THE PRODUCT GIVING RISE TO SUCH DAMAGES, 183 | NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY LIMITED 184 | REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION 185 | OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS 186 | EXCLUSION AND LIMITATION MAY NOT BE APPLICABLE. Marc-Andre 187 | Cournoyer IS NOT RESPONSIBLE FOR ANY LIABILITY ARISING OUT OF 188 | CONTENT PROVIDED BY LICENSEE OR A THIRD PARTY THAT IS ACCESSED 189 | THROUGH THE PRODUCT AND/OR ANY MATERIAL LINKED THROUGH SUCH 190 | CONTENT. ANY DATA INCLUDED IN A PRODUCT UPON SHIPMENT FROM 191 | Marc-Andre Cournoyer IS FOR TESTING USE ONLY AND Marc-Andre 192 | Cournoyer HEREBY DISCLAIMS ANY AND ALL LIABILITY ARISING 193 | THEREFROM. 194 | THE EXTENT OF Marc-Andre Cournoyer'S LIABILITY FOR THE LIMITED 195 | WARRANTY SECTION SHALL BE AS SET FORTH THEREIN. 196 | 197 | IX. MISCELLANEOUS 198 | 199 | This License will be governed by the law of the State of 200 | Washington, U.S.A. If any provision of this LICENSE is to be held 201 | unenforceable, such holding will not affect the validity of the 202 | other provisions hereof. Failure of a party to enforce any 203 | provision of this LICENSE shall not constitute or be construed as 204 | a waiver of such provision or of the right to enforce such 205 | provision. This License represents the entire understanding 206 | between the parties with respect to its subject matter. 207 | 208 | YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, THAT YOU 209 | UNDERSTAND THIS AGREEMENT, AND UNDERSTAND THAT BY CONTINUING THE 210 | INSTALLATION OF THE SOFTWARE PRODUCT, BY LOADING OR RUNNING THE 211 | SOFTWARE PRODUCT, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR 212 | COMPUTER HARD DRIVE, YOU AGREE TO BE BOUND BY THIS AGREEMENT'S 213 | TERMS AND CONDITIONS. YOU FURTHER AGREE THAT, EXCEPT FOR WRITTEN 214 | SEPARATE AGREEMENTS BETWEEN Marc-Andre Cournoyer AND YOU, THIS 215 | AGREEMENT IS A COMPLETE AND EXCLUSIVE STATEMENT OF THE RIGHTS AND 216 | LIABILITIES OF THE PARTIES. -------------------------------------------------------------------------------- /Book/book/code/LICENSE: -------------------------------------------------------------------------------- 1 | SHORT VERSION: 2 | 3 | You CAN use the included code to build a working programming 4 | language and sell it or open source it within the limits of the 5 | license you purchase. You CANNOT redistribute the included code 6 | solely as a starting point for creating a programming language. 7 | 8 | LONG VERSION: 9 | 10 | By purchasing, installing, or otherwise using the enclosed 11 | product, you agree to be bound by the terms and conditions of this 12 | License Agreement. 13 | 14 | IMPORTANT – READ CAREFULLY: This license agreement (“LICENSE”) is 15 | a legal agreement between you (either an individual or a single 16 | entity, also referred to as "LICENSEE", "YOU") and Marc-Andre 17 | Cournoyer, for the software containing this LICENSE which may also 18 | include the software’s source code written in a high-level 19 | computer language, associated media, printed materials, and 20 | "online" or electronic documentation (collectively referred to as 21 | “SOFTWARE”). 22 | 23 | Any earlier license we may have granted to you for the use of 24 | earlier versions of the SOFTWARE is replaced by this LICENSE. 25 | 26 | SOFTWARE PRODUCT LICENSE 27 | 28 | The SOFTWARE is protected by copyright laws and international 29 | copyright treaties, as well as other intellectual property laws 30 | and treaties and contains confidential information and trade 31 | secrets. Marc-Andre Cournoyer retains all rights not expressly 32 | granted to you in this LICENSE. 33 | 34 | I. GRANT OF LICENSE 35 | 36 | Marc-Andre Cournoyer hereby grants to you, and you accept, a 37 | non-exclusive, non-transferable license to install, copy, use and 38 | modify the SOFTWARE only as authorized below. 39 | 40 | This license grants to the right to use this SOFTWARE for personal 41 | use. You cannot redistribute this SOFTWARE unless you purchased 42 | the appropriate license (PRO). 43 | 44 | All product licenses are perpetual and royalty-free. 45 | 46 | 1. PERSONAL LICENSE 47 | 48 | You are granted a license to use the SOFTWARE for personal use. 49 | You may not redistribute the SOFTWARE in any form. 50 | 51 | 2. PRO LICENSE 52 | 53 | You are granted a license to use the SOFTWARE as the basis of one 54 | programming language can you can freely redistribute or open 55 | source. You use the license you want unless it grants the right to 56 | redistribute the code solely as a starting point for creating a 57 | programming language. 58 | 59 | REDISTRIBUTION RIGHTS 60 | 61 | You may distribute the SOFTWARE provided that: 62 | 63 | You reasonably ensure that the SOFTWARE is not distributed in any 64 | form that allows it to be reused by any application other than as 65 | a fully working programming language. 66 | 67 | SOURCE CODE 68 | 69 | Marc-Andre Cournoyer DOES NOT provide technical support for 70 | modified source code. The SOFTWARE’s source code is provided as 71 | is. In the event you develop any troubleshooting-related 72 | modifications of the SOFTWARE, either independently or jointly 73 | with Marc-Andre Cournoyer, such modifications and all rights 74 | associated therewith will be the exclusive property of Marc-Andre 75 | Cournoyer. You are granted the right to use such modifications as 76 | set forth in this agreement. You acknowledge that the SOFTWARE’s 77 | source code contains valuable and proprietary trade secrets of 78 | Marc-Andre Cournoyer. All individuals employed by or belonging to 79 | your entity 80 | agree to expend every effort to insure its confidentiality. You 81 | agree to assume full responsibility for such employees’ or 82 | contractors’ use, or misuse, of such disclosed source code as if 83 | it was your use. These obligations shall not apply to any 84 | information generally available to the public, independently 85 | developed or obtained without reliance on Marc-Andre Cournoyer 86 | information, or approved in writing for release by Marc-Andre 87 | Cournoyer without restriction. 88 | 89 | II. OTHER RIGHTS AND LIMITATIONS 90 | 91 | At no time may the SOFTWARE be used for development purposes by 92 | other individuals than the licensed developer(s). The SOFTWARE may 93 | not be distributed for use with solutions or PACKAGED PRODUCTS 94 | other than those developed by you. The SOFTWARE may not be 95 | distributed as part of products that have the same or 96 | substantially the same primary functionality. You are not allowed 97 | to resell, transfer, rent, lease, or sublicense the SOFTWARE and 98 | your associated rights. Under no circumstances shall you grant 99 | further redistribution rights to the end-users of your solution. 100 | You may not use the Marc-Andre Cournoyer product names, logos or 101 | trademarks to market YOUR SOFTWARE. You are not allowed to use, 102 | copy, modify, or merge copies of the SOFTWARE and any accompanying 103 | documents except as permitted in this LICENSE. You agree to 104 | indemnify, hold harmless, and defend Marc-Andre Cournoyer and its 105 | resellers from and against any and all claims or lawsuits 106 | including attorney's fees that arise or result from the use or 107 | distribution of YOUR SOFTWARE 108 | 109 | III. DELIVERY 110 | 111 | Marc-Andre Cournoyer shall deliver to LICENSEE a master copy of 112 | the SOFTWARE licensed hereunder in electronic files only. 113 | Documentation shall also be provided in electronic format. 114 | 115 | IV. UPGRADES 116 | 117 | You are eligible for free minor upgrades (e.g. v.1.5 to v.1.8), 118 | patches, and bug-fixes for the SOFTWARE, including source code. 119 | SOFTWARE labeled as an upgrade replaces and/or supplements (and 120 | may disable) the product that formed the basis for your 121 | eligibility for the upgrade. You may use the resulting upgraded 122 | product only in accordance with the terms of this LICENSE. 123 | 124 | You are entitled to receive all version updates for the SOFTWARE 125 | period of 1 (one) year. 126 | 127 | V. TERMINATION 128 | 129 | This LICENSE shall last as long as you use the SOFTWARE in 130 | compliance with this LICENSE. Marc-Andre Cournoyer may terminate 131 | this LICENSE ifyou fail to comply with any of the terms and 132 | conditions herein. In such event you agree to remove and destroy 133 | all copies of the SOFTWARE and any applicable source code. 134 | 135 | Marc-Andre Cournoyer reserves the right to discontinue at any time 136 | any product, shall it be offered individually or as a part of a 137 | product SUITE. However, Marc-Andre Cournoyer is obligated to 138 | provide the proper level of support for all discontinued products 139 | for a period of 1 (one) year after the date of discontinuance. 140 | 141 | VI. COPYRIGHT 142 | 143 | All title and copyrights in and to the SOFTWARE, the accompanying 144 | printed materials, and any copies of the SOFTWARE, and any 145 | trademarks or service marks of Marc-Andre Cournoyer are owned by 146 | Marc-Andre Cournoyer. All title and intellectual property rights 147 | in and to the content that may be accessed through use of the 148 | SOFTWARE is the property of the respective content owner and may 149 | be protected by applicable copyright or other intellectual 150 | property laws and treaties. This LICENSE grants you no rights to 151 | use such content. 152 | 153 | VII. LIMITED WARRANTY 154 | 155 | Marc-Andre Cournoyer warrants solely that the SOFTWARE will 156 | perform substantially in accordance with the accompanying written 157 | materials for a period of ninety (90) days. Marc-Andre Cournoyer 158 | does not warrant the use of the SOFTWARE will be uninterrupted or 159 | error free at all times and in all circumstances, nor that program 160 | errors will be corrected. This limited warranty shall not apply to 161 | any error or failure resulting from (i) machine error, (ii) 162 | LICENSEE's failure to follow operating instructions, (iii) 163 | negligence or accident, or (iv) modifications to the SOFTWARE by 164 | any person or entity other than Marc-Andre Cournoyer. In the event 165 | of a breach of warranty, LICENSEE's sole and exclusive remedy, is 166 | repair of all or any portion of the SOFTWARE. 167 | If such remedy fails of its essential purpose, LICENSEE's sole 168 | remedy and Marc-Andre Cournoyer's maximum liability shall be a 169 | refund of the paid purchase price for the defective SOFTWARE only. 170 | This limited warranty is only valid if Marc-Andre Cournoyer 171 | receives written notice of breach of warranty within thirty days 172 | after the warranty period expires. 173 | 174 | VIII. LIMITATION OF LIABILITY 175 | 176 | TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT 177 | WILL Marc-Andre Cournoyer BE LIABLE FOR ANY INDIRECT, SPECIAL, 178 | INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF OR 179 | INABILITY TO USE THE PRODUCT, INCLUDING, WITHOUT LIMITATION, 180 | DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR 181 | MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, 182 | EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF THE 183 | LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH 184 | THE CLAIM IS BASED. IN ANY CASE, Marc-Andre Cournoyer'S ENTIRE 185 | LIABILITY UNDER ANY PROVISION OF THIS AGREEMENT SHALL NOT EXCEED 186 | IN THE AGGREGATE THE SUM OF THE LICENSE FEES LICENSEE PAID TO 187 | Marc-Andre Cournoyer FOR THE PRODUCT GIVING RISE TO SUCH DAMAGES, 188 | NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY LIMITED 189 | REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION 190 | OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS 191 | EXCLUSION AND LIMITATION MAY NOT BE APPLICABLE. Marc-Andre 192 | Cournoyer IS NOT RESPONSIBLE FOR ANY LIABILITY ARISING OUT OF 193 | CONTENT PROVIDED BY LICENSEE OR A THIRD PARTY THAT IS ACCESSED 194 | THROUGH THE PRODUCT AND/OR ANY MATERIAL LINKED THROUGH SUCH 195 | CONTENT. ANY DATA INCLUDED IN A PRODUCT UPON SHIPMENT FROM 196 | Marc-Andre Cournoyer IS FOR TESTING USE ONLY AND Marc-Andre 197 | Cournoyer HEREBY DISCLAIMS ANY AND ALL LIABILITY ARISING 198 | THEREFROM. 199 | THE EXTENT OF Marc-Andre Cournoyer'S LIABILITY FOR THE LIMITED 200 | WARRANTY SECTION SHALL BE AS SET FORTH THEREIN. 201 | 202 | IX. MISCELLANEOUS 203 | 204 | This License will be governed by the law of the State of 205 | Washington, U.S.A. If any provision of this LICENSE is to be held 206 | unenforceable, such holding will not affect the validity of the 207 | other provisions hereof. Failure of a party to enforce any 208 | provision of this LICENSE shall not constitute or be construed as 209 | a waiver of such provision or of the right to enforce such 210 | provision. This License represents the entire understanding 211 | between the parties with respect to its subject matter. 212 | 213 | YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, THAT YOU 214 | UNDERSTAND THIS AGREEMENT, AND UNDERSTAND THAT BY CONTINUING THE 215 | INSTALLATION OF THE SOFTWARE PRODUCT, BY LOADING OR RUNNING THE 216 | SOFTWARE PRODUCT, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR 217 | COMPUTER HARD DRIVE, YOU AGREE TO BE BOUND BY THIS AGREEMENT'S 218 | TERMS AND CONDITIONS. YOU FURTHER AGREE THAT, EXCEPT FOR WRITTEN 219 | SEPARATE AGREEMENTS BETWEEN Marc-Andre Cournoyer AND YOU, THIS 220 | AGREEMENT IS A COMPLETE AND EXCLUSIVE STATEMENT OF THE RIGHTS AND 221 | LIABILITIES OF THE PARTIES. -------------------------------------------------------------------------------- /Book/book/code/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" 10 | require "nodes" 11 | 12 | class Parser < Racc::Parser 13 | 14 | module_eval(<<'...end grammar.y/module_eval...', 'grammar.y', 220) 15 | def parse(code, show_tokens=false) 16 | @tokens = Lexer.new.tokenize(code) # Tokenize the code using our lexer 17 | puts @tokens.inspect if show_tokens 18 | do_parse # Kickoff the parsing process 19 | end 20 | 21 | def next_token 22 | @tokens.shift 23 | end 24 | ...end grammar.y/module_eval... 25 | ##### State transition tables begin ### 26 | 27 | racc_action_table = [ 28 | 27, 25, 26, 16, 18, 19, 20, 21, 22, 23, 29 | 24, 27, 25, 26, 16, 18, 19, 20, 21, 22, 30 | 23, 24, 30, 72, 41, 42, 39, 40, 53, 15, 31 | 30, 17, 41, 42, 39, 40, 30, 16, 16, 30, 32 | 15, 74, 17, 27, 25, 26, 84, 18, 19, 20, 33 | 21, 22, 23, 24, 27, 25, 26, 45, 18, 19, 34 | 20, 21, 22, 23, 24, 17, 17, 30, 72, 41, 35 | 42, 47, 15, 68, 30, 82, 41, 42, 39, 40, 36 | 27, 25, 26, 15, 18, 19, 20, 21, 22, 23, 37 | 24, 27, 25, 26, 48, 18, 19, 20, 21, 22, 38 | 23, 24, 30, 28, 41, 42, 27, 25, 26, 15, 39 | 18, 19, 20, 21, 22, 23, 24, 27, 25, 26, 40 | 15, 18, 19, 20, 21, 22, 23, 24, 86, 79, 41 | 85, 78, 27, 25, 26, 15, 18, 19, 20, 21, 42 | 22, 23, 24, 27, 25, 26, 15, 18, 19, 20, 43 | 21, 22, 23, 24, 46, 49, 45, 51, 27, 25, 44 | 26, 15, 18, 19, 20, 21, 22, 23, 24, 27, 45 | 25, 26, 15, 18, 19, 20, 21, 22, 23, 24, 46 | 72, 88, nil, nil, 27, 25, 26, 15, 18, 19, 47 | 20, 21, 22, 23, 24, 27, 25, 26, 15, 18, 48 | 19, 20, 21, 22, 23, 24, nil, nil, nil, nil, 49 | 27, 25, 26, 15, 18, 19, 20, 21, 22, 23, 50 | 24, 27, 25, 26, 15, 18, 19, 20, 21, 22, 51 | 23, 24, nil, nil, nil, nil, 27, 25, 26, 15, 52 | 18, 19, 20, 21, 22, 23, 24, 27, 25, 26, 53 | 15, 18, 19, 20, 21, 22, 23, 24, nil, nil, 54 | nil, nil, 27, 25, 26, 15, 18, 19, 20, 21, 55 | 22, 23, 24, 27, 25, 26, 15, 18, 19, 20, 56 | 21, 22, 23, 24, nil, nil, nil, nil, 27, 25, 57 | 26, 15, 18, 19, 20, 21, 22, 23, 24, nil, 58 | nil, 30, 15, 41, 42, 39, 40, 35, 36, 37, 59 | 38, 33, 34, 32, 31, nil, nil, 15, 66, 72, 60 | nil, 30, nil, 41, 42, 39, 40, 35, 36, 37, 61 | 38, 33, 34, 32, 31, 30, nil, 41, 42, 39, 62 | 40, 35, 36, 37, 38, 33, 34, 32, 31, 30, 63 | nil, 41, 42, 39, 40, 35, 36, 37, 38, 33, 64 | 34, 32, 31, 30, nil, 41, 42, 39, 40, 35, 65 | 36, 37, 38, 33, 34, 32, 31, 30, nil, 41, 66 | 42, 39, 40, 35, 36, 37, 38, 33, 34, 32, 67 | 31, 30, nil, 41, 42, 39, 40, 35, 36, 37, 68 | 38, 33, 34, 32, 31, 30, nil, 41, 42, 39, 69 | 40, 35, 36, 37, 38, 33, 34, 32, 31, 30, 70 | nil, 41, 42, 39, 40, 35, 36, 37, 38, 33, 71 | 34, 32, 30, nil, 41, 42, 39, 40, 35, 36, 72 | 37, 38, 33, 34, 30, nil, 41, 42, 39, 40, 73 | 35, 36, 37, 38, 30, nil, 41, 42, 39, 40, 74 | 35, 36, 37, 38, 30, nil, 41, 42, 39, 40 ] 75 | 76 | racc_action_check = [ 77 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78 | 0, 72, 72, 72, 72, 72, 72, 72, 72, 72, 79 | 72, 72, 61, 48, 61, 61, 61, 61, 30, 0, 80 | 60, 0, 60, 60, 60, 60, 64, 80, 2, 65, 81 | 72, 48, 72, 45, 45, 45, 80, 45, 45, 45, 82 | 45, 45, 45, 45, 29, 29, 29, 53, 29, 29, 83 | 29, 29, 29, 29, 29, 80, 2, 62, 49, 62, 84 | 62, 24, 45, 45, 58, 74, 58, 58, 58, 58, 85 | 15, 15, 15, 29, 15, 15, 15, 15, 15, 15, 86 | 15, 40, 40, 40, 25, 40, 40, 40, 40, 40, 87 | 40, 40, 63, 1, 63, 63, 41, 41, 41, 15, 88 | 41, 41, 41, 41, 41, 41, 41, 47, 47, 47, 89 | 40, 47, 47, 47, 47, 47, 47, 47, 81, 69, 90 | 81, 69, 46, 46, 46, 41, 46, 46, 46, 46, 91 | 46, 46, 46, 27, 27, 27, 47, 27, 27, 27, 92 | 27, 27, 27, 27, 23, 26, 23, 28, 39, 39, 93 | 39, 46, 39, 39, 39, 39, 39, 39, 39, 79, 94 | 79, 79, 27, 79, 79, 79, 79, 79, 79, 79, 95 | 85, 86, nil, nil, 42, 42, 42, 39, 42, 42, 96 | 42, 42, 42, 42, 42, 38, 38, 38, 79, 38, 97 | 38, 38, 38, 38, 38, 38, nil, nil, nil, nil, 98 | 32, 32, 32, 42, 32, 32, 32, 32, 32, 32, 99 | 32, 33, 33, 33, 38, 33, 33, 33, 33, 33, 100 | 33, 33, nil, nil, nil, nil, 34, 34, 34, 32, 101 | 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 102 | 33, 35, 35, 35, 35, 35, 35, 35, nil, nil, 103 | nil, nil, 36, 36, 36, 34, 36, 36, 36, 36, 104 | 36, 36, 36, 37, 37, 37, 35, 37, 37, 37, 105 | 37, 37, 37, 37, nil, nil, nil, nil, 31, 31, 106 | 31, 36, 31, 31, 31, 31, 31, 31, 31, nil, 107 | nil, 43, 37, 43, 43, 43, 43, 43, 43, 43, 108 | 43, 43, 43, 43, 43, nil, nil, 31, 43, 50, 109 | nil, 50, nil, 50, 50, 50, 50, 50, 50, 50, 110 | 50, 50, 50, 50, 50, 3, nil, 3, 3, 3, 111 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 52, 112 | nil, 52, 52, 52, 52, 52, 52, 52, 52, 52, 113 | 52, 52, 52, 83, nil, 83, 83, 83, 83, 83, 114 | 83, 83, 83, 83, 83, 83, 83, 70, nil, 70, 115 | 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 116 | 70, 71, nil, 71, 71, 71, 71, 71, 71, 71, 117 | 71, 71, 71, 71, 71, 67, nil, 67, 67, 67, 118 | 67, 67, 67, 67, 67, 67, 67, 67, 67, 54, 119 | nil, 54, 54, 54, 54, 54, 54, 54, 54, 54, 120 | 54, 54, 55, nil, 55, 55, 55, 55, 55, 55, 121 | 55, 55, 55, 55, 57, nil, 57, 57, 57, 57, 122 | 57, 57, 57, 57, 56, nil, 56, 56, 56, 56, 123 | 56, 56, 56, 56, 59, nil, 59, 59, 59, 59 ] 124 | 125 | racc_action_pointer = [ 126 | -2, 103, 33, 320, nil, nil, nil, nil, nil, nil, 127 | nil, nil, nil, nil, nil, 78, nil, nil, nil, nil, 128 | nil, nil, nil, 125, 42, 83, 143, 141, 157, 52, 129 | 17, 286, 208, 219, 234, 245, 260, 271, 193, 156, 130 | 89, 104, 182, 286, nil, 41, 130, 115, 10, 55, 131 | 306, nil, 334, 26, 404, 417, 439, 429, 59, 449, 132 | 15, 7, 52, 87, 21, 24, nil, 390, nil, 99, 133 | 362, 376, 9, nil, 64, nil, nil, nil, nil, 167, 134 | 32, 98, nil, 348, nil, 167, 170, nil, nil ] 135 | 136 | racc_action_default = [ 137 | -1, -56, -2, -3, -6, -7, -8, -9, -10, -11, 138 | -12, -13, -14, -15, -16, -56, -18, -19, -20, -21, 139 | -22, -23, -24, -46, -44, -56, -56, -56, -56, -5, 140 | -56, -56, -56, -56, -56, -56, -56, -56, -56, -56, 141 | -56, -56, -56, -56, -25, -56, -56, -56, -56, -56, 142 | -56, 89, -4, -27, -32, -33, -34, -35, -36, -37, 143 | -38, -39, -40, -41, -42, -43, -17, -30, -28, -56, 144 | -47, -45, -56, -49, -51, -54, -55, -26, -29, -56, 145 | -56, -56, -52, -31, -48, -56, -56, -50, -53 ] 146 | 147 | racc_goto_table = [ 148 | 29, 2, 43, 73, 75, 76, 44, 1, 69, 81, 149 | nil, nil, nil, nil, 50, nil, 52, nil, 54, 55, 150 | 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 151 | nil, nil, 67, 70, 71, nil, 77, nil, nil, nil, 152 | 87, nil, nil, nil, nil, nil, nil, nil, nil, nil, 153 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 154 | nil, nil, nil, nil, nil, nil, 83, nil, nil, nil, 155 | nil, nil, nil, 80, nil, nil, nil, nil, 29 ] 156 | 157 | racc_goto_check = [ 158 | 4, 2, 3, 17, 17, 17, 15, 1, 16, 18, 159 | nil, nil, nil, nil, 3, nil, 3, nil, 3, 3, 160 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 161 | nil, nil, 3, 3, 3, nil, 15, nil, nil, nil, 162 | 17, nil, nil, nil, nil, nil, nil, nil, nil, nil, 163 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 164 | nil, nil, nil, nil, nil, nil, 3, nil, nil, nil, 165 | nil, nil, nil, 2, nil, nil, nil, nil, 4 ] 166 | 167 | racc_goto_pointer = [ 168 | nil, 7, 1, -13, -2, nil, nil, nil, nil, nil, 169 | nil, nil, nil, nil, nil, -17, -37, -45, -65 ] 170 | 171 | racc_goto_default = [ 172 | nil, nil, nil, 3, 4, 5, 6, 7, 8, 9, 173 | 10, 11, 12, 13, 14, nil, nil, nil, nil ] 174 | 175 | racc_reduce_table = [ 176 | 0, 0, :racc_error, 177 | 0, 35, :_reduce_1, 178 | 1, 35, :_reduce_2, 179 | 1, 36, :_reduce_3, 180 | 3, 36, :_reduce_4, 181 | 2, 36, :_reduce_5, 182 | 1, 36, :_reduce_6, 183 | 1, 37, :_reduce_none, 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 | 3, 37, :_reduce_17, 194 | 1, 38, :_reduce_none, 195 | 1, 38, :_reduce_none, 196 | 1, 39, :_reduce_20, 197 | 1, 39, :_reduce_21, 198 | 1, 39, :_reduce_22, 199 | 1, 39, :_reduce_23, 200 | 1, 39, :_reduce_24, 201 | 2, 40, :_reduce_25, 202 | 4, 40, :_reduce_26, 203 | 3, 40, :_reduce_27, 204 | 2, 49, :_reduce_28, 205 | 3, 49, :_reduce_29, 206 | 1, 50, :_reduce_30, 207 | 3, 50, :_reduce_31, 208 | 3, 41, :_reduce_32, 209 | 3, 41, :_reduce_33, 210 | 3, 41, :_reduce_34, 211 | 3, 41, :_reduce_35, 212 | 3, 41, :_reduce_36, 213 | 3, 41, :_reduce_37, 214 | 3, 41, :_reduce_38, 215 | 3, 41, :_reduce_39, 216 | 3, 41, :_reduce_40, 217 | 3, 41, :_reduce_41, 218 | 3, 41, :_reduce_42, 219 | 3, 41, :_reduce_43, 220 | 1, 42, :_reduce_44, 221 | 3, 43, :_reduce_45, 222 | 1, 44, :_reduce_46, 223 | 3, 45, :_reduce_47, 224 | 3, 51, :_reduce_48, 225 | 3, 46, :_reduce_49, 226 | 6, 46, :_reduce_50, 227 | 0, 52, :_reduce_51, 228 | 1, 52, :_reduce_52, 229 | 3, 52, :_reduce_53, 230 | 3, 47, :_reduce_54, 231 | 3, 48, :_reduce_55 ] 232 | 233 | racc_reduce_n = 56 234 | 235 | racc_shift_n = 89 236 | 237 | racc_token_table = { 238 | false => 0, 239 | :error => 1, 240 | :IF => 2, 241 | :DEF => 3, 242 | :CLASS => 4, 243 | :NEWLINE => 5, 244 | :NUMBER => 6, 245 | :STRING => 7, 246 | :TRUE => 8, 247 | :FALSE => 9, 248 | :NIL => 10, 249 | :IDENTIFIER => 11, 250 | :CONSTANT => 12, 251 | :INDENT => 13, 252 | :DEDENT => 14, 253 | "." => 15, 254 | "!" => 16, 255 | "*" => 17, 256 | "/" => 18, 257 | "+" => 19, 258 | "-" => 20, 259 | ">" => 21, 260 | ">=" => 22, 261 | "<" => 23, 262 | "<=" => 24, 263 | "==" => 25, 264 | "!=" => 26, 265 | "&&" => 27, 266 | "||" => 28, 267 | "=" => 29, 268 | "," => 30, 269 | "(" => 31, 270 | ")" => 32, 271 | ";" => 33 } 272 | 273 | racc_nt_base = 34 274 | 275 | racc_use_result_var = true 276 | 277 | Racc_arg = [ 278 | racc_action_table, 279 | racc_action_check, 280 | racc_action_default, 281 | racc_action_pointer, 282 | racc_goto_table, 283 | racc_goto_check, 284 | racc_goto_default, 285 | racc_goto_pointer, 286 | racc_nt_base, 287 | racc_reduce_table, 288 | racc_token_table, 289 | racc_shift_n, 290 | racc_reduce_n, 291 | racc_use_result_var ] 292 | 293 | Racc_token_to_s_table = [ 294 | "$end", 295 | "error", 296 | "IF", 297 | "DEF", 298 | "CLASS", 299 | "NEWLINE", 300 | "NUMBER", 301 | "STRING", 302 | "TRUE", 303 | "FALSE", 304 | "NIL", 305 | "IDENTIFIER", 306 | "CONSTANT", 307 | "INDENT", 308 | "DEDENT", 309 | "\".\"", 310 | "\"!\"", 311 | "\"*\"", 312 | "\"/\"", 313 | "\"+\"", 314 | "\"-\"", 315 | "\">\"", 316 | "\">=\"", 317 | "\"<\"", 318 | "\"<=\"", 319 | "\"==\"", 320 | "\"!=\"", 321 | "\"&&\"", 322 | "\"||\"", 323 | "\"=\"", 324 | "\",\"", 325 | "\"(\"", 326 | "\")\"", 327 | "\";\"", 328 | "$start", 329 | "Program", 330 | "Expressions", 331 | "Expression", 332 | "Terminator", 333 | "Literal", 334 | "Call", 335 | "Operator", 336 | "GetConstant", 337 | "SetConstant", 338 | "GetLocal", 339 | "SetLocal", 340 | "Def", 341 | "Class", 342 | "If", 343 | "Arguments", 344 | "ArgList", 345 | "Block", 346 | "ParamList" ] 347 | 348 | Racc_debug_parser = false 349 | 350 | ##### State transition tables end ##### 351 | 352 | # reduce 0 omitted 353 | 354 | module_eval(<<'.,.,', 'grammar.y', 49) 355 | def _reduce_1(val, _values, result) 356 | result = Nodes.new([]) 357 | result 358 | end 359 | .,., 360 | 361 | module_eval(<<'.,.,', 'grammar.y', 50) 362 | def _reduce_2(val, _values, result) 363 | result = val[0] 364 | result 365 | end 366 | .,., 367 | 368 | module_eval(<<'.,.,', 'grammar.y', 67) 369 | def _reduce_3(val, _values, result) 370 | result = Nodes.new(val) 371 | result 372 | end 373 | .,., 374 | 375 | module_eval(<<'.,.,', 'grammar.y', 68) 376 | def _reduce_4(val, _values, result) 377 | result = val[0] << val[2] 378 | result 379 | end 380 | .,., 381 | 382 | module_eval(<<'.,.,', 'grammar.y', 69) 383 | def _reduce_5(val, _values, result) 384 | result = val[0] 385 | result 386 | end 387 | .,., 388 | 389 | module_eval(<<'.,.,', 'grammar.y', 70) 390 | def _reduce_6(val, _values, result) 391 | result = Nodes.new([]) 392 | result 393 | end 394 | .,., 395 | 396 | # reduce 7 omitted 397 | 398 | # reduce 8 omitted 399 | 400 | # reduce 9 omitted 401 | 402 | # reduce 10 omitted 403 | 404 | # reduce 11 omitted 405 | 406 | # reduce 12 omitted 407 | 408 | # reduce 13 omitted 409 | 410 | # reduce 14 omitted 411 | 412 | # reduce 15 omitted 413 | 414 | # reduce 16 omitted 415 | 416 | module_eval(<<'.,.,', 'grammar.y', 85) 417 | def _reduce_17(val, _values, result) 418 | result = val[1] 419 | result 420 | end 421 | .,., 422 | 423 | # reduce 18 omitted 424 | 425 | # reduce 19 omitted 426 | 427 | module_eval(<<'.,.,', 'grammar.y', 103) 428 | def _reduce_20(val, _values, result) 429 | result = NumberNode.new(val[0]) 430 | result 431 | end 432 | .,., 433 | 434 | module_eval(<<'.,.,', 'grammar.y', 104) 435 | def _reduce_21(val, _values, result) 436 | result = StringNode.new(val[0]) 437 | result 438 | end 439 | .,., 440 | 441 | module_eval(<<'.,.,', 'grammar.y', 105) 442 | def _reduce_22(val, _values, result) 443 | result = TrueNode.new 444 | result 445 | end 446 | .,., 447 | 448 | module_eval(<<'.,.,', 'grammar.y', 106) 449 | def _reduce_23(val, _values, result) 450 | result = FalseNode.new 451 | result 452 | end 453 | .,., 454 | 455 | module_eval(<<'.,.,', 'grammar.y', 107) 456 | def _reduce_24(val, _values, result) 457 | result = NilNode.new 458 | result 459 | end 460 | .,., 461 | 462 | module_eval(<<'.,.,', 'grammar.y', 119) 463 | def _reduce_25(val, _values, result) 464 | result = CallNode.new(nil, val[0], val[1]) 465 | result 466 | end 467 | .,., 468 | 469 | module_eval(<<'.,.,', 'grammar.y', 121) 470 | def _reduce_26(val, _values, result) 471 | result = CallNode.new(val[0], val[2], val[3]) 472 | result 473 | end 474 | .,., 475 | 476 | module_eval(<<'.,.,', 'grammar.y', 122) 477 | def _reduce_27(val, _values, result) 478 | result = CallNode.new(val[0], val[2], []) 479 | result 480 | end 481 | .,., 482 | 483 | module_eval(<<'.,.,', 'grammar.y', 126) 484 | def _reduce_28(val, _values, result) 485 | result = [] 486 | result 487 | end 488 | .,., 489 | 490 | module_eval(<<'.,.,', 'grammar.y', 127) 491 | def _reduce_29(val, _values, result) 492 | result = val[1] 493 | result 494 | end 495 | .,., 496 | 497 | module_eval(<<'.,.,', 'grammar.y', 131) 498 | def _reduce_30(val, _values, result) 499 | result = val 500 | result 501 | end 502 | .,., 503 | 504 | module_eval(<<'.,.,', 'grammar.y', 132) 505 | def _reduce_31(val, _values, result) 506 | result = val[0] << val[2] 507 | result 508 | end 509 | .,., 510 | 511 | module_eval(<<'.,.,', 'grammar.y', 143) 512 | def _reduce_32(val, _values, result) 513 | result = CallNode.new(val[0], val[1], [val[2]]) 514 | result 515 | end 516 | .,., 517 | 518 | module_eval(<<'.,.,', 'grammar.y', 144) 519 | def _reduce_33(val, _values, result) 520 | result = CallNode.new(val[0], val[1], [val[2]]) 521 | result 522 | end 523 | .,., 524 | 525 | module_eval(<<'.,.,', 'grammar.y', 145) 526 | def _reduce_34(val, _values, result) 527 | result = CallNode.new(val[0], val[1], [val[2]]) 528 | result 529 | end 530 | .,., 531 | 532 | module_eval(<<'.,.,', 'grammar.y', 146) 533 | def _reduce_35(val, _values, result) 534 | result = CallNode.new(val[0], val[1], [val[2]]) 535 | result 536 | end 537 | .,., 538 | 539 | module_eval(<<'.,.,', 'grammar.y', 147) 540 | def _reduce_36(val, _values, result) 541 | result = CallNode.new(val[0], val[1], [val[2]]) 542 | result 543 | end 544 | .,., 545 | 546 | module_eval(<<'.,.,', 'grammar.y', 148) 547 | def _reduce_37(val, _values, result) 548 | result = CallNode.new(val[0], val[1], [val[2]]) 549 | result 550 | end 551 | .,., 552 | 553 | module_eval(<<'.,.,', 'grammar.y', 149) 554 | def _reduce_38(val, _values, result) 555 | result = CallNode.new(val[0], val[1], [val[2]]) 556 | result 557 | end 558 | .,., 559 | 560 | module_eval(<<'.,.,', 'grammar.y', 150) 561 | def _reduce_39(val, _values, result) 562 | result = CallNode.new(val[0], val[1], [val[2]]) 563 | result 564 | end 565 | .,., 566 | 567 | module_eval(<<'.,.,', 'grammar.y', 151) 568 | def _reduce_40(val, _values, result) 569 | result = CallNode.new(val[0], val[1], [val[2]]) 570 | result 571 | end 572 | .,., 573 | 574 | module_eval(<<'.,.,', 'grammar.y', 152) 575 | def _reduce_41(val, _values, result) 576 | result = CallNode.new(val[0], val[1], [val[2]]) 577 | result 578 | end 579 | .,., 580 | 581 | module_eval(<<'.,.,', 'grammar.y', 153) 582 | def _reduce_42(val, _values, result) 583 | result = CallNode.new(val[0], val[1], [val[2]]) 584 | result 585 | end 586 | .,., 587 | 588 | module_eval(<<'.,.,', 'grammar.y', 154) 589 | def _reduce_43(val, _values, result) 590 | result = CallNode.new(val[0], val[1], [val[2]]) 591 | result 592 | end 593 | .,., 594 | 595 | module_eval(<<'.,.,', 'grammar.y', 159) 596 | def _reduce_44(val, _values, result) 597 | result = GetConstantNode.new(val[0]) 598 | result 599 | end 600 | .,., 601 | 602 | module_eval(<<'.,.,', 'grammar.y', 163) 603 | def _reduce_45(val, _values, result) 604 | result = SetConstantNode.new(val[0], val[2]) 605 | result 606 | end 607 | .,., 608 | 609 | module_eval(<<'.,.,', 'grammar.y', 167) 610 | def _reduce_46(val, _values, result) 611 | result = GetLocalNode.new(val[0]) 612 | result 613 | end 614 | .,., 615 | 616 | module_eval(<<'.,.,', 'grammar.y', 171) 617 | def _reduce_47(val, _values, result) 618 | result = SetLocalNode.new(val[0], val[2]) 619 | result 620 | end 621 | .,., 622 | 623 | module_eval(<<'.,.,', 'grammar.y', 183) 624 | def _reduce_48(val, _values, result) 625 | result = val[1] 626 | result 627 | end 628 | .,., 629 | 630 | module_eval(<<'.,.,', 'grammar.y', 189) 631 | def _reduce_49(val, _values, result) 632 | result = DefNode.new(val[1], [], val[2]) 633 | result 634 | end 635 | .,., 636 | 637 | module_eval(<<'.,.,', 'grammar.y', 191) 638 | def _reduce_50(val, _values, result) 639 | result = DefNode.new(val[1], val[3], val[5]) 640 | result 641 | end 642 | .,., 643 | 644 | module_eval(<<'.,.,', 'grammar.y', 195) 645 | def _reduce_51(val, _values, result) 646 | result = [] 647 | result 648 | end 649 | .,., 650 | 651 | module_eval(<<'.,.,', 'grammar.y', 196) 652 | def _reduce_52(val, _values, result) 653 | result = val 654 | result 655 | end 656 | .,., 657 | 658 | module_eval(<<'.,.,', 'grammar.y', 197) 659 | def _reduce_53(val, _values, result) 660 | result = val[0] << val[2] 661 | result 662 | end 663 | .,., 664 | 665 | module_eval(<<'.,.,', 'grammar.y', 203) 666 | def _reduce_54(val, _values, result) 667 | result = ClassNode.new(val[1], val[2]) 668 | result 669 | end 670 | .,., 671 | 672 | module_eval(<<'.,.,', 'grammar.y', 208) 673 | def _reduce_55(val, _values, result) 674 | result = IfNode.new(val[1], val[2]) 675 | result 676 | end 677 | .,., 678 | 679 | def _reduce_none(val, _values, result) 680 | val[0] 681 | end 682 | 683 | end # class Parser 684 | --------------------------------------------------------------------------------