grammar BaseBrat #Wrap up program rule program line+ { def brat Treetop::Runtime::SyntaxNode.clear_variables "var @brat = $loader.loadmodule(\"core\", $loader);\n" << "@brat.base_object.@main_tantrum = function() {\n" << "var array = @brat.base_array;" << "var hash = @brat.base_hash;" << "var number = @brat.base_number;" << "var string = @brat.base_string;" << "var regex = @brat.base_regex;" << "var symbol = @brat.base_symbol;" << "var object = this;" << "this.export = function(object, name) { if(@brat.has_field(name, \"@value\")) { $objset($exports, $hash(@brat.to_identifier(name.@value)), object); } else $throw(\"Import expects a string, not \" + $string(name)); }" << self.inner_brat << ";\n" << "}\n" << "@exit_value = @brat.base_object.@main_tantrum();\n" end def inner_brat Treetop::Runtime::SyntaxNode.clear_variables elements.map {|e| " " << e.brat if e.respond_to? :brat }.compact.join(";").gsub(";", ";\n") end def core_brat <<-NEKO var @brat = $loader.loadmodule("internal", $loader); var array = @brat.base_array; var hash = @brat.base_hash; var number = @brat.base_number; var string = @brat.base_string; var regex = @brat.base_regex; var symbol = @brat.base_symbol; var object = @brat.base_object; this = @brat.base_object; export = function(object, name) { if(@brat.has_field(name, "@value")) { $objset($exports, $hash(@brat.nice_identifier(name.@value)), object); } else $throw("Import expects a string, not " + $string(name)); } #{inner_brat} $exports.has_field = @brat.has_field; $exports.hset = @brat.hset; $exports.make_array = @brat.make_array; $exports.make_hash = @brat.make_hash; $exports.base_array = array; $exports.base_hash = hash; $exports.base_number = number; $exports.base_string = string; $exports.base_object = object; $exports.base_regex = regex; $exports.base_symbol = symbol; $exports.num_args = @brat.num_args; $exports.nice_identifier = @brat.nice_identifier; $exports.to_identifier = @brat.to_identifier; NEKO end } / eof end #Matches a single expression, comment, or an empty line rule line ~comment / ~(space?) e:expression ~(space?) ~(comment?) ~( eof / eol / eob) { def brat if e.respond_to? :no_result_brat e.no_result_brat elsif e.respond_to? :brat e.brat end end } / ~empty_line end #Matches an empty line rule empty_line space? ~eol { def brat "" end } end #Expressions! Which is basically everything rule expression regex / binary_operation / index_set / index_get / assignment / paren_exp ~(!".") { def brat; output = paren_exp.brat; @result = paren_exp.result; output; end } / method_access / method_invocation / number / unary_operation / string / symbol / function_definition / hash / array end #Expressions which can be followed by [] rule indexed_expression hash / array / paren_exp / unary_operation / method_invocation / string end #Expressions which can be used as lhs of "." method calls rule method_target_expression hash / array / paren_exp / number / string / regex / symbol / unary_operation end #Expressions which can be used as the lhs of a binary expression rule binary_lhs_expression index_get / paren_exp / number / string / hash / bare_array / method_with_parens / regex / symbol / unary_operation end rule unary_rhs_expression index_get / paren_exp / number / string / hash / bare_array / method_invocation / regex / symbol end #Matches regular expression literals rule regex "/" r:(!"/" . / "\\/")* "/" { def brat next_temp "var #@result = regex.new(\"#{r.text_value}\");" end } end #Matches comments rule comment space? ("//" / "#") (!(eol/eof) .)+ { def brat "" end } / space? "/*" (!"*/" .)+ "*/" end #Matches hash table literals rule hash "[" spaceorbreak hash_inner spaceorbreak "]" { def brat output = hash_inner.brat @result = hash_inner.result output end } end #Matches array literals rule array "[" spaceorbreak inner_arg_list spaceorbreak "]" { def brat output = inner_arg_list.brat next_temp output << "var #@result = @brat.make_array(#{inner_arg_list.array});" end } / "[" spaceorbreak "]" { def brat next_temp "var #@result = @brat.make_array($amake(0));" end } end #Matches bare arrays #(Note: is this necessary?) rule bare_array "[" spaceorbreak inner_arg_list spaceorbreak "]" ~(&(space / eol)) { def brat output = inner_arg_list.brat next_temp output << "var #@result = @brat.make_array(#{inner_arg_list.array});" end } end #Inside of a hash literal rule hash_inner first:hash_argument rest:(spaceorbreak "," spaceorbreak n:hash_argument spaceorbreak)* { def brat next_temp hash_temp = @result output = "var #{hash_temp} = $hnew(#{rest.elements.length + 1});" output << first.brat output << "@brat.hset(#{hash_temp}, #{first.index}, #{first.value});" rest.elements.each do |e| elem = e.n output << elem.brat output << "@brat.hset(#{hash_temp}, #{elem.index}, #{elem.value});" end next_temp output << "var #@result = @brat.make_hash(#{hash_temp});" end } end #Single member of a hash table rule hash_argument i:expression spaceorbreak ":" spaceorbreak v:expression { attr_reader :index, :value def brat output = i.brat output << v.brat @index = i.result @value = v.result output end } end #Setting value via index (e.g., "a[1] = b") rule index_set a:indexed_expression "[" i:expression "]" space? "=" spaceorbreak v:expression { def brat output = a.brat output << i.brat output << v.brat next_temp output << "var #@result = #{a.result}.__set(#{i.result}, #{v.result});" @result = v.result output << "#{@result};" end } end #Retrieving value via index (e.g., "a[1]") rule index_get e:indexed_expression a:("[" inner_arg_list "]")+ !"." args:arg_list { def brat output = e.brat last = e.result a.elements.each do |elem| output << elem.inner_arg_list.brat next_temp output << "var #@result = #{last}.__get(#{elem.inner_arg_list.list});" last = @result end unless args.respond_to? :nothing? output << args.brat next_temp output << "var #@result = " << invoke(last, args.list, args.length) << ";" end output end } end #Matches assignment using "=" operator rule assignment lhs:field_access space? "=" spaceorbreak rhs:function_definition { def brat output = lhs.brat << rhs.brat << "$objset(#{lhs.result}, $hash(\"#{lhs.i.brat}\"), #{rhs.result});" @result = rhs.result output end } / lhs:field_access space? "=" spaceorbreak rhs:method_access { def brat output = lhs.brat << rhs.brat << "$objset(#{lhs.result}, $hash(\"#{lhs.i.brat}\"), #{rhs.result});" @result = rhs.result output end } / lhs:field_access space? "=" spaceorbreak rhs:expression { def brat output = lhs.brat << rhs.brat << "$objset(#{lhs.result}, $hash(\"#{lhs.i.brat}\"), function() {#{rhs.result}});" @result = rhs.result output end } / lhs:identifier space? "=" spaceorbreak rhs:expression { def brat var = lhs.brat temp = var_exist? var if temp output = rhs.brat << "#{temp} = #{rhs.result};" elsif $interactive var_add var output = rhs.brat << "#{var} = #{rhs.result};" else var_add var, next_temp output = rhs.brat << "#@result = #{rhs.result};" end @result = rhs.result output end } end #Matches a lhs that is a field access (e.g., "a.b") rule field_access m:method_chain i:(identifier / operator) { def brat output = m.brat @result = m.result output end } end #Matches a parenthesized expression rule paren_exp '(' ~(space?) expression ~(space?) ')' { def brat output = expression.brat @result = expression.result output end } end #Matches an identifier, which is any letterfollowed by letters, digits, #and some symbols. #Also mangles anything that is a Neko keyword. rule identifier [a-zA-Z] (!"->" !space [a-zA-Z0-9_!?\-*+^&@~/\\><$_%])* { def brat escape_identifier text_value end } end #Matches a number rule number float / integer end #Matches floats rule float '-' i:pos_integer "." d:pos_integer { def brat next_temp "var #@result = number.@stof(\"-#{i.text_value}.#{d.text_value}\");" end } / i:pos_integer "." d:pos_integer { def brat next_temp "var #@result = number.@stof(\"#{i.text_value}.#{d.text_value}\");" end } end #Matches integers rule integer pos_integer / '-' [0-9]+ { def brat next_temp "var #@result = number.@stoi(\"#{text_value}\");" end } end rule pos_integer [0-9]+ { def brat next_temp "var #@result = number.@stoi(\"#{text_value}\");" end } end #Matches doubly-quoted strings rule string '"' (!'"' . / '\"')* '"' { def brat next_temp "var #@result = string.new(#{text_value});" end } end #Like symbols in Scheme or Ruby rule symbol "'" id:(identifier / operator / number)+ { def brat next_temp "var #@result = symbol.new(\"#{id.elements.map { |m| m.text_value }.join}\");" end } end rule symbol_convert identifier / operator / number { def brat n.text_value end } end #Matches function definitions rule function_definition "{" space? fa:formal_args space? l:line* space? "}" { def brat new_scope if fa.respond_to? :brat args = fa.brat else args = "" end next_temp output = "var #@result = function(#{args}) { \n#{l.elements.map {|e| e.brat}.join(";")} };" pop_scope output end } end #Matches formal arguments in a function definition rule formal_args space? i:identifier? rest_formal space? "|" { def brat if i.respond_to? :brat var_add i.brat, next_temp if $interactive args = [i.brat] + rest_formal.brat else args = [@result] + rest_formal.brat end else args = [] end.join(", ") end } / !"|" end #Matches list items in a formal argument list rule rest_formal (space? "," space? identifier)* { def brat if elements.length > 0 elements.map do |e| var_add e.identifier.brat, next_temp if $interactive e.identifier.brat else @result end end else [] end end } end #Matches a unary operation, which requires a symbol to be #immediately followed by an expression. rule unary_operation op:operator !space rhs:unary_rhs_expression { def brat next_temp args = @result next_temp "var #{args} = $amake(0);" << rhs.brat << "var #@result = " << call_method(rhs.result, op.brat, args, 0) << ";" end } end #Matches binary operations. rule binary_operation lhs:binary_operation_chain rhs:expression { def brat output = lhs.brat output << rhs.brat in_stack = lhs.stack in_stack << rhs.result out_stack = [] op_stack = [] res_stack = [] val = true in_stack.each do |e| if val out_stack.push e val = false else prec = precedence(e) while op_stack.length > 0 && prec <= precedence(op_stack.last) do out_stack.push op_stack.pop end op_stack.push e val = true end end while not op_stack.empty? out_stack.push op_stack.pop end out_stack.each do |e| if e[0,5] == "@temp" res_stack.push e else r = res_stack.pop l = res_stack.pop next_temp output << "var #@result = " << call_method(l, e, "$array(#{r})", 1) << ";" res_stack.push @result end end next_temp output << "var #@result = #{res_stack.pop};" end } end #Matches several binary operations rule binary_operation_chain (lhs:binary_lhs_expression ~space op:operator ~space)+ { attr_reader :stack def brat @stack = [] output = "" elements.each do |e| output << e.lhs.brat @stack << e.lhs.result @stack << e.op.brat end output end } end #Matches method calls which must have parentheses or no arguments rule method_with_parens lhs:method_chain i:(identifier / operator) a:simple_arg_list { def brat output = lhs.brat output << a.brat next_temp output << "var #@result = " << call_method(lhs.result, i.brat, a.array, a.length) << ";" end } / m:identifier a:simple_arg_list { def brat next_temp name = m.brat a.brat << "var #@result = " << get_value(name, a.array, a.length) << ";" end }/ m:paren_exp a:simple_arg_list { def brat output = m.brat output << a.brat next_temp output << "var #@result = " << invoke(m.result, a.list, a.length) << ";" end } end #Matches method/function invocations rule method_invocation lhs:method_chain i:(identifier / operator) a:arg_list { def brat output = lhs.brat output << a.brat next_temp output << "var #@result = " << call_method(lhs.result, i.brat, a.array, a.length) << ";" end #if lhs refers to 'this', we can just call it as though #there were no lhs, possibly doing tail-recursion optimization def no_result_brat return self.brat output = lhs.brat output << a.brat next_temp output << "if(this == #{lhs.result}) {" << get_value_clean(i.brat, a.list, a.length) << "} else {" << call_method(lhs.result, i.brat, a.array, a.length) << "};" end } / m:identifier a:arg_list { def brat next_temp name = m.brat a.brat << "var #@result = " << get_value(name, a.array, a.length) << ";" end def no_result_brat next_temp name = m.brat a.brat << get_value_clean(name, a.list, a.length) << ";" end } / m:paren_exp a:arg_list { def brat output = m.brat output << a.brat next_temp output << "var #@result = " << invoke(m.result, a.list, a.length) << ";" end } end #Matches a method chain, up to the last "." rule method_chain (e:method_lhs ".")+ { def brat output = elements.first.e.brat last = elements.first.e.result @result = last meth = args = nil elements[1..-1].each do |elem| meth = elem.e.identifier.brat args = elem.e.arg_list output << args.brat next_temp output << "var #@result = " << call_method(last, meth, args.array, args.length) << ";" last = @result if not elem.e.a.elements.empty? elem.e.a.elements.each do |ele| output << ele.inner_arg_list.brat next_temp output << "var #@result = #{last}.__get(#{ele.inner_arg_list.list});" last = @result end end end output end } end #Matches expressions which can be the targets of method calls rule method_lhs identifier !space arg_list a:("[" inner_arg_list "]")* { def brat next_temp output = arg_list.brat name = identifier.brat temp = var_exist?(name) || name output << "var #@result;" << "if(@brat.has_field(this, \"#{name}\")) {" << " #@result = " << call_method("this", name, arg_list.array, arg_list.length) << "}" << "else " << " if($typeof(#{temp}) == $tfunction) {" << " #@result = " << invoke(temp, arg_list.list, arg_list.length) << ";" << " } else if($typeof(#{temp}) == $tobject) { " << " #@result = #{temp}}" << "else { $throw \"Tried to invoke method on #{name}\" };" last = @result if not a.elements.empty? a.elements.each do |elem| output << elem.inner_arg_list.brat next_temp output << "var #@result = #{last}.__get(#{elem.inner_arg_list.list});" last = @result end end output end } / a:array a_rest:(array+) { def brat output = a.brat last = a.result a_rest.elements.each do |elem| output << elem.inner_arg_list.brat next_temp output << "var #@result = #{last}.__get(#{elem.inner_arg_list.list});" last = @result end @result = last output end } / method_target_expression { def brat super end } end #Matches the "don't execute this" operator ("->") which just returns the method #instead of executing/calling it. rule method_access "->" m:identifier { def brat next_temp name = m.brat temp = var_exist? name if temp "var #@result = #{temp};" else "var #@result;" << "if (#{name} == null)" << "#@result = $objget(this, $hash(\"#{name}\"));" << "else #@result = #{name};" end end } / o:method_invocation "->" m:(identifier / operator) { def brat next_temp o.brat << "var #@result = $objget(#{o.result}, $hash(\"#{m.brat}\"));" end } end #Matches an argument list, including no arguments at all rule arg_list '(' ~(space?) inner_arg_list ~(space?) ')' { def brat inner_arg_list.brat end def array inner_arg_list.array end def length inner_arg_list.length end def list inner_arg_list.list end } / '(' empty_list ')' { def brat "" end def array "$amake(0)" end def length 0 end def list "" end } / space inner_arg_list { def brat inner_arg_list.brat end def array inner_arg_list.array end def length inner_arg_list.length end def list inner_arg_list.list end } / ![(] ~(&(space?)) { def brat "" end def array "$amake(0)" end def length 0 end def list "" end def nothing? true end } end #Matches an argument list which must be in parentheses, or no arguments at all rule simple_arg_list '(' ~(space?) inner_arg_list ~(space?) ')' { def brat inner_arg_list.brat end def array inner_arg_list.array end def length inner_arg_list.length end def list inner_arg_list.list end } / '(' empty_list ')' { def brat "" end def array "$amake(0)" end def length 0 end def list "" end } / ![(] &(space) { def brat "" end def array "$amake(0)" end def length 0 end def list "" end def nothing? true end } end #Matches the inside of the argument list rule inner_arg_list e:arg_first o:arg_next* { attr_reader :length, :list def brat output = "" args = [] named_args = [] if e.respond_to? :named_argument? output << e.brat named_args << e else output << e.brat args << e.result end o.elements.each do |elem| if elem.respond_to? :brat if elem.arg_first.respond_to? :named_argument? output << elem.arg_first.brat named_args << elem.arg_first else output << elem.brat args << elem.result end end end unless named_args.empty? next_temp hash_temp = @result output << "var #{hash_temp} = $hnew(#{named_args.length});" named_args.map do |e| output << e.i.brat output << "@brat.hset(#{hash_temp}, #{e.i.result}, #{e.result});" end next_temp output << "var #@result = @brat.make_hash(#{hash_temp});" args << @result end @list = args.join(',') @length = args.length output end def array if length > 0 "$array(#{self.list})" else "$amake(0)" end end } end #Matches a single argument rule arg_first named_argument / expression end #Matches the rest of the arguments rule arg_next ~((space / eol)*) "," ~((space / eol)*) arg_first { def brat output = arg_first.brat @result = arg_first.result output end } end #Matches a 'named' argument rule named_argument i:expression spaceorbreak ":" spaceorbreak e:expression { def brat output = e.brat @result = e.result output end def named_argument? true end } end #Matches allowable operators and converts them to mangled method names rule operator ~(!("->" !operator) !("=" !(operator / "="))) ("!=" / ">=" / "<=" / [!?\-*+^@~/\\><$_%\=] / "||" / "|" / "&&" / "&")+ { def brat escape_operator(elements.map {|e| e.text_value}.join) end } end #Matches nothing or spaces rule empty_list ~(space?) end #Matches end-of-lines, which can be \n, ;, or \r\n rule eol ("\n" / ";" / "\r\n")+ { def brat "\n" end } end #Matches spaces or tabs rule space (" " / "\t")+ { def brat text_value end } end #Matches the end of the file rule eof ~((eol / space)? !.) end #Matches the end of a function rule eob ~(space? &"}" space?) end #Matches spaces, eols, or nothing rule spaceorbreak space? eol? space? end end