poetics 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +7 -0
- data/LICENSE +22 -0
- data/README +87 -0
- data/Rakefile +21 -0
- data/bin/poetics +140 -0
- data/lib/poetics.rb +4 -0
- data/lib/poetics/library.rb +1 -0
- data/lib/poetics/library/code_loader.rb +13 -0
- data/lib/poetics/parser.rb +21 -0
- data/lib/poetics/parser/parser.rb +875 -0
- data/lib/poetics/parser/poetics.kpeg +42 -0
- data/lib/poetics/syntax.rb +3 -0
- data/lib/poetics/syntax/ast.rb +31 -0
- data/lib/poetics/syntax/literal.rb +65 -0
- data/lib/poetics/syntax/node.rb +15 -0
- data/lib/poetics/version.rb +5 -0
- data/poetics.gemspec +23 -0
- data/spec/custom.rb +4 -0
- data/spec/custom/matchers/parse_as.rb +21 -0
- data/spec/custom/runner/relates.rb +97 -0
- data/spec/custom/utils/options.rb +21 -0
- data/spec/custom/utils/script.rb +26 -0
- data/spec/default.mspec +6 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/syntax/literal_spec.rb +60 -0
- metadata +98 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Brian Ford. All rights reserved.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
A native implementation of CoffeeScript [1] that runs on the Rubinius VM [2].
|
2
|
+
|
3
|
+
Q. Whence comes the name Poetics?
|
4
|
+
A. Poetics is a partial anagram of the word CoffeeScript. It is also a nod to
|
5
|
+
Jeremy Ashkenas, the author of CoffeeScript, and his interest in code as
|
6
|
+
literature.
|
7
|
+
|
8
|
+
Q. Why a native implementation?
|
9
|
+
A. CoffeeScript is an interesting language in its own right. Rather than
|
10
|
+
merely being a syntax layer on top of Javascript, and bound to expressing
|
11
|
+
its semantics in those of Javascript, it deserves its own implementation.
|
12
|
+
Many of the reasons to use CoffeeScript in Node.js would also apply to
|
13
|
+
using this native implementation.
|
14
|
+
|
15
|
+
|
16
|
+
Installation
|
17
|
+
|
18
|
+
First, install Rubinius:
|
19
|
+
|
20
|
+
1. Using RVM (http://rvm.beginrescueend.com).
|
21
|
+
|
22
|
+
rvm install rbx
|
23
|
+
|
24
|
+
2. Or directly (http://rubini.us/doc/en/what-is-rubinius/).
|
25
|
+
|
26
|
+
Second, install Poetics:
|
27
|
+
|
28
|
+
rbx -S gem install poetics
|
29
|
+
|
30
|
+
|
31
|
+
Running Poetics
|
32
|
+
|
33
|
+
Poetics provides a REPL for exploratory programming or runs CoffeeScript
|
34
|
+
scripts.
|
35
|
+
|
36
|
+
rbx -S poetics -h
|
37
|
+
|
38
|
+
The REPL presently just parses code and returns an S-Expression
|
39
|
+
representation:
|
40
|
+
|
41
|
+
$ rbx -S poetics
|
42
|
+
> 42
|
43
|
+
[:number, 42.0, 1, 1]
|
44
|
+
> "hello, y'all"
|
45
|
+
[:string, "hello, y'all", 1, 1]
|
46
|
+
>
|
47
|
+
|
48
|
+
|
49
|
+
Getting Help
|
50
|
+
|
51
|
+
Poetics is a work in progress. If you encounter trouble, please file an issue
|
52
|
+
at https://github.com/brixen/poetics/issues
|
53
|
+
|
54
|
+
|
55
|
+
Contributing
|
56
|
+
|
57
|
+
If you find Poetics interesting and would like to help out, fork the project
|
58
|
+
on Github and submit a pull request.
|
59
|
+
|
60
|
+
|
61
|
+
People
|
62
|
+
|
63
|
+
Poetics was created by Brian Ford (brixen) to force him to really learn
|
64
|
+
Javascript and CoffeeScript.
|
65
|
+
|
66
|
+
<add your name here>
|
67
|
+
|
68
|
+
|
69
|
+
License
|
70
|
+
|
71
|
+
Poetics is MIT licensed. See the LICENSE file.
|
72
|
+
|
73
|
+
|
74
|
+
Credits
|
75
|
+
|
76
|
+
Jeremy has created an very interesting language in CoffeeScript. This
|
77
|
+
implementation steals bits and pieces from other projects:
|
78
|
+
|
79
|
+
Rubinius (https://github.com/evanphx/rubinius/)
|
80
|
+
KPeg (https://github.com/evanphx/kpeg/)
|
81
|
+
Atomy (https://github.com/vito/atomy/)
|
82
|
+
Poison (https://github.com/brixen/poison/)
|
83
|
+
Talon (https://github.com/evanphx/talon/)
|
84
|
+
|
85
|
+
|
86
|
+
[1] CoffeeScript (http://jashkenas.github.com/coffee-script/)
|
87
|
+
[2] Rubinius (http://rubini.us)
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
base = File.expand_path '../lib/poetics/parser', __FILE__
|
7
|
+
|
8
|
+
grammar = "#{base}/poetics.kpeg"
|
9
|
+
parser = "#{base}/parser.rb"
|
10
|
+
|
11
|
+
file parser => grammar do
|
12
|
+
sh "rbx -S kpeg -f -s #{base}/poetics.kpeg -o #{base}/parser.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Convert the grammar description to a parser"
|
16
|
+
task :parser => parser
|
17
|
+
|
18
|
+
desc "Run the specs (default)"
|
19
|
+
task :spec => :parser do
|
20
|
+
sh "mspec spec"
|
21
|
+
end
|
data/bin/poetics
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
#!/usr/bin/env rbx
|
2
|
+
#
|
3
|
+
# vim: filetype=ruby
|
4
|
+
|
5
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
6
|
+
|
7
|
+
require 'readline'
|
8
|
+
require 'poetics'
|
9
|
+
|
10
|
+
class Poetics::Script
|
11
|
+
HISTORY = File.expand_path "~/.poetics_history"
|
12
|
+
|
13
|
+
attr_accessor :prompt
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@evals = []
|
17
|
+
@script = nil
|
18
|
+
@history = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def options(argv=ARGV)
|
22
|
+
options = Rubinius::Options.new "Usage: poetics [options] [script]", 20
|
23
|
+
|
24
|
+
options.on "-", "Read and evaluate code from STDIN" do
|
25
|
+
@evals << STDIN.read
|
26
|
+
end
|
27
|
+
|
28
|
+
options.on "-c", "FILE", "Check the syntax of FILE" do |f|
|
29
|
+
begin
|
30
|
+
begin
|
31
|
+
Poetics::Parser.parse_file f
|
32
|
+
rescue => e
|
33
|
+
e.render
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
puts "Syntax OK"
|
38
|
+
exit 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
options.on "-e", "CODE", "Execute CODE" do |e|
|
43
|
+
@evals << e
|
44
|
+
end
|
45
|
+
|
46
|
+
options.on "-v", "--version", "Display the version" do
|
47
|
+
puts Poetics::COMMAND_VERSION
|
48
|
+
end
|
49
|
+
|
50
|
+
options.on "-h", "--help", "Display this help" do
|
51
|
+
puts options
|
52
|
+
exit 0
|
53
|
+
end
|
54
|
+
|
55
|
+
options.doc ""
|
56
|
+
|
57
|
+
rest = options.parse(argv)
|
58
|
+
@script ||= rest.first
|
59
|
+
end
|
60
|
+
|
61
|
+
def evals
|
62
|
+
return if @evals.empty?
|
63
|
+
|
64
|
+
Poetics::CodeLoader.evaluate @evals.join("\n")
|
65
|
+
end
|
66
|
+
|
67
|
+
def script
|
68
|
+
return unless @script
|
69
|
+
|
70
|
+
if File.exists? @script
|
71
|
+
Poetics::CodeLoader.execute_file @script
|
72
|
+
else
|
73
|
+
STDERR.puts "Unable to find '#{@script}' to run"
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_history
|
79
|
+
return unless File.exists? HISTORY
|
80
|
+
|
81
|
+
File.open HISTORY, "r" do |f|
|
82
|
+
f.each { |l| Readline::HISTORY << l }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def save_history
|
87
|
+
File.open HISTORY, "w" do |f|
|
88
|
+
@history.last(100).map { |s| f.puts s }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def record_history(str)
|
93
|
+
@history << str
|
94
|
+
end
|
95
|
+
|
96
|
+
def start_prompt
|
97
|
+
@prompt = "> "
|
98
|
+
end
|
99
|
+
|
100
|
+
def continue_prompt
|
101
|
+
@prompt = ". "
|
102
|
+
end
|
103
|
+
|
104
|
+
def repl
|
105
|
+
return if @script
|
106
|
+
load_history
|
107
|
+
start_prompt
|
108
|
+
|
109
|
+
begin
|
110
|
+
snippet = ""
|
111
|
+
while str = Readline.readline(prompt)
|
112
|
+
break if str.nil? and snippet.empty?
|
113
|
+
next if str.empty?
|
114
|
+
|
115
|
+
snippet << str
|
116
|
+
begin
|
117
|
+
value = Poetics::CodeLoader.evaluate snippet
|
118
|
+
p value
|
119
|
+
|
120
|
+
record_history snippet
|
121
|
+
snippet = ""
|
122
|
+
start_prompt
|
123
|
+
rescue => e
|
124
|
+
STDERR.puts e
|
125
|
+
end
|
126
|
+
end
|
127
|
+
ensure
|
128
|
+
save_history
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def main
|
133
|
+
options
|
134
|
+
evals
|
135
|
+
script
|
136
|
+
repl
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
Poetics::Script.new.main
|
data/lib/poetics.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'poetics/library/code_loader'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Poetics
|
2
|
+
class CodeLoader
|
3
|
+
def self.evaluate(string)
|
4
|
+
# We're just parsing for now
|
5
|
+
Poetics::Parser.parse_to_sexp string
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.execute_file(name)
|
9
|
+
value = Poetics::Parser.parse_to_sexp IO.read(name)
|
10
|
+
p value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'poetics/parser/parser'
|
2
|
+
|
3
|
+
class Poetics::Parser
|
4
|
+
include Poetics::Syntax
|
5
|
+
|
6
|
+
def self.parse_to_sexp(string)
|
7
|
+
parser = new string
|
8
|
+
unless parser.parse
|
9
|
+
parser.raise_error
|
10
|
+
end
|
11
|
+
|
12
|
+
parser.result.to_sexp
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :line, :column
|
16
|
+
|
17
|
+
def position(line, column)
|
18
|
+
@line = line
|
19
|
+
@column = column
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,875 @@
|
|
1
|
+
class Poetics::Parser
|
2
|
+
# STANDALONE START
|
3
|
+
def setup_parser(str, debug=false)
|
4
|
+
@string = str
|
5
|
+
@pos = 0
|
6
|
+
@memoizations = Hash.new { |h,k| h[k] = {} }
|
7
|
+
@result = nil
|
8
|
+
@failed_rule = nil
|
9
|
+
@failing_rule_offset = -1
|
10
|
+
|
11
|
+
setup_foreign_grammar
|
12
|
+
end
|
13
|
+
|
14
|
+
# This is distinct from setup_parser so that a standalone parser
|
15
|
+
# can redefine #initialize and still have access to the proper
|
16
|
+
# parser setup code.
|
17
|
+
#
|
18
|
+
def initialize(str, debug=false)
|
19
|
+
setup_parser(str, debug)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :string
|
23
|
+
attr_reader :result, :failing_rule_offset
|
24
|
+
attr_accessor :pos
|
25
|
+
|
26
|
+
# STANDALONE START
|
27
|
+
def current_column(target=pos)
|
28
|
+
if c = string.rindex("\n", target-1)
|
29
|
+
return target - c - 1
|
30
|
+
end
|
31
|
+
|
32
|
+
target + 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_line(target=pos)
|
36
|
+
cur_offset = 0
|
37
|
+
cur_line = 0
|
38
|
+
|
39
|
+
string.each_line do |line|
|
40
|
+
cur_line += 1
|
41
|
+
cur_offset += line.size
|
42
|
+
return cur_line if cur_offset >= target
|
43
|
+
end
|
44
|
+
|
45
|
+
-1
|
46
|
+
end
|
47
|
+
|
48
|
+
def lines
|
49
|
+
lines = []
|
50
|
+
string.each_line { |l| lines << l }
|
51
|
+
lines
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
|
56
|
+
def get_text(start)
|
57
|
+
@string[start..@pos-1]
|
58
|
+
end
|
59
|
+
|
60
|
+
def show_pos
|
61
|
+
width = 10
|
62
|
+
if @pos < width
|
63
|
+
"#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")"
|
64
|
+
else
|
65
|
+
"#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def failure_info
|
70
|
+
l = current_line @failing_rule_offset
|
71
|
+
c = current_column @failing_rule_offset
|
72
|
+
|
73
|
+
if @failed_rule.kind_of? Symbol
|
74
|
+
info = self.class::Rules[@failed_rule]
|
75
|
+
"line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'"
|
76
|
+
else
|
77
|
+
"line #{l}, column #{c}: failed rule '#{@failed_rule}'"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def failure_caret
|
82
|
+
l = current_line @failing_rule_offset
|
83
|
+
c = current_column @failing_rule_offset
|
84
|
+
|
85
|
+
line = lines[l-1]
|
86
|
+
"#{line}\n#{' ' * (c - 1)}^"
|
87
|
+
end
|
88
|
+
|
89
|
+
def failure_character
|
90
|
+
l = current_line @failing_rule_offset
|
91
|
+
c = current_column @failing_rule_offset
|
92
|
+
lines[l-1][c-1, 1]
|
93
|
+
end
|
94
|
+
|
95
|
+
def failure_oneline
|
96
|
+
l = current_line @failing_rule_offset
|
97
|
+
c = current_column @failing_rule_offset
|
98
|
+
|
99
|
+
char = lines[l-1][c-1, 1]
|
100
|
+
|
101
|
+
if @failed_rule.kind_of? Symbol
|
102
|
+
info = self.class::Rules[@failed_rule]
|
103
|
+
"@#{l}:#{c} failed rule '#{info.name}', got '#{char}'"
|
104
|
+
else
|
105
|
+
"@#{l}:#{c} failed rule '#{@failed_rule}', got '#{char}'"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class ParseError < RuntimeError
|
110
|
+
end
|
111
|
+
|
112
|
+
def raise_error
|
113
|
+
raise ParseError, failure_oneline
|
114
|
+
end
|
115
|
+
|
116
|
+
def show_error(io=STDOUT)
|
117
|
+
error_pos = @failing_rule_offset
|
118
|
+
line_no = current_line(error_pos)
|
119
|
+
col_no = current_column(error_pos)
|
120
|
+
|
121
|
+
io.puts "On line #{line_no}, column #{col_no}:"
|
122
|
+
|
123
|
+
if @failed_rule.kind_of? Symbol
|
124
|
+
info = self.class::Rules[@failed_rule]
|
125
|
+
io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')"
|
126
|
+
else
|
127
|
+
io.puts "Failed to match rule '#{@failed_rule}'"
|
128
|
+
end
|
129
|
+
|
130
|
+
io.puts "Got: #{string[error_pos,1].inspect}"
|
131
|
+
line = lines[line_no-1]
|
132
|
+
io.puts "=> #{line}"
|
133
|
+
io.print(" " * (col_no + 3))
|
134
|
+
io.puts "^"
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_failed_rule(name)
|
138
|
+
if @pos > @failing_rule_offset
|
139
|
+
@failed_rule = name
|
140
|
+
@failing_rule_offset = @pos
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
attr_reader :failed_rule
|
145
|
+
|
146
|
+
def match_string(str)
|
147
|
+
len = str.size
|
148
|
+
if @string[pos,len] == str
|
149
|
+
@pos += len
|
150
|
+
return str
|
151
|
+
end
|
152
|
+
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def scan(reg)
|
157
|
+
if m = reg.match(@string[@pos..-1])
|
158
|
+
width = m.end(0)
|
159
|
+
@pos += width
|
160
|
+
return true
|
161
|
+
end
|
162
|
+
|
163
|
+
return nil
|
164
|
+
end
|
165
|
+
|
166
|
+
if "".respond_to? :getbyte
|
167
|
+
def get_byte
|
168
|
+
if @pos >= @string.size
|
169
|
+
return nil
|
170
|
+
end
|
171
|
+
|
172
|
+
s = @string.getbyte @pos
|
173
|
+
@pos += 1
|
174
|
+
s
|
175
|
+
end
|
176
|
+
else
|
177
|
+
def get_byte
|
178
|
+
if @pos >= @string.size
|
179
|
+
return nil
|
180
|
+
end
|
181
|
+
|
182
|
+
s = @string[@pos]
|
183
|
+
@pos += 1
|
184
|
+
s
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def parse(rule=nil)
|
189
|
+
if !rule
|
190
|
+
_root ? true : false
|
191
|
+
else
|
192
|
+
# This is not shared with code_generator.rb so this can be standalone
|
193
|
+
method = rule.gsub("-","_hyphen_")
|
194
|
+
__send__("_#{method}") ? true : false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class LeftRecursive
|
199
|
+
def initialize(detected=false)
|
200
|
+
@detected = detected
|
201
|
+
end
|
202
|
+
|
203
|
+
attr_accessor :detected
|
204
|
+
end
|
205
|
+
|
206
|
+
class MemoEntry
|
207
|
+
def initialize(ans, pos)
|
208
|
+
@ans = ans
|
209
|
+
@pos = pos
|
210
|
+
@uses = 1
|
211
|
+
@result = nil
|
212
|
+
end
|
213
|
+
|
214
|
+
attr_reader :ans, :pos, :uses, :result
|
215
|
+
|
216
|
+
def inc!
|
217
|
+
@uses += 1
|
218
|
+
end
|
219
|
+
|
220
|
+
def move!(ans, pos, result)
|
221
|
+
@ans = ans
|
222
|
+
@pos = pos
|
223
|
+
@result = result
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def external_invoke(other, rule, *args)
|
228
|
+
old_pos = @pos
|
229
|
+
old_string = @string
|
230
|
+
|
231
|
+
@pos = other.pos
|
232
|
+
@string = other.string
|
233
|
+
|
234
|
+
begin
|
235
|
+
if val = __send__(rule, *args)
|
236
|
+
other.pos = @pos
|
237
|
+
else
|
238
|
+
other.set_failed_rule "#{self.class}##{rule}"
|
239
|
+
end
|
240
|
+
val
|
241
|
+
ensure
|
242
|
+
@pos = old_pos
|
243
|
+
@string = old_string
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def apply(rule)
|
248
|
+
if m = @memoizations[rule][@pos]
|
249
|
+
m.inc!
|
250
|
+
|
251
|
+
prev = @pos
|
252
|
+
@pos = m.pos
|
253
|
+
if m.ans.kind_of? LeftRecursive
|
254
|
+
m.ans.detected = true
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
|
258
|
+
@result = m.result
|
259
|
+
|
260
|
+
return m.ans
|
261
|
+
else
|
262
|
+
lr = LeftRecursive.new(false)
|
263
|
+
m = MemoEntry.new(lr, @pos)
|
264
|
+
@memoizations[rule][@pos] = m
|
265
|
+
start_pos = @pos
|
266
|
+
|
267
|
+
ans = __send__ rule
|
268
|
+
|
269
|
+
m.move! ans, @pos, @result
|
270
|
+
|
271
|
+
# Don't bother trying to grow the left recursion
|
272
|
+
# if it's failing straight away (thus there is no seed)
|
273
|
+
if ans and lr.detected
|
274
|
+
return grow_lr(rule, start_pos, m)
|
275
|
+
else
|
276
|
+
return ans
|
277
|
+
end
|
278
|
+
|
279
|
+
return ans
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def grow_lr(rule, start_pos, m)
|
284
|
+
while true
|
285
|
+
@pos = start_pos
|
286
|
+
@result = m.result
|
287
|
+
|
288
|
+
ans = __send__ rule
|
289
|
+
return nil unless ans
|
290
|
+
|
291
|
+
break if @pos <= m.pos
|
292
|
+
|
293
|
+
m.move! ans, @pos, @result
|
294
|
+
end
|
295
|
+
|
296
|
+
@result = m.result
|
297
|
+
@pos = m.pos
|
298
|
+
return m.ans
|
299
|
+
end
|
300
|
+
|
301
|
+
class RuleInfo
|
302
|
+
def initialize(name, rendered)
|
303
|
+
@name = name
|
304
|
+
@rendered = rendered
|
305
|
+
end
|
306
|
+
|
307
|
+
attr_reader :name, :rendered
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.rule_info(name, rendered)
|
311
|
+
RuleInfo.new(name, rendered)
|
312
|
+
end
|
313
|
+
|
314
|
+
#
|
315
|
+
def setup_foreign_grammar; end
|
316
|
+
|
317
|
+
# root = - value? - end
|
318
|
+
def _root
|
319
|
+
|
320
|
+
_save = self.pos
|
321
|
+
while true # sequence
|
322
|
+
_tmp = apply(:__hyphen_)
|
323
|
+
unless _tmp
|
324
|
+
self.pos = _save
|
325
|
+
break
|
326
|
+
end
|
327
|
+
_save1 = self.pos
|
328
|
+
_tmp = apply(:_value)
|
329
|
+
unless _tmp
|
330
|
+
_tmp = true
|
331
|
+
self.pos = _save1
|
332
|
+
end
|
333
|
+
unless _tmp
|
334
|
+
self.pos = _save
|
335
|
+
break
|
336
|
+
end
|
337
|
+
_tmp = apply(:__hyphen_)
|
338
|
+
unless _tmp
|
339
|
+
self.pos = _save
|
340
|
+
break
|
341
|
+
end
|
342
|
+
_tmp = apply(:_end)
|
343
|
+
unless _tmp
|
344
|
+
self.pos = _save
|
345
|
+
end
|
346
|
+
break
|
347
|
+
end # end sequence
|
348
|
+
|
349
|
+
set_failed_rule :_root unless _tmp
|
350
|
+
return _tmp
|
351
|
+
end
|
352
|
+
|
353
|
+
# end = !.
|
354
|
+
def _end
|
355
|
+
_save = self.pos
|
356
|
+
_tmp = get_byte
|
357
|
+
_tmp = _tmp ? nil : true
|
358
|
+
self.pos = _save
|
359
|
+
set_failed_rule :_end unless _tmp
|
360
|
+
return _tmp
|
361
|
+
end
|
362
|
+
|
363
|
+
# - = (" " | "\t" | "\n")*
|
364
|
+
def __hyphen_
|
365
|
+
while true
|
366
|
+
|
367
|
+
_save1 = self.pos
|
368
|
+
while true # choice
|
369
|
+
_tmp = match_string(" ")
|
370
|
+
break if _tmp
|
371
|
+
self.pos = _save1
|
372
|
+
_tmp = match_string("\t")
|
373
|
+
break if _tmp
|
374
|
+
self.pos = _save1
|
375
|
+
_tmp = match_string("\n")
|
376
|
+
break if _tmp
|
377
|
+
self.pos = _save1
|
378
|
+
break
|
379
|
+
end # end choice
|
380
|
+
|
381
|
+
break unless _tmp
|
382
|
+
end
|
383
|
+
_tmp = true
|
384
|
+
set_failed_rule :__hyphen_ unless _tmp
|
385
|
+
return _tmp
|
386
|
+
end
|
387
|
+
|
388
|
+
# value = (string | number | boolean)
|
389
|
+
def _value
|
390
|
+
|
391
|
+
_save = self.pos
|
392
|
+
while true # choice
|
393
|
+
_tmp = apply(:_string)
|
394
|
+
break if _tmp
|
395
|
+
self.pos = _save
|
396
|
+
_tmp = apply(:_number)
|
397
|
+
break if _tmp
|
398
|
+
self.pos = _save
|
399
|
+
_tmp = apply(:_boolean)
|
400
|
+
break if _tmp
|
401
|
+
self.pos = _save
|
402
|
+
break
|
403
|
+
end # end choice
|
404
|
+
|
405
|
+
set_failed_rule :_value unless _tmp
|
406
|
+
return _tmp
|
407
|
+
end
|
408
|
+
|
409
|
+
# boolean = position (true | false | null | undefined)
|
410
|
+
def _boolean
|
411
|
+
|
412
|
+
_save = self.pos
|
413
|
+
while true # sequence
|
414
|
+
_tmp = apply(:_position)
|
415
|
+
unless _tmp
|
416
|
+
self.pos = _save
|
417
|
+
break
|
418
|
+
end
|
419
|
+
|
420
|
+
_save1 = self.pos
|
421
|
+
while true # choice
|
422
|
+
_tmp = apply(:_true)
|
423
|
+
break if _tmp
|
424
|
+
self.pos = _save1
|
425
|
+
_tmp = apply(:_false)
|
426
|
+
break if _tmp
|
427
|
+
self.pos = _save1
|
428
|
+
_tmp = apply(:_null)
|
429
|
+
break if _tmp
|
430
|
+
self.pos = _save1
|
431
|
+
_tmp = apply(:_undefined)
|
432
|
+
break if _tmp
|
433
|
+
self.pos = _save1
|
434
|
+
break
|
435
|
+
end # end choice
|
436
|
+
|
437
|
+
unless _tmp
|
438
|
+
self.pos = _save
|
439
|
+
end
|
440
|
+
break
|
441
|
+
end # end sequence
|
442
|
+
|
443
|
+
set_failed_rule :_boolean unless _tmp
|
444
|
+
return _tmp
|
445
|
+
end
|
446
|
+
|
447
|
+
# true = "true" {true_value}
|
448
|
+
def _true
|
449
|
+
|
450
|
+
_save = self.pos
|
451
|
+
while true # sequence
|
452
|
+
_tmp = match_string("true")
|
453
|
+
unless _tmp
|
454
|
+
self.pos = _save
|
455
|
+
break
|
456
|
+
end
|
457
|
+
@result = begin; true_value; end
|
458
|
+
_tmp = true
|
459
|
+
unless _tmp
|
460
|
+
self.pos = _save
|
461
|
+
end
|
462
|
+
break
|
463
|
+
end # end sequence
|
464
|
+
|
465
|
+
set_failed_rule :_true unless _tmp
|
466
|
+
return _tmp
|
467
|
+
end
|
468
|
+
|
469
|
+
# false = "false" {false_value}
|
470
|
+
def _false
|
471
|
+
|
472
|
+
_save = self.pos
|
473
|
+
while true # sequence
|
474
|
+
_tmp = match_string("false")
|
475
|
+
unless _tmp
|
476
|
+
self.pos = _save
|
477
|
+
break
|
478
|
+
end
|
479
|
+
@result = begin; false_value; end
|
480
|
+
_tmp = true
|
481
|
+
unless _tmp
|
482
|
+
self.pos = _save
|
483
|
+
end
|
484
|
+
break
|
485
|
+
end # end sequence
|
486
|
+
|
487
|
+
set_failed_rule :_false unless _tmp
|
488
|
+
return _tmp
|
489
|
+
end
|
490
|
+
|
491
|
+
# null = "null" {null_value}
|
492
|
+
def _null
|
493
|
+
|
494
|
+
_save = self.pos
|
495
|
+
while true # sequence
|
496
|
+
_tmp = match_string("null")
|
497
|
+
unless _tmp
|
498
|
+
self.pos = _save
|
499
|
+
break
|
500
|
+
end
|
501
|
+
@result = begin; null_value; end
|
502
|
+
_tmp = true
|
503
|
+
unless _tmp
|
504
|
+
self.pos = _save
|
505
|
+
end
|
506
|
+
break
|
507
|
+
end # end sequence
|
508
|
+
|
509
|
+
set_failed_rule :_null unless _tmp
|
510
|
+
return _tmp
|
511
|
+
end
|
512
|
+
|
513
|
+
# undefined = "undefined" {undefined_value}
|
514
|
+
def _undefined
|
515
|
+
|
516
|
+
_save = self.pos
|
517
|
+
while true # sequence
|
518
|
+
_tmp = match_string("undefined")
|
519
|
+
unless _tmp
|
520
|
+
self.pos = _save
|
521
|
+
break
|
522
|
+
end
|
523
|
+
@result = begin; undefined_value; end
|
524
|
+
_tmp = true
|
525
|
+
unless _tmp
|
526
|
+
self.pos = _save
|
527
|
+
end
|
528
|
+
break
|
529
|
+
end # end sequence
|
530
|
+
|
531
|
+
set_failed_rule :_undefined unless _tmp
|
532
|
+
return _tmp
|
533
|
+
end
|
534
|
+
|
535
|
+
# number = position (real | hex | int)
|
536
|
+
def _number
|
537
|
+
|
538
|
+
_save = self.pos
|
539
|
+
while true # sequence
|
540
|
+
_tmp = apply(:_position)
|
541
|
+
unless _tmp
|
542
|
+
self.pos = _save
|
543
|
+
break
|
544
|
+
end
|
545
|
+
|
546
|
+
_save1 = self.pos
|
547
|
+
while true # choice
|
548
|
+
_tmp = apply(:_real)
|
549
|
+
break if _tmp
|
550
|
+
self.pos = _save1
|
551
|
+
_tmp = apply(:_hex)
|
552
|
+
break if _tmp
|
553
|
+
self.pos = _save1
|
554
|
+
_tmp = apply(:_int)
|
555
|
+
break if _tmp
|
556
|
+
self.pos = _save1
|
557
|
+
break
|
558
|
+
end # end choice
|
559
|
+
|
560
|
+
unless _tmp
|
561
|
+
self.pos = _save
|
562
|
+
end
|
563
|
+
break
|
564
|
+
end # end sequence
|
565
|
+
|
566
|
+
set_failed_rule :_number unless _tmp
|
567
|
+
return _tmp
|
568
|
+
end
|
569
|
+
|
570
|
+
# hexdigits = /[0-9A-Fa-f]/
|
571
|
+
def _hexdigits
|
572
|
+
_tmp = scan(/\A(?-mix:[0-9A-Fa-f])/)
|
573
|
+
set_failed_rule :_hexdigits unless _tmp
|
574
|
+
return _tmp
|
575
|
+
end
|
576
|
+
|
577
|
+
# hex = "0x" < hexdigits+ > {hexadecimal(text)}
|
578
|
+
def _hex
|
579
|
+
|
580
|
+
_save = self.pos
|
581
|
+
while true # sequence
|
582
|
+
_tmp = match_string("0x")
|
583
|
+
unless _tmp
|
584
|
+
self.pos = _save
|
585
|
+
break
|
586
|
+
end
|
587
|
+
_text_start = self.pos
|
588
|
+
_save1 = self.pos
|
589
|
+
_tmp = apply(:_hexdigits)
|
590
|
+
if _tmp
|
591
|
+
while true
|
592
|
+
_tmp = apply(:_hexdigits)
|
593
|
+
break unless _tmp
|
594
|
+
end
|
595
|
+
_tmp = true
|
596
|
+
else
|
597
|
+
self.pos = _save1
|
598
|
+
end
|
599
|
+
if _tmp
|
600
|
+
text = get_text(_text_start)
|
601
|
+
end
|
602
|
+
unless _tmp
|
603
|
+
self.pos = _save
|
604
|
+
break
|
605
|
+
end
|
606
|
+
@result = begin; hexadecimal(text); end
|
607
|
+
_tmp = true
|
608
|
+
unless _tmp
|
609
|
+
self.pos = _save
|
610
|
+
end
|
611
|
+
break
|
612
|
+
end # end sequence
|
613
|
+
|
614
|
+
set_failed_rule :_hex unless _tmp
|
615
|
+
return _tmp
|
616
|
+
end
|
617
|
+
|
618
|
+
# digits = ("0" | /[1-9]/ /[0-9]/*)
|
619
|
+
def _digits
|
620
|
+
|
621
|
+
_save = self.pos
|
622
|
+
while true # choice
|
623
|
+
_tmp = match_string("0")
|
624
|
+
break if _tmp
|
625
|
+
self.pos = _save
|
626
|
+
|
627
|
+
_save1 = self.pos
|
628
|
+
while true # sequence
|
629
|
+
_tmp = scan(/\A(?-mix:[1-9])/)
|
630
|
+
unless _tmp
|
631
|
+
self.pos = _save1
|
632
|
+
break
|
633
|
+
end
|
634
|
+
while true
|
635
|
+
_tmp = scan(/\A(?-mix:[0-9])/)
|
636
|
+
break unless _tmp
|
637
|
+
end
|
638
|
+
_tmp = true
|
639
|
+
unless _tmp
|
640
|
+
self.pos = _save1
|
641
|
+
end
|
642
|
+
break
|
643
|
+
end # end sequence
|
644
|
+
|
645
|
+
break if _tmp
|
646
|
+
self.pos = _save
|
647
|
+
break
|
648
|
+
end # end choice
|
649
|
+
|
650
|
+
set_failed_rule :_digits unless _tmp
|
651
|
+
return _tmp
|
652
|
+
end
|
653
|
+
|
654
|
+
# int = < digits > {number(text)}
|
655
|
+
def _int
|
656
|
+
|
657
|
+
_save = self.pos
|
658
|
+
while true # sequence
|
659
|
+
_text_start = self.pos
|
660
|
+
_tmp = apply(:_digits)
|
661
|
+
if _tmp
|
662
|
+
text = get_text(_text_start)
|
663
|
+
end
|
664
|
+
unless _tmp
|
665
|
+
self.pos = _save
|
666
|
+
break
|
667
|
+
end
|
668
|
+
@result = begin; number(text); end
|
669
|
+
_tmp = true
|
670
|
+
unless _tmp
|
671
|
+
self.pos = _save
|
672
|
+
end
|
673
|
+
break
|
674
|
+
end # end sequence
|
675
|
+
|
676
|
+
set_failed_rule :_int unless _tmp
|
677
|
+
return _tmp
|
678
|
+
end
|
679
|
+
|
680
|
+
# real = < digits "." digits ("e" /[-+]/? /[0-9]/+)? > {number(text)}
|
681
|
+
def _real
|
682
|
+
|
683
|
+
_save = self.pos
|
684
|
+
while true # sequence
|
685
|
+
_text_start = self.pos
|
686
|
+
|
687
|
+
_save1 = self.pos
|
688
|
+
while true # sequence
|
689
|
+
_tmp = apply(:_digits)
|
690
|
+
unless _tmp
|
691
|
+
self.pos = _save1
|
692
|
+
break
|
693
|
+
end
|
694
|
+
_tmp = match_string(".")
|
695
|
+
unless _tmp
|
696
|
+
self.pos = _save1
|
697
|
+
break
|
698
|
+
end
|
699
|
+
_tmp = apply(:_digits)
|
700
|
+
unless _tmp
|
701
|
+
self.pos = _save1
|
702
|
+
break
|
703
|
+
end
|
704
|
+
_save2 = self.pos
|
705
|
+
|
706
|
+
_save3 = self.pos
|
707
|
+
while true # sequence
|
708
|
+
_tmp = match_string("e")
|
709
|
+
unless _tmp
|
710
|
+
self.pos = _save3
|
711
|
+
break
|
712
|
+
end
|
713
|
+
_save4 = self.pos
|
714
|
+
_tmp = scan(/\A(?-mix:[-+])/)
|
715
|
+
unless _tmp
|
716
|
+
_tmp = true
|
717
|
+
self.pos = _save4
|
718
|
+
end
|
719
|
+
unless _tmp
|
720
|
+
self.pos = _save3
|
721
|
+
break
|
722
|
+
end
|
723
|
+
_save5 = self.pos
|
724
|
+
_tmp = scan(/\A(?-mix:[0-9])/)
|
725
|
+
if _tmp
|
726
|
+
while true
|
727
|
+
_tmp = scan(/\A(?-mix:[0-9])/)
|
728
|
+
break unless _tmp
|
729
|
+
end
|
730
|
+
_tmp = true
|
731
|
+
else
|
732
|
+
self.pos = _save5
|
733
|
+
end
|
734
|
+
unless _tmp
|
735
|
+
self.pos = _save3
|
736
|
+
end
|
737
|
+
break
|
738
|
+
end # end sequence
|
739
|
+
|
740
|
+
unless _tmp
|
741
|
+
_tmp = true
|
742
|
+
self.pos = _save2
|
743
|
+
end
|
744
|
+
unless _tmp
|
745
|
+
self.pos = _save1
|
746
|
+
end
|
747
|
+
break
|
748
|
+
end # end sequence
|
749
|
+
|
750
|
+
if _tmp
|
751
|
+
text = get_text(_text_start)
|
752
|
+
end
|
753
|
+
unless _tmp
|
754
|
+
self.pos = _save
|
755
|
+
break
|
756
|
+
end
|
757
|
+
@result = begin; number(text); end
|
758
|
+
_tmp = true
|
759
|
+
unless _tmp
|
760
|
+
self.pos = _save
|
761
|
+
end
|
762
|
+
break
|
763
|
+
end # end sequence
|
764
|
+
|
765
|
+
set_failed_rule :_real unless _tmp
|
766
|
+
return _tmp
|
767
|
+
end
|
768
|
+
|
769
|
+
# string = position "\"" < /[^\\"]*/ > "\"" {string_value(text)}
|
770
|
+
def _string
|
771
|
+
|
772
|
+
_save = self.pos
|
773
|
+
while true # sequence
|
774
|
+
_tmp = apply(:_position)
|
775
|
+
unless _tmp
|
776
|
+
self.pos = _save
|
777
|
+
break
|
778
|
+
end
|
779
|
+
_tmp = match_string("\"")
|
780
|
+
unless _tmp
|
781
|
+
self.pos = _save
|
782
|
+
break
|
783
|
+
end
|
784
|
+
_text_start = self.pos
|
785
|
+
_tmp = scan(/\A(?-mix:[^\\"]*)/)
|
786
|
+
if _tmp
|
787
|
+
text = get_text(_text_start)
|
788
|
+
end
|
789
|
+
unless _tmp
|
790
|
+
self.pos = _save
|
791
|
+
break
|
792
|
+
end
|
793
|
+
_tmp = match_string("\"")
|
794
|
+
unless _tmp
|
795
|
+
self.pos = _save
|
796
|
+
break
|
797
|
+
end
|
798
|
+
@result = begin; string_value(text); end
|
799
|
+
_tmp = true
|
800
|
+
unless _tmp
|
801
|
+
self.pos = _save
|
802
|
+
end
|
803
|
+
break
|
804
|
+
end # end sequence
|
805
|
+
|
806
|
+
set_failed_rule :_string unless _tmp
|
807
|
+
return _tmp
|
808
|
+
end
|
809
|
+
|
810
|
+
# line = { current_line }
|
811
|
+
def _line
|
812
|
+
@result = begin; current_line ; end
|
813
|
+
_tmp = true
|
814
|
+
set_failed_rule :_line unless _tmp
|
815
|
+
return _tmp
|
816
|
+
end
|
817
|
+
|
818
|
+
# column = { current_column }
|
819
|
+
def _column
|
820
|
+
@result = begin; current_column ; end
|
821
|
+
_tmp = true
|
822
|
+
set_failed_rule :_column unless _tmp
|
823
|
+
return _tmp
|
824
|
+
end
|
825
|
+
|
826
|
+
# position = line:l column:c { position(l, c) }
|
827
|
+
def _position
|
828
|
+
|
829
|
+
_save = self.pos
|
830
|
+
while true # sequence
|
831
|
+
_tmp = apply(:_line)
|
832
|
+
l = @result
|
833
|
+
unless _tmp
|
834
|
+
self.pos = _save
|
835
|
+
break
|
836
|
+
end
|
837
|
+
_tmp = apply(:_column)
|
838
|
+
c = @result
|
839
|
+
unless _tmp
|
840
|
+
self.pos = _save
|
841
|
+
break
|
842
|
+
end
|
843
|
+
@result = begin; position(l, c) ; end
|
844
|
+
_tmp = true
|
845
|
+
unless _tmp
|
846
|
+
self.pos = _save
|
847
|
+
end
|
848
|
+
break
|
849
|
+
end # end sequence
|
850
|
+
|
851
|
+
set_failed_rule :_position unless _tmp
|
852
|
+
return _tmp
|
853
|
+
end
|
854
|
+
|
855
|
+
Rules = {}
|
856
|
+
Rules[:_root] = rule_info("root", "- value? - end")
|
857
|
+
Rules[:_end] = rule_info("end", "!.")
|
858
|
+
Rules[:__hyphen_] = rule_info("-", "(\" \" | \"\\t\" | \"\\n\")*")
|
859
|
+
Rules[:_value] = rule_info("value", "(string | number | boolean)")
|
860
|
+
Rules[:_boolean] = rule_info("boolean", "position (true | false | null | undefined)")
|
861
|
+
Rules[:_true] = rule_info("true", "\"true\" {true_value}")
|
862
|
+
Rules[:_false] = rule_info("false", "\"false\" {false_value}")
|
863
|
+
Rules[:_null] = rule_info("null", "\"null\" {null_value}")
|
864
|
+
Rules[:_undefined] = rule_info("undefined", "\"undefined\" {undefined_value}")
|
865
|
+
Rules[:_number] = rule_info("number", "position (real | hex | int)")
|
866
|
+
Rules[:_hexdigits] = rule_info("hexdigits", "/[0-9A-Fa-f]/")
|
867
|
+
Rules[:_hex] = rule_info("hex", "\"0x\" < hexdigits+ > {hexadecimal(text)}")
|
868
|
+
Rules[:_digits] = rule_info("digits", "(\"0\" | /[1-9]/ /[0-9]/*)")
|
869
|
+
Rules[:_int] = rule_info("int", "< digits > {number(text)}")
|
870
|
+
Rules[:_real] = rule_info("real", "< digits \".\" digits (\"e\" /[-+]/? /[0-9]/+)? > {number(text)}")
|
871
|
+
Rules[:_string] = rule_info("string", "position \"\\\"\" < /[^\\\\\"]*/ > \"\\\"\" {string_value(text)}")
|
872
|
+
Rules[:_line] = rule_info("line", "{ current_line }")
|
873
|
+
Rules[:_column] = rule_info("column", "{ current_column }")
|
874
|
+
Rules[:_position] = rule_info("position", "line:l column:c { position(l, c) }")
|
875
|
+
end
|