Created
February 5, 2020 08:22
-
-
Save somebody1234/0f1ee02fa3f6d1ea2b4a6440450a8fee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PREC_EXPR = { | |
:closed => [ | |
[[/\G\(/, /\G\)/, -> (i) { -> (s) { i.(s) } }]] | |
] | |
} | |
PREC_TIMES = { | |
:left => [ | |
[[/\G\*/,], -> (l, r) { -> (s) { l.(s) * r.(s) } }], | |
[[/\G\//,], -> (l, r) { -> (s) { l.(s) / r.(s) } }], | |
[[/\G%/,], -> (l, r) { -> (s) { l.(s) % r.(s) } }], | |
], | |
:tighter => PREC_EXPR | |
} | |
PREC_PLUS = { | |
:left => [ | |
[[/\G\+/,], -> (l, r) { -> (s) { l.(s) + r.(s) } }], | |
[[/\G-/,], -> (l, r) { -> (s) { l.(s) - r.(s) } }], | |
], | |
:tighter => PREC_TIMES | |
} | |
PREC_AND = { | |
:left => [ | |
[[/\G&&/,], -> (l, r) { -> (s) { l.(s) && r.(s) } }], | |
], | |
:tighter => PREC_PLUS | |
} | |
PREC_OR = { | |
:left => [ | |
[[/\G\|\|/,], -> (l, r) { -> (s) { l.(s) || r.(s) } }], | |
], | |
:tighter => PREC_AND | |
} | |
PREC_TOP = PREC_OR | |
def parse(string) | |
index = 0 | |
locs = [] | |
def save_loc; locs.push index end | |
def pop_loc; locs.pop end | |
def load_loc; index = locs.pop end | |
def eat(token) | |
while match = whitespace || comment do | |
index += match[0].length | |
end | |
match = token.match string, index | |
if match then index += match[0].length end | |
match[0] if match | |
end | |
def braced_body | |
return unless eat(/\G{/) | |
save_loc | |
result = body | |
(load_loc; return) unless eat(/\G}/) | |
result | |
end | |
def body | |
statements = [] | |
stmt = nil | |
while stmt = statement do | |
statements.push stmt | |
end | |
-> { statements.each{|statement| statement.() } } | |
end | |
def statement | |
expression | |
end | |
def expression | |
p_expr(PREC_TOP) | |
end | |
def p_expr(node) | |
p node[:left] || node[:right] || node[:prefix] || node[:postfix] || node[:closed] || node[:non] | |
return literal unless node | |
def op(key, hole: :none) | |
ops = node[key] | |
return unless ops | |
ops.lazy.map {|op, create_lambda| | |
exprs = [] | |
save_loc | |
return unless eat(op[0]) | |
op[1..].each {|regex| | |
(load_loc; save_loc; exprs = []; return) unless expr = expression and eat(regex) | |
exprs.push(expr) | |
} | |
case hole | |
when :last | |
-> (inner) { create_lambda.(*exprs, inner) } | |
when :first | |
-> (inner) { create_lambda.(inner, *exprs) } | |
when :both | |
-> (left, right) { create_lambda.(left, *exprs, right) } | |
else | |
create_lambda.(*exprs) | |
end | |
}.first | |
end | |
def p_hat | |
closed_expr = op(:closed) | |
return closed_expr if closed_expr | |
save_loc | |
if tighter_expr = p_up then | |
if non_expr = op(:non) && tighter_expr_2 = p_up then | |
pop_loc | |
return non_expr.(tighter_expr, tighter_expr_2) | |
else | |
load_loc; save_loc | |
end | |
end | |
load_loc | |
right_exprs = [] | |
while right_expr = p_right do right_exprs.push right_expr end | |
if right_exprs.length != 0 and tighter_expr = p_up then | |
return right_exprs.reduce(tighter_expr) {|prev, expr| expr.(prev) } | |
end | |
if tighter_expr then | |
left_exprs = [] | |
while left_expr = p_left do left_exprs.push left_expr end | |
if left_exprs.length != 0 then | |
return left_exprs.reduce(tighter_expr) {|prev, expr| expr.(prev) } | |
end | |
end | |
nil | |
end | |
def p_right | |
prefix_expr = op(:prefix, hole: :right) | |
return prefix_expr if prefix_expr | |
if tighter_expr = p_up then | |
right_expr = op(:right, hole: :right) | |
return -> (inner) { right_expr.(tighter_expr, expr.(inner)) } | |
end | |
end | |
def p_left | |
postfix_expr = op(:postfix_expr, hole: :left) | |
return postfix_expr if postfix_expr | |
if tighter_expr = p_up then | |
left_expr = op(:left, hole: :left) | |
return -> (inner) { left_expr.(expr.(inner), tighter_expr) } | |
end | |
end | |
def p_up; p_expr(node[:tighter]) end | |
p_hat | |
end | |
def literal | |
decimal || integer || string || boolean | |
end | |
def decimal; result = eat(/\G\d+\.\d+/).to_f; -> (s) { result } end | |
def integer; result = eat(/\G\d+/).to_i; -> (s) { result } end | |
def string; result = eat(/\G'(?:'|\\[\\'nt])'/).undump; -> (s) { result } end | |
def boolean; result = eat(/\btrue\b|\bfalse\b/) == 'true'; -> (s) { result } end | |
body | |
end | |
parse("1 + 2") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment