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