Skip to content

Instantly share code, notes, and snippets.

@somebody1234
Created February 5, 2020 08:22
Show Gist options
  • Save somebody1234/0f1ee02fa3f6d1ea2b4a6440450a8fee to your computer and use it in GitHub Desktop.
Save somebody1234/0f1ee02fa3f6d1ea2b4a6440450a8fee to your computer and use it in GitHub Desktop.
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