mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
319 lines
12 KiB
Text
319 lines
12 KiB
Text
|
# Copyright (c) 2014 James Harton
|
||
|
#
|
||
|
# MIT License
|
||
|
#
|
||
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||
|
# a copy of this software and associated documentation files (the
|
||
|
# "Software"), to deal in the Software without restriction, including
|
||
|
# without limitation the rights to use, copy, modify, merge, publish,
|
||
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
# permit persons to whom the Software is furnished to do so, subject to
|
||
|
# the following conditions:
|
||
|
#
|
||
|
# The above copyright notice and this permission notice shall be
|
||
|
# included in all copies or substantial portions of the Software.
|
||
|
#
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
|
||
|
class Huia::Parser
|
||
|
|
||
|
token
|
||
|
IDENTIFIER EQUAL PLUS MINUS ASTERISK FWD_SLASH COLON FLOAT INTEGER STRING
|
||
|
EXPO INDENT OUTDENT OPAREN CPAREN DOT SIGNATURE NL EOF PIPE COMMA NIL TRUE
|
||
|
FALSE EQUALITY CALL SELF CONSTANT CHAR DOUBLE_TICK_STRING
|
||
|
DOUBLE_TICK_STRING_END INTERPOLATE_START INTERPOLATE_END BOX LSQUARE
|
||
|
RSQUARE FACES LFACE RFACE BANG TILDE RETURN NOT_EQUALITY OR AND GT LT
|
||
|
GTE LTE AT
|
||
|
|
||
|
prechigh
|
||
|
left EXPO
|
||
|
left BANG TILDE
|
||
|
left ASTERISK FWD_SLASH PERCENT
|
||
|
left PLUS MINUS
|
||
|
|
||
|
right EQUAL
|
||
|
preclow
|
||
|
|
||
|
rule
|
||
|
statements: statement
|
||
|
| statements statement { return scope }
|
||
|
|
||
|
statement: expr eol { return scope.append val[0] }
|
||
|
| expr { return scope.append val[0] }
|
||
|
| eol { return scope }
|
||
|
|
||
|
eol: NL | EOF
|
||
|
nlq: NL |
|
||
|
|
||
|
expr: literal
|
||
|
| grouped_expr
|
||
|
| binary_op
|
||
|
| unary_op
|
||
|
| method_call
|
||
|
| constant
|
||
|
| variable
|
||
|
| array
|
||
|
| hash
|
||
|
| return
|
||
|
|
||
|
return: return_expr
|
||
|
| return_nil
|
||
|
return_expr: RETURN expr { return n(:Return, val[1]) }
|
||
|
return_nil: RETURN { return n(:Return, n(:Nil)) }
|
||
|
|
||
|
array: empty_array
|
||
|
| array_list
|
||
|
|
||
|
empty_array: BOX { return n :Array }
|
||
|
|
||
|
array_list: LSQUARE array_items RSQUARE { return val[1] }
|
||
|
array_items: expr { return n :Array, [val[0]] }
|
||
|
| array_items COMMA expr { val[0].append(val[2]); return val[0] }
|
||
|
|
||
|
hash: empty_hash
|
||
|
| hash_list
|
||
|
empty_hash: FACES { return n :Hash }
|
||
|
hash_list: LFACE hash_items RFACE { return val[1] }
|
||
|
hash_items: hash_item { return n :Hash, val[0] }
|
||
|
| hash_items COMMA hash_item { val[0].append(val[2]); return val[0] }
|
||
|
hash_item: expr COLON expr { return n :HashItem, val[0], val[2] }
|
||
|
|
||
|
constant: CONSTANT { return constant val[0] }
|
||
|
|
||
|
indented: indented_w_stmts
|
||
|
| indented_w_expr
|
||
|
| indented_wo_stmts
|
||
|
indented_w_stmts: indent statements outdent { return val[0] }
|
||
|
indented_w_expr: indent expr outdent { return val[0].append(val[1]) }
|
||
|
indented_wo_stmts: indent outdent { return val[0] }
|
||
|
outdent: OUTDENT { return pop_scope }
|
||
|
|
||
|
|
||
|
indent_w_args: indent_pipe indent_args PIPE nlq INDENT { return val[0] }
|
||
|
indent_pipe: PIPE { return push_scope }
|
||
|
indent_wo_args: INDENT { return push_scope }
|
||
|
indent: indent_w_args
|
||
|
| indent_wo_args
|
||
|
|
||
|
indent_args: indent_arg
|
||
|
| indent_args COMMA indent_arg
|
||
|
indent_arg: arg_var { return scope.add_argument val[0] }
|
||
|
| arg_var EQUAL expr { return n :Assignment, val[0], val[2] }
|
||
|
arg_var: IDENTIFIER { return n :Variable, val[0] }
|
||
|
|
||
|
method_call: method_call_on_object
|
||
|
| method_call_on_self
|
||
|
| method_call_on_closure
|
||
|
method_call_on_object: expr DOT call_signature { return n :MethodCall, val[0], val[2] }
|
||
|
| expr DOT IDENTIFIER { return n :MethodCall, val[0], n(:CallSignature, val[2]) }
|
||
|
method_call_on_self: call_signature { return n :MethodCall, scope_instance, val[0] }
|
||
|
|
||
|
method_call_on_closure: AT call_signature { return n :MethodCall, this_closure, val[1] }
|
||
|
| AT IDENTIFIER { return n :MethodCall, this_closure, n(:CallSignature, val[1]) }
|
||
|
|
||
|
call_signature: call_arguments
|
||
|
| call_simple_name
|
||
|
call_simple_name: CALL { return n :CallSignature, val[0] }
|
||
|
call_argument: SIGNATURE call_passed_arg { return n :CallSignature, val[0], [val[1]] }
|
||
|
call_passed_arg: call_passed_simple
|
||
|
| call_passed_indented
|
||
|
call_passed_simple: expr
|
||
|
| expr NL
|
||
|
call_passed_indented: indented
|
||
|
| indented NL
|
||
|
call_arguments: call_argument { return val[0] }
|
||
|
| call_arguments call_argument { return val[0].concat_signature val[1] }
|
||
|
|
||
|
grouped_expr: OPAREN expr CPAREN { return n :Expression, val[1] }
|
||
|
|
||
|
variable: IDENTIFIER { return allocate_local val[0] }
|
||
|
|
||
|
binary_op: assignment
|
||
|
| addition
|
||
|
| subtraction
|
||
|
| multiplication
|
||
|
| division
|
||
|
| exponentiation
|
||
|
| modulo
|
||
|
| equality
|
||
|
| not_equality
|
||
|
| logical_or
|
||
|
| logical_and
|
||
|
| greater_than
|
||
|
| less_than
|
||
|
| greater_or_eq
|
||
|
| less_or_eq
|
||
|
|
||
|
assignment: IDENTIFIER EQUAL expr { return allocate_local_assignment val[0], val[2] }
|
||
|
addition: expr PLUS expr { return binary val[0], val[2], 'plus:' }
|
||
|
subtraction: expr MINUS expr { return binary val[0], val[2], 'minus:' }
|
||
|
multiplication: expr ASTERISK expr { return binary val[0], val[2], 'multiplyBy:' }
|
||
|
division: expr FWD_SLASH expr { return binary val[0], val[2], 'divideBy:' }
|
||
|
exponentiation: expr EXPO expr { return binary val[0], val[2], 'toThePowerOf:' }
|
||
|
modulo: expr PERCENT expr { return binary val[0], val[2], 'moduloOf:' }
|
||
|
equality: expr EQUALITY expr { return binary val[0], val[2], 'isEqualTo:' }
|
||
|
not_equality: expr NOT_EQUALITY expr { return binary val[0], val[2], 'isNotEqualTo:' }
|
||
|
logical_or: expr OR expr { return binary val[0], val[2], 'logicalOr:' }
|
||
|
logical_and: expr AND expr { return binary val[0], val[2], 'logicalAnd:' }
|
||
|
greater_than: expr GT expr { return binary val[0], val[2], 'isGreaterThan:' }
|
||
|
less_than: expr LT expr { return binary val[0], val[2], 'isLessThan:' }
|
||
|
greater_or_eq: expr GTE expr { return binary val[0], val[2], 'isGreaterOrEqualTo:' }
|
||
|
less_or_eq: expr LTE expr { return binary val[0], val[2], 'isLessOrEqualTo:' }
|
||
|
|
||
|
unary_op: unary_not
|
||
|
| unary_plus
|
||
|
| unary_minus
|
||
|
| unary_complement
|
||
|
|
||
|
unary_not: BANG expr { return unary val[1], 'unaryNot' }
|
||
|
unary_plus: PLUS expr { return unary val[1], 'unaryPlus' }
|
||
|
unary_minus: MINUS expr { return unary val[1], 'unaryMinus' }
|
||
|
unary_complement: TILDE expr { return unary val[1], 'unaryComplement' }
|
||
|
|
||
|
literal: integer
|
||
|
| float
|
||
|
| string
|
||
|
| nil
|
||
|
| true
|
||
|
| false
|
||
|
| self
|
||
|
|
||
|
float: FLOAT { return n :Float, val[0] }
|
||
|
integer: INTEGER { return n :Integer, val[0] }
|
||
|
nil: NIL { return n :Nil }
|
||
|
true: TRUE { return n :True }
|
||
|
false: FALSE { return n :False }
|
||
|
self: SELF { return n :Self }
|
||
|
|
||
|
string: STRING { return n :String, val[0] }
|
||
|
| interpolated_string
|
||
|
| empty_string
|
||
|
|
||
|
interpolated_string: DOUBLE_TICK_STRING interpolated_string_contents DOUBLE_TICK_STRING_END { return val[1] }
|
||
|
interpolation: INTERPOLATE_START expr INTERPOLATE_END { return val[1] }
|
||
|
interpolated_string_contents: interpolated_string_chunk { return n :InterpolatedString, val[0] }
|
||
|
| interpolated_string_contents interpolated_string_chunk { val[0].append(val[1]); return val[0] }
|
||
|
interpolated_string_chunk: chars { return val[0] }
|
||
|
| interpolation { return to_string(val[0]) }
|
||
|
empty_string: DOUBLE_TICK_STRING DOUBLE_TICK_STRING_END { return n :String, '' }
|
||
|
|
||
|
chars: CHAR { return n :String, val[0] }
|
||
|
| chars CHAR { val[0].append(val[1]); return val[0] }
|
||
|
end
|
||
|
|
||
|
---- inner
|
||
|
|
||
|
attr_accessor :lexer, :scopes, :state
|
||
|
|
||
|
def initialize lexer
|
||
|
@lexer = lexer
|
||
|
@state = []
|
||
|
@scopes = []
|
||
|
push_scope
|
||
|
end
|
||
|
|
||
|
def ast
|
||
|
@ast ||= do_parse
|
||
|
@scopes.first
|
||
|
end
|
||
|
|
||
|
def on_error t, val, vstack
|
||
|
line = lexer.line
|
||
|
col = lexer.column
|
||
|
message = "Unexpected #{token_to_str t} at #{lexer.filename} line #{line}:#{col}:\n\n"
|
||
|
|
||
|
start = line - 5 > 0 ? line - 5 : 0
|
||
|
i_size = line.to_s.size
|
||
|
(start..(start + 5)).each do |i|
|
||
|
message << sprintf("\t%#{i_size}d: %s\n", i, lexer.get_line(i))
|
||
|
message << "\t#{' ' * i_size} #{'-' * (col - 1)}^\n" if i == line
|
||
|
end
|
||
|
|
||
|
raise SyntaxError, message
|
||
|
end
|
||
|
|
||
|
def next_token
|
||
|
nt = lexer.next_computed_token
|
||
|
# just use a state stack for now, we'll have to do something
|
||
|
# more sophisticated soon.
|
||
|
if nt && nt.first == :state
|
||
|
if nt.last
|
||
|
state.push << nt.last
|
||
|
else
|
||
|
state.pop
|
||
|
end
|
||
|
next_token
|
||
|
else
|
||
|
nt
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def push_scope
|
||
|
new_scope = Huia::AST::Scope.new scope
|
||
|
new_scope.file = lexer.filename
|
||
|
new_scope.line = lexer.line
|
||
|
new_scope.column = lexer.column
|
||
|
scopes.push new_scope
|
||
|
new_scope
|
||
|
end
|
||
|
|
||
|
def pop_scope
|
||
|
scopes.pop
|
||
|
end
|
||
|
|
||
|
def scope
|
||
|
scopes.last
|
||
|
end
|
||
|
|
||
|
def binary left, right, method
|
||
|
node(:MethodCall, left, node(:CallSignature, method, [right]))
|
||
|
end
|
||
|
|
||
|
def unary left, method
|
||
|
node(:MethodCall, left, node(:CallSignature, method))
|
||
|
end
|
||
|
|
||
|
def node type, *args
|
||
|
Huia::AST.const_get(type).new(*args).tap do |n|
|
||
|
n.file = lexer.filename
|
||
|
n.line = lexer.line
|
||
|
n.column = lexer.column
|
||
|
end
|
||
|
end
|
||
|
alias n node
|
||
|
|
||
|
def allocate_local name
|
||
|
node(:Variable, name).tap do |n|
|
||
|
scope.allocate_local n
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def allocate_local_assignment name, value
|
||
|
node(:Assignment, name, value).tap do |n|
|
||
|
scope.allocate_local n
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def this_closure
|
||
|
allocate_local('@')
|
||
|
end
|
||
|
|
||
|
def scope_instance
|
||
|
node(:ScopeInstance, scope)
|
||
|
end
|
||
|
|
||
|
def constant name
|
||
|
return scope_instance if name == 'self'
|
||
|
node(:Constant, name)
|
||
|
end
|
||
|
|
||
|
def to_string expr
|
||
|
node(:MethodCall, expr, node(:CallSignature, 'toString'))
|
||
|
end
|