Skip to content

Instantly share code, notes, and snippets.

@RoxasShadow
Created August 22, 2015 21:39
Show Gist options
  • Save RoxasShadow/a676f078f6643b82da95 to your computer and use it in GitHub Desktop.
Save RoxasShadow/a676f078f6643b82da95 to your computer and use it in GitHub Desktop.
The result of two days of play with parslet while I was waiting for dinner on the couch. It's pretty ugly and nonsensical but still :v
require 'pp'
require 'parslet'
require 'parslet/convenience'
module Nonhocapito
class Parslet::Parser
def self.basic_rule(key, input = nil, &block)
block ||= -> { match[input].repeat }
rule(key, &block)
rule("#{key}?".to_sym) { send(key).maybe }
end
end
class Parser < Parslet::Parser
# Bootstrapping
rule(:bootstrap) { eoc? >>
(
assignation.as(:assignation) |
new_func.as(:new_func) |
function.as(:function) |
or_operation
).repeat
}
root(:bootstrap)
# Values
basic_rule(:space, ' ')
basic_rule(:comma, ',')
basic_rule(:semicolon, ';')
basic_rule(:new_line, '\n')
basic_rule(:quote) { match[?'] | match[?"] }
rule(:and_operator) { (str('and') | str('&&')) >> space? }
rule(:or_operator) { (str('or') | str('||')) >> space? }
rule(:func_block) { space? >> str('=>') >> space? >> function.as(:body).repeat >> space? }
rule(:eoc?) { semicolon? >> new_line? >> space? }
rule(:identifier) { match['\w'].repeat.as(:identifier) }
rule(:param) { float | integer | string | reference }
rule(:args) { (param >> comma? >> space?).repeat.as(:args) }
rule(:value) { param.as(:value) >> eoc? }
rule(:assignable) { and_operation.as(:exp) }
rule(:assignation) { identifier >> space? >> str('=') >> space? >> assignable }
rule(:expression) { value | space? >> str('(') >> space? >> or_operation >> str(')') >> space? }
rule(:function) { identifier >> str('(') >> args.maybe >> str(')') >> eoc? }
rule(:new_func) { identifier.as(:signature) >> func_block >> str('<=') >> eoc? }
rule(:and_operation) {
(expression.as(:left) >> and_operator >>
and_operation.as(:right)).as(:and) |
expression >> eoc? }
rule(:or_operation) {
(and_operation.as(:left) >> or_operator >>
or_operation.as(:right)).as(:or) |
and_operation }
# rule(:line_comment) { (str('//') >> (new_line.absent? >> any).repeat).as(:comment) }
# rule(:multiline_comment) { (str('/*') >> (str('*/').absent? >> any).repeat >> str('*/')).as(:comment) }
# rule(:comment?) { (line_comment | multiline_comment).repeat }
# Types
rule(:reference) { match['\w'].repeat(1).as(:reference) } # to a variable
rule(:string) {
quote >> (
str('\\') >> any |
quote.absent? >> any
).repeat.as(:string) >> quote >> space?
}
rule(:integer) {
((str('+') | str('-')).maybe >> match('[0-9]').repeat(1)).as(:integer) >> space?
}
rule(:float) {
(
integer >> (
str('.') >> match('[0-9]').repeat(1) |
str('e') >> match('[0-9]').repeat(1)
).as(:e)
).as(:float) >> space?
}
end
class Transformer < Parslet::Transform
@@keyholder = {}
@@func_queue = []
# Disjunctive Normal Form
class DNF < Struct.new(:data)
def data
values[0]
end
end
def internals
{ keyholder: @@keyholder, func_queue: @@func_queue }
end
def process
until @@func_queue.empty?
func = @@func_queue.shift
func_name, func_params = func.shift, func[0]
Kernel.send(func_name, *func_params)
end
end
# new_func -> [signature, body]
# Methods are currently just injected to Kernel
rule(new_func: subtree(:function)) do
func_name, functions = function.shift, function
[func_name, functions.dup].tap do
functions.map! do |function|
-> { Kernel.send(function.shift, *function) }
end
Kernel.eval %Q{
def self.#{func_name}
ObjectSpace._id2ref(#{functions.object_id}).each(&:call)
end
}
end
end
# The name of the function created by new_func
rule(signature: { identifier: simple(:identifier) }) do
String(identifier)
end
# Same of `function`, but it's part of new_func
rule(body: { identifier: simple(:identifier), args: subtree(:args) }) do
[String(identifier), args]
end
rule(assignation: { identifier: simple(:identifier), exp: subtree(:exp) }) do
value = exp.is_a?(Array) ? exp.flatten[0] : exp.data
res = [String(identifier), value]
p res
@@keyholder[res[0]] = res[1]
res
end
rule(function: { identifier: simple(:identifier), args: subtree(:args) }) do
func = [String(identifier), args]
@@func_queue << func
func
end
rule(value: simple(:value)) { [[value]] }
rule(args: simple(:args)) { [[args]] }
rule(integer: simple(:value)) { Integer(value) }
rule(reference: simple(:value)) { @@keyholder[String(value)] }
rule(string: simple(:value)) { String(value) }
rule(float: { integer: simple(:integer), e: simple(:e) }) { Integer(integer) + Float(e) }
rule(or: { left: subtree(:left), right: subtree(:right) }) do
(left + right)
end
rule(and: { left: subtree(:left), right: subtree(:right) }) do
dnf = [].tap do |res|
left.each do |l|
right.each do |r|
res << (l + r)
end
end
end
DNF.new(dnf)
end
end
def self.evaluate(source)
tree = Parser.new.parse(source)
transformer = Transformer.new
transformer.apply(tree)
transformer.process
end
def self.evaluate_with_debug(source)
puts '** INPUT **'
puts source
puts "\n** AST **"
pp tree = Parser.new.parse_with_debug(source)
puts "\n** TRANSFORMATION **"
transformer = Transformer.new
pp evaluation = transformer.apply(tree)
puts "\n** INTERNALS **"
pp transformer.internals
puts "\n** EVALUATION **"
transformer.process
end
end
# TODO: lazy new_func so say_hi() prints "hi ever"
Nonhocapito.evaluate_with_debug %Q{
say_hi => puts('hi') puts(what) <=
what = 'ever' some = 1.5 thing = -2;
puts("what", what)
kk = 1 and (2 || 3);
say_hi()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment