-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparser_util.rb
More file actions
116 lines (100 loc) · 2.71 KB
/
parser_util.rb
File metadata and controls
116 lines (100 loc) · 2.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
class ParserUtil
class RuleDefineEvalEnv < Object
def match *tokens, &block
@matches ||= {}
@matches[tokens] = block
@matches
end
end
class MatchedEvalEnv < Object
def initialize(tokens, matched)
tokens.zip(matched).each do |name, value|
next unless name.is_a? Symbol
instance_variable_set("@#{name}", value)
end
@matched = matched
end
end
class << self
attr_reader :rules
def rule name, **options, &block
@rules ||= {}
@rules[name] = RuleDefineEvalEnv.new.instance_eval(&block)
end
def binary_operation term_name, operator_name, factor_name, &block
tail_name = "#{term_name}_tail".to_sym
rule term_name do
match factor_name, tail_name do
instance_variable_get("@#{tail_name}").call(instance_variable_get("@#{factor_name}"))
end
end
rule tail_name do |params|
match operator_name, factor_name, tail_name do
if block_given?
->(left){
operator = instance_variable_get("@#{operator_name}")[1]
right = instance_variable_get("@#{factor_name}")
instance_variable_get("@#{tail_name}").call(block.call(operator, left, right))
}
else
->(n){ instance_variable_get("@#{tail_name}").call [:call, instance_variable_get("@#{operator_name}")[1], [n, instance_variable_get("@#{factor_name}")]] }
end
end
match :empty do
->(n){ n }
end
end
end
end
def rules
self.class.rules
end
def parse(tokens)
@tokens = tokens
@index = 0
match_rule(@start_rule).tap do
puts "Warning: not consumming all tokens, may have syntax error" if @index < @tokens.size
end
end
def initialize start_rule = :program
@start_rule = start_rule
end
def match_rule rule_name
result = false
rules[rule_name].each do |tokens, block|
return result if result = match_rule_line(*tokens, block)
end
result
end
def match_rule_line *tokens, block
#p "matching #{tokens}, current_token #{@tokens[@index]}"
old_index = @index
line_result = tokens.map do |token|
if rules[token]
rule_result = match_rule(token)
if rule_result
rule_result
else
@index = old_index
return false
end
elsif token == :empty
nil
else
if current_token == token
@index += 1
@tokens[@index-1]
else
@index = old_index
return false
end
end
end
MatchedEvalEnv.new(tokens, line_result).instance_eval(&block)
end
def current_token
@tokens[@index][0]
rescue
:empty
end
end