Skip to content
Browse files

initial commit

  • Loading branch information...
0 parents commit 55605294784e91c77e2f10e21d58254660f67164 Julien Desrosiers committed
Showing with 1,340 additions and 0 deletions.
  1. +75 −0 bracket_lexer.rb
  2. +18 −0 example.rb
  3. +145 −0 grammar.y
  4. +120 −0 lexer.rb
  5. +20 −0 lexer_test.rb
  6. +173 −0 nodes.rb
  7. +456 −0 parser.rb
  8. +35 −0 parser_output.txt
  9. +40 −0 parser_test.rb
  10. +146 −0 runtime.rb
  11. +112 −0 spartan.rb
75 bracket_lexer.rb
@@ -0,0 +1,75 @@
+class BracketLexer
+ KEYWORDS = ["def", "class", "if", "else", "true", "false", "nil"]
+
+ def tokenize(code)
+ code.chomp!
+ i = 0
+ tokens = []
+ current_indent = 0
+ indent_stack = []
+
+ while i < code.size
+ chunk = code[i..-1]
+
+ if identifier = chunk[/\A([a-z]\w*)/, 1]
+ if KEYWORDS.include?(identifier)
+ tokens << [identifier.upcase.to_sym, identifier]
+ else
+ tokens << [:IDENTIFIER, identifier]
+ end
+ i += identifier.size
+
+ elsif constant = chunk[/\A([A-Z]\w*)/, 1]
+ tokens << [:CONSTANT, constant]
+ i += constant.size
+
+ elsif number = chunk[/\A([0-9]+)/, 1]
+ tokens << [:NUMBER, number.to_i]
+ i += number.size
+
+ elsif string = chunk[/\A"(.*?)"/, 1]
+ tokens << [:STRING, string]
+ i += string.size + 2
+
+ # All indentation magic code was removed and only this elsif
+ # was added.
+ elsif chunk.match(/\A\n+/)
+ tokens << [:NEWLINE, "\n"]
+ i += 1
+
+ elsif chunk.match(/\A /)
+ i += 1
+
+ else
+ value = chunk[0,1]
+ tokens << [value, value]
+ i += 1
+
+ end
+
+ end
+
+ tokens
+ end
+end
+
+code = <<-EOS
+if 1 {
+ print "..."
+ if false {
+ pass
+ }
+ print "done!"
+}
+print "The End"
+EOS
+
+p BracketLexer.new.tokenize(code)
+# [[:IF, "if"], [:NUMBER, 1], ["{", "{"], [:NEWLINE, "\n"],
+# [:IDENTIFIER, "print"], [:STRING, "..."], [:NEWLINE, "\n"],
+# [:IF, "if"], [:FALSE, "false"], ["{", "{"], [:NEWLINE, "\n"],
+# [:IDENTIFIER, "pass"], [:NEWLINE, "\n"],
+# ["}", "}"], [:NEWLINE, "\n"],
+# [:IDENTIFIER, "print"], [:STRING, "done!"], [:NEWLINE, "\n"],
+# ["}", "}"], [:NEWLINE, "\n"],
+# [:IDENTIFIER, "print"], [:STRING, "The End"]]
18 example.rb
@@ -0,0 +1,18 @@
+require "parser"
+require "runtime"
+
+code = <<-EOS
+class Awesome:
+ def does_it_work:
+ "yeah!"
+
+awesome_object = Awesome.new
+if awesome_object:
+ print("awesome_object.does_it_work: ")
+ print(awesome_object.does_it_work)
+else:
+ print("Something is wrong...")
+EOS
+
+nodes = Parser.new.parse(code)
+nodes.eval(Runtime)
145 grammar.y
@@ -0,0 +1,145 @@
+class Parser
+
+# Declare tokens produced by the lexer
+token IF ELSE
+token DEF
+token CLASS
+token NEWLINE
+token NUMBER
+token STRING
+token TRUE FALSE NIL
+token IDENTIFIER
+token CONSTANT
+token INDENT DEDENT
+
+rule
+ # All rules are declared in this format:
+ #
+ # RuleName:
+ # OtherRule TOKEN AnotherRule { code to run when this matches }
+ # | OtherRule { ... }
+ # ;
+ #
+ # In the code section ({...} on the right):
+ # - Assign to "result" the value returned by the rule.
+ # - Use val[index of expression] to reference expressions on the left.
+
+
+ # All parsing will end in this rule, being the trunk of the AST.
+ Root:
+ /* nothing */ { result = Nodes.new([]) }
+ | Expressions { result = val[0] }
+ ;
+
+ # Any list of expressions, class or method body.
+ Expressions:
+ Expression { result = Nodes.new(val) }
+ | Expressions Terminator Expression { result = val[0] << val[2] }
+ # To ignore trailing line breaks
+ | Expressions Terminator { result = Nodes.new([val[0]]) }
+ ;
+
+ # All types of expressions in our language
+ Expression:
+ Literal
+ | Call
+ | Constant
+ | Assign
+ | Def
+ | Class
+ | If
+ ;
+
+ # All tokens that can terminate an expression
+ Terminator:
+ NEWLINE
+ | ";"
+ ;
+
+ Literal:
+ NUMBER { result = LiteralNode.new(val[0]) }
+ | STRING { result = LiteralNode.new(val[0]) }
+ | TRUE { result = LiteralNode.new(true) }
+ | FALSE { result = LiteralNode.new(false) }
+ | NIL { result = LiteralNode.new(nil) }
+ ;
+
+ # A method call
+ Call:
+ # method
+ IDENTIFIER { result = CallNode.new(nil, val[0]) }
+ # method(arguments)
+ | IDENTIFIER "(" ArgList ")" { result = CallNode.new(nil, val[0], val[2]) }
+ # receiver.method
+ | Expression "." IDENTIFIER { result = CallNode.new(val[0], val[2]) }
+ # receiver.method(arguments)
+ | Expression "."
+ IDENTIFIER "(" ArgList ")" { result = CallNode.new(val[0], val[2], val[4]) }
+ ;
+
+ ArgList:
+ /* nothing */ { result = [] }
+ | Expression { result = val }
+ | ArgList "," Expression { result = val[0] << val[2] }
+ ;
+
+ Constant:
+ CONSTANT { result = GetConstantNode.new(val[0]) }
+ ;
+
+ # Assignation to variables or contants
+ Assign:
+ IDENTIFIER "=" Expression { result = SetLocalNode.new(val[0], val[2]) }
+ | CONSTANT "=" Expression { result = SetConstantNode.new(val[0], val[2]) }
+ ;
+
+ # Method definition
+ Def:
+ DEF IDENTIFIER Block { result = DefNode.new(val[1], [], val[2]) }
+ | DEF IDENTIFIER
+ "(" ParamList ")" Block { result = DefNode.new(val[1], val[3], val[5]) }
+ ;
+
+ ParamList:
+ /* nothing */ { result = [] }
+ | IDENTIFIER { result = val }
+ | ParamList "," IDENTIFIER { result = val[0] << val[2] }
+ ;
+
+ # Class definition
+ Class:
+ CLASS CONSTANT Block { result = ClassNode.new(val[1], val[2]) }
+ ;
+
+ # if and if-else block
+ If:
+ IF Expression Block { result = IfNode.new(val[1], val[2]) }
+ | IF Expression Block NEWLINE
+ ELSE Block { result = IfNode.new(val[1], val[2], val[5]) }
+ ;
+
+ # A block of indented code. You see here that all the hard work was done
+ # by the lexer.
+ Block:
+ INDENT Expressions DEDENT { result = val[1] }
+ # If you don't like indentation you could replace the previous rule with
+ # the following one do seperate blocks w/ "{" ... "}".
+ # (You'll need remove the indentation magic section in the lexer too)
+ # "{" Expressions "}" { replace = val[1] }
+ ;
+end
+
+---- header
+ require "lexer"
+ require "nodes"
+
+---- inner
+ def parse(code, show_tokens=false)
+ @tokens = Lexer.new.tokenize(code)
+ puts @tokens.inspect if show_tokens
+ do_parse
+ end
+
+ def next_token
+ @tokens.shift
+ end
120 lexer.rb
@@ -0,0 +1,120 @@
+class Lexer
+ KEYWORDS = ["def", "class", "if", "else", "true", "false", "nil"]
+
+ def tokenize(code)
+ # Cleanup code by remove extra line breaks
+ code.chomp!
+
+ # Current character position we're parsing
+ i = 0
+
+ # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
+ tokens = []
+
+ # Current indent level is the number of spaces in the last indent.
+ current_indent = 0
+ # We keep track of the indentation levels we are in so
+ # that when we dedent, we can check if we're on the
+ # correct level.
+ indent_stack = []
+
+ # This is how to implement a very simple scanner.
+ # Scan one caracter at the time until you find something to parse.
+ while i < code.size
+ chunk = code[i..-1]
+
+ # Matching standard tokens.
+ #
+ # Matching if, print, method names, etc.
+ if identifier = chunk[/\A([a-z]\w*)/, 1]
+ # Keywords are special identifiers tagged with their own
+ # name, 'if' will result in an [:IF, "if"] token
+ if KEYWORDS.include?(identifier)
+ tokens << [identifier.upcase.to_sym, identifier]
+ # Non-keyword identifiers include method and variable
+ # names.
+ else
+ tokens << [:IDENTIFIER, identifier]
+ end
+ # skip what we just parsed
+ i += identifier.size
+
+ # Matching class names and constants.
+ elsif constant = chunk[/\A([A-Z]\w*)/, 1]
+ tokens << [:CONSTANT, constant]
+ i += constant.size
+
+ elsif number = chunk[/\A([0-9]+)/, 1]
+ tokens << [:NUMBER, number.to_i]
+ i += number.size
+
+ elsif string = chunk[/\A"(.*?)"/, 1]
+ tokens << [:STRING, string]
+ i += string.size + 2
+
+ # Here's the indentation magic!
+ #
+ # We have to take care of 3 cases:
+ #
+ # if true: # 1) the block is created
+ # line 1
+ # line 2 # 2) new line inside a block
+ # continue # 3) dedent
+ #
+ # This elsif takes care of the first case.
+ # The number of spaces will determine the indent level.
+ elsif indent = chunk[/\A\:\n( +)/m, 1]
+ # When we create a new block we expect the indent level
+ # to go up.
+ if indent.size <= current_indent
+ raise "Bad indent level, got #{indent.size} indents, " +
+ "expected > #{current_indent}"
+ end
+ # Adjust the current indentation level.
+ current_indent = indent.size
+ indent_stack.push(current_indent)
+ tokens << [:INDENT, indent.size]
+ i += indent.size + 2
+
+ # This one takes care of cases 2 and 3.
+ # We stay in the same block if the indent level is the
+ # same as current_indent, or close a block, if it is lower.
+ elsif indent = chunk[/\A\n( *)/m, 1]
+ if indent.size < current_indent
+ indent_stack.pop
+ current_indent = indent_stack.first || 0
+ tokens << [:DEDENT, indent.size]
+ tokens << [:NEWLINE, "\n"]
+ elsif indent.size == current_indent
+ # Nothing to do, we're still in the same block
+ tokens << [:NEWLINE, "\n"]
+ else # indent.size > current_indent
+ # Cannot increase indent level without using ":", so
+ # this is an error.
+ raise "Missing ':'"
+ end
+ i += indent.size + 1
+
+ # Ignore whitespace
+ elsif chunk.match(/\A /)
+ i += 1
+
+ # We treat all other single characters as a token.
+ # Eg.: ( ) , . !
+ else
+ value = chunk[0,1]
+ tokens << [value, value]
+ i += 1
+
+ end
+
+ end
+
+ # Close all open blocks
+ while indent = indent_stack.pop
+ tokens << [:DEDENT, indent_stack.first || 0]
+ end
+
+ tokens
+ end
+end
20 lexer_test.rb
@@ -0,0 +1,20 @@
+require "lexer"
+
+code = <<-EOS
+if 1:
+ print "..."
+ if false:
+ pass
+ print "done!"
+print "The End"
+EOS
+
+p Lexer.new.tokenize(code)
+# [[:IF, "if"], [:NUMBER, 1],
+# [:INDENT, 2], [:IDENTIFIER, "print"], [:STRING, "..."], [:NEWLINE, "\n"],
+# [:IF, "if"], [:IDENTIFIER, "false"],
+# [:INDENT, 4], [:IDENTIFIER, "pass"],
+# [:DEDENT, 2], [:NEWLINE, "\n"],
+# [:IDENTIFIER, "print"], [:STRING, "done!"],
+# [:DEDENT, 0], [:NEWLINE, "\n"],
+# [:IDENTIFIER, "print"], [:STRING, "The End"]]
173 nodes.rb
@@ -0,0 +1,173 @@
+# Collection of nodes each one representing an expression.
+class Nodes
+ def initialize(nodes)
+ @nodes = nodes
+ end
+
+ def <<(node)
+ @nodes << node
+ self
+ end
+
+ # This method is the "interpreter" part of our language.
+ # All nodes know how to eval itself and returns the result
+ # of its evaluation.
+ # The "context" variable is the environment in which the node
+ # is evaluated (local variables, current class, etc.).
+ def eval(context)
+ # The last value evaluated in a method is the return value.
+ @nodes.map { |node| node.eval(context) }.last
+ end
+end
+
+# Literals are static values that have a Ruby representation,
+# eg.: a string, a number, true, false, nil, etc.
+class LiteralNode
+ def initialize(value)
+ @value = value
+ end
+
+ def eval(context)
+ case @value
+ when Numeric
+ Runtime["Number"].new_value(@value)
+ when String
+ Runtime["String"].new_value(@value)
+ when TrueClass
+ Runtime["true"]
+ when FalseClass
+ Runtime["false"]
+ when NilClass
+ Runtime["nil"]
+ else
+ raise "Unknown literal type: " + @value.class.name
+ end
+ end
+end
+
+# Node of a method call or local variable access,
+# can take any of these forms:
+#
+# method # this form can also be a local variable
+# method(argument1, argument2)
+# receiver.method
+# receiver.method(argument1, argument2)
+#
+class CallNode
+ def initialize(receiver, method, arguments=[])
+ @receiver = receiver
+ @method = method
+ @arguments = arguments
+ end
+
+ def eval(context)
+ # If there's no receiver and the method name is
+ # the name of a local variable, then it's a local
+ # variable access.
+ # This trick allows us to skip the () when calling
+ # a method.
+ if @receiver.nil? && context.locals[@method]
+ context.locals[@method]
+
+ # Method call
+ else
+ # In case there's no receiver we default to self
+ # So that calling "print" is like "self.print".
+ if @receiver
+ receiver = @receiver.eval(context)
+ else
+ receiver = context.current_self
+ end
+ arguments = @arguments.map { |arg| arg.eval(context) }
+ receiver.call(@method, arguments)
+ end
+ end
+end
+
+# Retreiving the value of a constant.
+class GetConstantNode
+ def initialize(name)
+ @name = name
+ end
+
+ def eval(context)
+ context[@name]
+ end
+end
+
+# Setting the value of a constant.
+class SetConstantNode
+ def initialize(name, value)
+ @name = name
+ @value = value
+ end
+
+ def eval(context)
+ context[@name] = @value.eval(context)
+ end
+end
+
+# Setting the value of a local variable.
+class SetLocalNode
+ def initialize(name, value)
+ @name = name
+ @value = value
+ end
+
+ def eval(context)
+ context.locals[@name] = @value.eval(context)
+ end
+end
+
+# Method definition.
+class DefNode
+ def initialize(name, params, body)
+ @name = name
+ @params = params
+ @body = body
+ end
+
+ def eval(context)
+ context.current_class.awesome_methods[@name] = AwesomeMethod.new(@params, @body)
+ end
+end
+
+# Class definition.
+class ClassNode
+ def initialize(name, body)
+ @name = name
+ @body = body
+ end
+
+ def eval(context)
+ # Create the class and put it's value in a constant.
+ awesome_class = AwesomeClass.new
+ context[@name] = awesome_class
+
+ # Evaluate the body of the class in its context.
+ @body.eval(Context.new(awesome_class, awesome_class))
+
+ awesome_class
+ end
+end
+
+# if-else control structure.
+# Look at this node if you want to implement other
+# control structures like while, for, loop, etc.
+class IfNode
+ def initialize(condition, body, else_body=nil)
+ @condition = condition
+ @body = body
+ @else_body = else_body
+ end
+
+ def eval(context)
+ # We turn the condition node into a Ruby value
+ # to use Ruby's "if" control structure.
+ if @condition.eval(context).ruby_value
+ @body.eval(context)
+ elsif @else_body
+ @else_body.eval(context)
+ end
+ end
+end
456 parser.rb
@@ -0,0 +1,456 @@
+#
+# DO NOT MODIFY!!!!
+# This file is automatically generated by Racc 1.4.5
+# from Racc grammer file "".
+#
+
+require 'racc/parser.rb'
+
+ require "lexer"
+ require "nodes"
+
+class Parser < Racc::Parser
+
+module_eval(<<'...end grammar.y/module_eval...', 'grammar.y', 137)
+ def parse(code, show_tokens=false)
+ @tokens = Lexer.new.tokenize(code)
+ puts @tokens.inspect if show_tokens
+ do_parse
+ end
+
+ def next_token
+ @tokens.shift
+ end
+...end grammar.y/module_eval...
+##### State transition tables begin ###
+
+racc_action_table = [
+ 5, 37, 9, 10, 42, 13, 15, 17, 19, 1,
+ 3, 6, 5, 37, 9, 10, 40, 13, 15, 17,
+ 19, 1, 3, 6, 5, 29, 9, 10, 29, 13,
+ 15, 17, 19, 1, 3, 6, 5, 46, 9, 10,
+ 35, 13, 15, 17, 19, 1, 3, 6, 5, 29,
+ 9, 10, 29, 13, 15, 17, 19, 1, 3, 6,
+ 5, 48, 9, 10, 49, 13, 15, 17, 19, 1,
+ 3, 6, 5, 31, 9, 10, 52, 13, 15, 17,
+ 19, 1, 3, 6, 5, 30, 9, 10, 29, 13,
+ 15, 17, 19, 1, 3, 6, 5, 25, 9, 10,
+ 28, 13, 15, 17, 19, 1, 3, 6, 28, 53,
+ 27, 37, 58, 21, 45, 29, 29, 22, 27, 55,
+ 44, 56, 45, 37, 23, 37, 60 ]
+
+racc_action_check = [
+ 0, 30, 0, 0, 30, 0, 0, 0, 0, 0,
+ 0, 0, 26, 31, 26, 26, 29, 26, 26, 26,
+ 26, 26, 26, 26, 25, 33, 25, 25, 34, 25,
+ 25, 25, 25, 25, 25, 25, 5, 36, 5, 5,
+ 23, 5, 5, 5, 5, 5, 5, 5, 37, 38,
+ 37, 37, 39, 37, 37, 37, 37, 37, 37, 37,
+ 22, 40, 22, 22, 42, 22, 22, 22, 22, 22,
+ 22, 22, 48, 10, 48, 48, 46, 48, 48, 48,
+ 48, 48, 48, 48, 21, 9, 21, 21, 8, 21,
+ 21, 21, 21, 21, 21, 21, 45, 6, 45, 45,
+ 47, 45, 45, 45, 45, 45, 45, 45, 7, 47,
+ 47, 24, 54, 3, 54, 51, 24, 3, 7, 50,
+ 32, 50, 32, 52, 4, 55, 56 ]
+
+racc_action_pointer = [
+ -2, nil, nil, 96, 124, 34, 76, 102, 69, 73,
+ 60, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, 82, 58, 40, 97, 22, 10, nil, nil, 4,
+ -13, -1, 102, 6, 9, nil, 31, 46, 30, 33,
+ 44, nil, 52, nil, nil, 94, 73, 94, 70, nil,
+ 101, 96, 109, nil, 94, 111, 114, nil, nil, nil,
+ nil ]
+
+racc_action_default = [
+ -1, -19, -12, -20, -39, -39, -27, -2, -3, -39,
+ -39, -6, -7, -15, -8, -16, -9, -17, -10, -18,
+ -11, -24, -39, -39, -39, -39, -5, -14, -13, -39,
+ -39, -39, -39, -25, -28, 61, -36, -39, -29, -4,
+ -22, -30, -32, -35, -21, -39, -39, -39, -24, -33,
+ -39, -26, -39, -38, -39, -39, -39, -37, -23, -31,
+ -34 ]
+
+racc_goto_table = [
+ 24, 7, 36, 4, 32, 50, nil, nil, 41, 43,
+ nil, nil, nil, nil, nil, nil, 33, 34, nil, nil,
+ 38, 39, nil, nil, nil, nil, nil, nil, nil, nil,
+ 57, 54, nil, 59, nil, nil, nil, nil, 47, nil,
+ 51, nil, nil, 33 ]
+
+racc_goto_check = [
+ 3, 2, 13, 1, 12, 14, nil, nil, 13, 13,
+ nil, nil, nil, nil, nil, nil, 3, 3, nil, nil,
+ 3, 3, nil, nil, nil, nil, nil, nil, nil, nil,
+ 13, 12, nil, 13, nil, nil, nil, nil, 2, nil,
+ 3, nil, nil, 3 ]
+
+racc_goto_pointer = [
+ nil, 3, 1, -5, nil, nil, nil, nil, nil, nil,
+ nil, nil, -17, -22, -37 ]
+
+racc_goto_default = [
+ nil, nil, nil, 8, 26, 11, 12, 14, 16, 18,
+ 20, 2, nil, nil, nil ]
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 0, 23, :_reduce_1,
+ 1, 23, :_reduce_2,
+ 1, 24, :_reduce_3,
+ 3, 24, :_reduce_4,
+ 2, 24, :_reduce_5,
+ 1, 25, :_reduce_none,
+ 1, 25, :_reduce_none,
+ 1, 25, :_reduce_none,
+ 1, 25, :_reduce_none,
+ 1, 25, :_reduce_none,
+ 1, 25, :_reduce_none,
+ 1, 25, :_reduce_none,
+ 1, 26, :_reduce_none,
+ 1, 26, :_reduce_none,
+ 1, 27, :_reduce_15,
+ 1, 27, :_reduce_16,
+ 1, 27, :_reduce_17,
+ 1, 27, :_reduce_18,
+ 1, 27, :_reduce_19,
+ 1, 28, :_reduce_20,
+ 4, 28, :_reduce_21,
+ 3, 28, :_reduce_22,
+ 6, 28, :_reduce_23,
+ 0, 34, :_reduce_24,
+ 1, 34, :_reduce_25,
+ 3, 34, :_reduce_26,
+ 1, 29, :_reduce_27,
+ 3, 30, :_reduce_28,
+ 3, 30, :_reduce_29,
+ 3, 31, :_reduce_30,
+ 6, 31, :_reduce_31,
+ 0, 36, :_reduce_32,
+ 1, 36, :_reduce_33,
+ 3, 36, :_reduce_34,
+ 3, 32, :_reduce_35,
+ 3, 33, :_reduce_36,
+ 6, 33, :_reduce_37,
+ 3, 35, :_reduce_38 ]
+
+racc_reduce_n = 39
+
+racc_shift_n = 61
+
+racc_token_table = {
+ false => 0,
+ :error => 1,
+ :IF => 2,
+ :ELSE => 3,
+ :DEF => 4,
+ :CLASS => 5,
+ :NEWLINE => 6,
+ :NUMBER => 7,
+ :STRING => 8,
+ :TRUE => 9,
+ :FALSE => 10,
+ :NIL => 11,
+ :IDENTIFIER => 12,
+ :CONSTANT => 13,
+ :INDENT => 14,
+ :DEDENT => 15,
+ ";" => 16,
+ "(" => 17,
+ ")" => 18,
+ "." => 19,
+ "," => 20,
+ "=" => 21 }
+
+racc_nt_base = 22
+
+racc_use_result_var = true
+
+Racc_arg = [
+ racc_action_table,
+ racc_action_check,
+ racc_action_default,
+ racc_action_pointer,
+ racc_goto_table,
+ racc_goto_check,
+ racc_goto_default,
+ racc_goto_pointer,
+ racc_nt_base,
+ racc_reduce_table,
+ racc_token_table,
+ racc_shift_n,
+ racc_reduce_n,
+ racc_use_result_var ]
+
+Racc_token_to_s_table = [
+ "$end",
+ "error",
+ "IF",
+ "ELSE",
+ "DEF",
+ "CLASS",
+ "NEWLINE",
+ "NUMBER",
+ "STRING",
+ "TRUE",
+ "FALSE",
+ "NIL",
+ "IDENTIFIER",
+ "CONSTANT",
+ "INDENT",
+ "DEDENT",
+ "\";\"",
+ "\"(\"",
+ "\")\"",
+ "\".\"",
+ "\",\"",
+ "\"=\"",
+ "$start",
+ "Root",
+ "Expressions",
+ "Expression",
+ "Terminator",
+ "Literal",
+ "Call",
+ "Constant",
+ "Assign",
+ "Def",
+ "Class",
+ "If",
+ "ArgList",
+ "Block",
+ "ParamList" ]
+
+Racc_debug_parser = false
+
+##### State transition tables end #####
+
+# reduce 0 omitted
+
+module_eval(<<'.,.,', 'grammar.y', 29)
+ def _reduce_1(val, _values, result)
+ result = Nodes.new([])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 30)
+ def _reduce_2(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 35)
+ def _reduce_3(val, _values, result)
+ result = Nodes.new(val)
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 36)
+ def _reduce_4(val, _values, result)
+ result = val[0] << val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 38)
+ def _reduce_5(val, _values, result)
+ result = Nodes.new([val[0]])
+ result
+ end
+.,.,
+
+# reduce 6 omitted
+
+# reduce 7 omitted
+
+# reduce 8 omitted
+
+# reduce 9 omitted
+
+# reduce 10 omitted
+
+# reduce 11 omitted
+
+# reduce 12 omitted
+
+# reduce 13 omitted
+
+# reduce 14 omitted
+
+module_eval(<<'.,.,', 'grammar.y', 59)
+ def _reduce_15(val, _values, result)
+ result = LiteralNode.new(val[0])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 60)
+ def _reduce_16(val, _values, result)
+ result = LiteralNode.new(val[0])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 61)
+ def _reduce_17(val, _values, result)
+ result = LiteralNode.new(true)
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 62)
+ def _reduce_18(val, _values, result)
+ result = LiteralNode.new(false)
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 63)
+ def _reduce_19(val, _values, result)
+ result = LiteralNode.new(nil)
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 69)
+ def _reduce_20(val, _values, result)
+ result = CallNode.new(nil, val[0])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 71)
+ def _reduce_21(val, _values, result)
+ result = CallNode.new(nil, val[0], val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 73)
+ def _reduce_22(val, _values, result)
+ result = CallNode.new(val[0], val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 76)
+ def _reduce_23(val, _values, result)
+ result = CallNode.new(val[0], val[2], val[4])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 80)
+ def _reduce_24(val, _values, result)
+ result = []
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 81)
+ def _reduce_25(val, _values, result)
+ result = val
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 82)
+ def _reduce_26(val, _values, result)
+ result = val[0] << val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 86)
+ def _reduce_27(val, _values, result)
+ result = GetConstantNode.new(val[0])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 91)
+ def _reduce_28(val, _values, result)
+ result = SetLocalNode.new(val[0], val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 92)
+ def _reduce_29(val, _values, result)
+ result = SetConstantNode.new(val[0], val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 97)
+ def _reduce_30(val, _values, result)
+ result = DefNode.new(val[1], [], val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 99)
+ def _reduce_31(val, _values, result)
+ result = DefNode.new(val[1], val[3], val[5])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 103)
+ def _reduce_32(val, _values, result)
+ result = []
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 104)
+ def _reduce_33(val, _values, result)
+ result = val
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 105)
+ def _reduce_34(val, _values, result)
+ result = val[0] << val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 110)
+ def _reduce_35(val, _values, result)
+ result = ClassNode.new(val[1], val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 115)
+ def _reduce_36(val, _values, result)
+ result = IfNode.new(val[1], val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 117)
+ def _reduce_37(val, _values, result)
+ result = IfNode.new(val[1], val[2], val[5])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'grammar.y', 123)
+ def _reduce_38(val, _values, result)
+ result = val[1]
+ result
+ end
+.,.,
+
+def _reduce_none(val, _values, result)
+ val[0]
+end
+
+end # class Parser
35 parser_output.txt
@@ -0,0 +1,35 @@
+input:
+class Awesome:
+ def initialize(name):
+ pass
+
+ def x:
+ 2
+
+if true:
+ aw = Awesome.new("brilliant!")
+else:
+ weird
+
+output:
+#<Nodes:0x933b4 @nodes=[
+ #<ClassNode:0x93404 @name="Awesome", @body=#<Nodes:0x93468 @nodes=[
+ #<Nodes:0x93620 @nodes=[
+ #<Nodes:0x93684 @nodes=[
+ #<DefNode:0x936d4 @name="initialize", @body=#<Nodes:0x93738 @nodes=[
+ #<CallNode:0x9379c @receiver=nil, @arguments=[], @method="pass">
+ ]>, @params=["name"]>
+ ]>, #<DefNode:0x93508 @name="x", @body=#<Nodes:0x93580 @nodes=[
+ #<LiteralNode:0x935d0 @value=2>
+ ]>, @params=[]>
+ ]>
+ ]>>
+ ,
+ #<IfNode:0x93080 @else_body=#<Nodes:0x930e4 @nodes=[
+ #<CallNode:0x93148 @receiver=nil, @arguments=[], @method="weird">
+ ]>, @body=#<Nodes:0x931ac @nodes=[
+ #<SetLocalNode:0x931fc @name="aw", @value=#<CallNode:0x9324c @receiver=#<GetConstantNode:0x93314 @name="Awesome">, @arguments=[
+ #<LiteralNode:0x932c4 @value="brilliant!">
+ ], @method="new">>
+ ]>, @condition=#<LiteralNode:0x93364 @value=true>>
+]>
40 parser_test.rb
@@ -0,0 +1,40 @@
+require "parser.rb"
+
+code = <<-EOS
+class Awesome:
+ def initialize(name):
+ pass
+
+ def x:
+ 2
+
+if true:
+ aw = Awesome.new("brilliant!")
+else:
+ weird
+EOS
+
+
+p Parser.new.parse(code)
+# <Nodes @nodes=[
+# <ClassNode @name="Awesome", @body=<Nodes @nodes=[
+# <Nodes @nodes=[
+# <Nodes @nodes=[
+# <DefNode @name="initialize", @params=["name"], @body=<Nodes @nodes=[
+# <CallNode @method="pass">
+# ]>>
+# ]>,
+# <DefNode @name="x", @body=<Nodes @nodes=[
+# <LiteralNode @value=2>
+# ]>>
+# ]>
+# ]>>,
+# <IfNode @condition=<LiteralNode @value=true>, @body=<Nodes @nodes=[
+# <SetLocalNode @name="aw",
+# @value=<CallNode @method="new",
+# @arguments=[<LiteralNode @value="brilliant!">],
+# @receiver=<GetConstantNode @name="Awesome">>>
+# ]>, @else_body=<Nodes @nodes=[
+# <CallNode @method="weird">
+# ]>>
+# ]>
146 runtime.rb
@@ -0,0 +1,146 @@
+# Represents an Awesome object instance in the Ruby world.
+class AwesomeObject
+ attr_accessor :awesome_class, :ruby_value
+
+ # Each object have a class (named awesome_class to prevent
+ # errors with Ruby, because Ruby objects already have a class
+ # method). Optionaly an object can hold a Ruby value.
+ def initialize(awesome_class, ruby_value=self)
+ @awesome_class = awesome_class
+ @ruby_value = ruby_value
+ end
+
+ # Call a method on the object.
+ def call(method, arguments)
+ # Like a typical class base runtime model, we store
+ # methods in the class of the object.
+ @awesome_class.lookup(method).call(self, arguments)
+ end
+end
+
+# Represents a Awesome class in the Ruby world.
+# Classes are objects in Awesome so they inherit from AwesomeObject.
+class AwesomeClass < AwesomeObject
+ attr_reader :awesome_methods
+
+ # Creates a new class. Number is an instance of Class for example.
+ def initialize
+ @awesome_methods = {}
+
+ # Check if we're bootstraping. During this process
+ # the runtime is not fully initialized and core classes
+ # do not yet exists, so we defer setting the object class
+ # once this is all done.
+ # This solves the chiken and egg problem with the Class class.
+ # We can initialize Class then set Class.class = Class.
+ if defined?(Runtime)
+ awesome_class = Runtime["Class"]
+ else
+ awesome_class = nil
+ end
+
+ super(awesome_class)
+ end
+
+ # Lookup a method
+ def lookup(method_name)
+ method = @awesome_methods[method_name]
+ unless method
+ # Here's where we'd implement logic to lookup the method
+ # in super class to implement inheritance.
+ raise "Method not found: #{method_name}"
+ end
+ method
+ end
+
+ # Create a new instance of this Awesome class
+ def new
+ AwesomeObject.new(self)
+ end
+
+ # Create an instance of this Awesome class that holds
+ # a Ruby value. Like a String, Number or true for example.
+ def new_value(value)
+ AwesomeObject.new(self, value)
+ end
+end
+
+# A method defined in the Awesome world.
+# We can use Ruby's Proc to define a method in Ruby world.
+# We'll learn more about this at the end of this file, during
+# the boostraping process.
+class AwesomeMethod
+ def initialize(params, body)
+ @params = params
+ @body = body
+ end
+
+ def call(receiver, arguments)
+ @body.eval(Context.new(receiver))
+ end
+end
+
+# Evaluation context tracking values that change depending on
+# where the code is evaluated.
+# "locals" holds local variables.
+# "current_self" is the object on which methods with no receivers
+# are called, eg.: print is like current_self.print
+# "current_class" is the class on which methods are defined with
+# the "def" keyword.
+class Context
+ attr_reader :locals, :current_self, :current_class
+
+ # We store constants as class variable since they are globally accessible.
+ # If you want to implement so kind of namespacing, you could store it
+ # in the instance of this class.
+ @@constants = {}
+
+ def initialize(current_self, current_class=current_self.awesome_class)
+ @locals = {}
+ @current_self = current_self
+ @current_class = current_class
+ end
+
+ # Shortcuts to access constants, Runtime[...] instead of Runtime.constants[...]
+ def [](name)
+ @@constants[name]
+ end
+ def []=(name, value)
+ @@constants[name] = value
+ end
+end
+
+# Bootstrap the runtime
+awesome_class = AwesomeClass.new # Class is a class
+awesome_class.awesome_class = awesome_class # Class.class == Class
+awesome_object_class = AwesomeClass.new # Object = Class.new
+
+# Create the Runtime object (the root context) on which all code will
+# start its evaluation.
+Runtime = Context.new(awesome_object_class.new)
+
+# Register the core classes as constants in the runtime.
+Runtime["Class"] = awesome_class
+Runtime["Object"] = awesome_object_class
+Runtime["Number"] = AwesomeClass.new
+Runtime["String"] = AwesomeClass.new
+Runtime["TrueClass"] = AwesomeClass.new
+Runtime["FalseClass"] = AwesomeClass.new
+Runtime["NilClass"] = AwesomeClass.new
+
+# Register primitives that map to Ruby values
+Runtime["true"] = Runtime["TrueClass"].new_value(true)
+Runtime["false"] = Runtime["FalseClass"].new_value(false)
+Runtime["nil"] = Runtime["NilClass"].new_value(nil)
+
+# Define some Awesome methods in Ruby. We can use a proc since they
+# respond to "call".
+Runtime["Class"].awesome_methods["new"] = proc do |receiver, arguments|
+ # Creates a new instance of the class
+ receiver.new
+end
+
+Runtime["Object"].awesome_methods["print"] = proc do |receiver, arguments|
+ puts arguments.first.ruby_value
+ Runtime["nil"]
+end
112 spartan.rb
@@ -0,0 +1,112 @@
+require "parser.rb"
+
+code = <<-EOS
+class Awesome:
+ def initialize(name):
+ pass
+
+ def x:
+ 2
+
+if true:
+ aw = Awesome.new("brilliant!")
+else:
+ weird
+EOS
+
+
+
+objects = Parser.new.parse(code)
+
+def ifarg(objet)
+ objet.instance_variable_get(:@value)
+end
+
+def ifbody(objet)
+ node(objet) { puts objet }
+end
+
+def node(objet)
+ # puts objet.class
+ # puts objet.instance_variable_get(:@condition)
+ if objet.instance_of?(IfNode)
+ puts "if ("+ ifarg(objet.instance_variable_get(:@condition)).to_s + ")"
+ puts "{"
+ yield ifbody(objet.instance_variable_get(:@body)) if block_given?
+ puts "}"
+ end
+
+ if objet.instance_of?(ClassNode)
+ puts "class "+ objet.instance_variable_get(:@name).to_s + "{"
+ yield node(objet.instance_variable_get(:@body)) if block_given?
+ puts "}"
+ end
+
+ if objet.instance_of?(Nodes)
+ yield node(objet.instance_variable_get(:@nodes)) if block_given?
+ end
+
+ if objet.instance_of?(DefNode)
+ puts "function "+ objet.instance_variable_get(:@name).to_s + "(" + objet.instance_variable_get(:@params).to_s + ") {"
+ yield node(objet.instance_variable_get(:@body)) if block_given?
+ puts "}"
+ end
+
+ if objet.instance_of?(String)
+ yield objet if block_given?
+ end
+end
+
+if objects.instance_of?(Nodes)
+ objarray = objects.instance_variable_get(:@nodes)
+ objarray.each do |object|
+ puts node(object)
+ end
+end
+
+
+# <Nodes @nodes=[
+# <ClassNode @name="Awesome", @body=<Nodes @nodes=[
+# <Nodes @nodes=[
+# <Nodes @nodes=[
+# <DefNode @name="initialize", @params=["name"], @body=<Nodes @nodes=[
+# <CallNode @method="pass">
+# ]>>
+# ]>,
+# <DefNode @name="x", @body=<Nodes @nodes=[
+# <LiteralNode @value=2>
+# ]>>
+# ]>
+# ]>>,
+# <IfNode @condition=<LiteralNode @value=true>, @body=<Nodes @nodes=[
+# <SetLocalNode @name="aw",
+# @value=<CallNode @method="new",
+# @arguments=[<LiteralNode @value="brilliant!">],
+# @receiver=<GetConstantNode @name="Awesome">>>
+# ]>, @else_body=<Nodes @nodes=[
+# <CallNode @method="weird">
+# ]>>
+# ]>
+
+
+# <Nodes:0x933b4 @nodes=[
+# <ClassNode:0x93404 @name="Awesome", @body=#<Nodes:0x93468 @nodes=[
+# <Nodes:0x93620 @nodes=[
+# <Nodes:0x93684 @nodes=[
+# <DefNode:0x936d4 @name="initialize", @body=#<Nodes:0x93738 @nodes=[
+# <CallNode:0x9379c @receiver=nil, @arguments=[], @method="pass">
+# ]>, @params=["name"]>
+# ]>,
+# <DefNode:0x93508 @name="x", @body=#<Nodes:0x93580 @nodes=[
+# <LiteralNode:0x935d0 @value=2>
+# ]>, @params=[]>
+# ]>
+# ]>>,
+# <IfNode:0x93080 @else_body=#<Nodes:0x930e4 @nodes=[
+# <CallNode:0x93148 @receiver=nil, @arguments=[], @method="weird">
+# ]>, @body=#<Nodes:0x931ac @nodes=[
+# <SetLocalNode:0x931fc @name="aw", @value=#<CallNode:0x9324c @receiver=#<GetConstantNode:0x93314 @name="Awesome">, @arguments=[
+# <LiteralNode:0x932c4 @value="brilliant!">
+# ], @method="new">>
+# ]>, @condition=#<LiteralNode:0x93364 @value=true>>
+# ]>

0 comments on commit 5560529

Please sign in to comment.
Something went wrong with that request. Please try again.