meta_compile 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +4 -4
- data/Rakefile +98 -50
- data/bin/meta_compile +295 -195
- data/bin/metac_minimal +231 -0
- data/bin/metac_readable +331 -0
- data/bin/{metacomp_re → metac_regexp} +0 -0
- data/meta_compile.gemspec +1 -1
- data/syntaxes/meta_to_ruby.meta +66 -43
- metadata +5 -3
data/README.md
CHANGED
@@ -3,7 +3,7 @@ meta_compile
|
|
3
3
|
|
4
4
|
A meta compilation framework for Ruby à la [Meta-II by Val Schorre](http://ibm-1401.info/Meta-II-schorre.pdf).
|
5
5
|
|
6
|
-
This uses a [C version of Meta-II](https://github.com/impeachgod/meta) developed by Long Nguyen to bootstrap a Ruby version from a [fully self-contained, 26 (non-empty) line specification file](https://raw.github.com/robertfeldt/meta_compile/master/syntaxes/meta_to_ruby_minimal.meta). The [generated Ruby Meta-II compiler file](https://github.com/robertfeldt/meta_compile/blob/master/bin/
|
6
|
+
This uses a [C version of Meta-II](https://github.com/impeachgod/meta) developed by Long Nguyen to bootstrap a Ruby version from a [fully self-contained, 26 (non-empty) line specification file](https://raw.github.com/robertfeldt/meta_compile/master/syntaxes/meta_to_ruby_minimal.meta). The [generated Ruby Meta-II compiler file](https://github.com/robertfeldt/meta_compile/blob/master/bin/metac_minimal) is 231 lines of Ruby code. A more readable and commented version of the specification file [is available here](https://raw.github.com/robertfeldt/meta_compile/master/syntaxes/meta_to_ruby.meta). It also has better error handling both when used from the command line and when compiling (but it is longer, 74 loc for the spec and 331 loc for the [generated compiler](https://github.com/robertfeldt/meta_compile/blob/master/bin/metac_readable)).
|
7
7
|
|
8
8
|
Install
|
9
9
|
-------
|
@@ -45,7 +45,7 @@ and this should convince us:
|
|
45
45
|
|
46
46
|
Limitations
|
47
47
|
-----------
|
48
|
-
+
|
48
|
+
+ Rudimentary error handling/reports while compiling/parsing
|
49
49
|
|
50
50
|
Ok, now what?
|
51
51
|
-------------
|
@@ -62,11 +62,11 @@ Let's use this to compile programs which can only contain assignments of numbers
|
|
62
62
|
|
63
63
|
The new thing here compared to the original Meta-II syntax is the two Regexp's (in as and ex1). First we need to bootstrap the meta compiler that accepts regexps:
|
64
64
|
|
65
|
-
rake
|
65
|
+
rake boot_regexp
|
66
66
|
|
67
67
|
Then we create a compiler for the assignments syntax:
|
68
68
|
|
69
|
-
ruby bin/
|
69
|
+
ruby bin/metac_regexp syntaxes/assignments.meta > tas.rb
|
70
70
|
|
71
71
|
We now have a compiler for assignments and if we apply it [to the file](https://raw.github.com/robertfeldt/meta_compile/master/inputs/assignments.input1):
|
72
72
|
|
data/Rakefile
CHANGED
@@ -9,18 +9,19 @@ def pexec(str)
|
|
9
9
|
end
|
10
10
|
|
11
11
|
desc "Bootstrap our version of Long Nguyen's C version of Meta-II"
|
12
|
-
task :
|
13
|
-
Dir.chdir "bootstrap"
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
12
|
+
task :boot_c do
|
13
|
+
Dir.chdir "bootstrap" do
|
14
|
+
puts "1. Build bootstrap compiler"
|
15
|
+
pexec "gcc -o bootstrapped_c bootstrap.c"
|
16
|
+
puts "2. Use the bootstrapped meta compiler to compile the meta_to_c.meta description of itself"
|
17
|
+
pexec "./bootstrapped_c meta_to_c.meta meta_compiler_from_boostrapped_c.c"
|
18
|
+
puts "3. Build the compiler we created from the meta description"
|
19
|
+
pexec "gcc -o meta_c meta_compiler_from_boostrapped_c.c"
|
20
|
+
puts "4. Now use the generated compiler to generate itself"
|
21
|
+
pexec "./meta_c meta_to_c.meta meta_compiler.c"
|
22
|
+
puts "5. Now ensure the compilers are exactly the same"
|
23
|
+
pexec "diff meta_compiler_from_boostrapped_c.c meta_compiler.c"
|
24
|
+
end
|
24
25
|
end
|
25
26
|
|
26
27
|
# Non-empty Lines of Code calc
|
@@ -28,42 +29,49 @@ def loc(filename)
|
|
28
29
|
File.readlines(filename).map {|l| l.strip.length==0?nil:l}.compact.length
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
def bootstrap_from_c(syntaxFile)
|
33
|
+
if syntaxFile =~ /^syntaxes/
|
34
|
+
syntaxf = File.join("..", syntaxFile)
|
35
|
+
else
|
36
|
+
syntaxf = File.join("..", "syntaxes", syntaxFile)
|
37
|
+
end
|
33
38
|
Dir.chdir("bootstrap") do
|
34
39
|
puts "1. Build bootstrap compiler"
|
35
40
|
pexec "gcc -o bootstrapped_c bootstrap.c"
|
36
41
|
puts "2. Use the c meta compiler to compile the meta_to_ruby_minimal.meta description"
|
37
|
-
pexec "./bootstrapped_c
|
42
|
+
pexec "./bootstrapped_c #{syntaxf} compile_to_ruby.c"
|
38
43
|
puts "3. Build the stepping stone ruby compiler we created from the meta description"
|
39
44
|
pexec "gcc -o meta_r compile_to_ruby.c"
|
40
45
|
puts "4. Now use the generated stepping stone compiler to generate a ruby compiler for the ruby meta syntax"
|
41
|
-
pexec "./meta_r
|
46
|
+
pexec "./meta_r #{syntaxf} meta_ruby_compiler_from_c.rb"
|
42
47
|
puts "5. Run the generated ruby meta compiler to a ruby version"
|
43
|
-
pexec "ruby -I. meta_ruby_compiler_from_c.rb
|
48
|
+
pexec "ruby -I. meta_ruby_compiler_from_c.rb #{syntaxf} > meta_ruby_compiler.rb"
|
44
49
|
puts "6. Ruby version differ since it has single instead of double quotes around strings"
|
45
50
|
#pexec "diff meta_ruby_compiler_from_c.rb meta_ruby_compiler.rb"
|
46
51
|
puts "7. But we can generate again and ensure it is a meta compiler"
|
47
|
-
pexec "ruby -I. meta_ruby_compiler.rb
|
52
|
+
pexec "ruby -I. meta_ruby_compiler.rb #{syntaxf} > meta_ruby_compiler2.rb"
|
48
53
|
pexec "diff meta_ruby_compiler.rb meta_ruby_compiler2.rb"
|
49
54
|
puts "8. One more round just to show off... :)"
|
50
|
-
pexec "ruby -I. meta_ruby_compiler2.rb
|
55
|
+
pexec "ruby -I. meta_ruby_compiler2.rb #{syntaxf} > meta_ruby_compiler3.rb"
|
51
56
|
pexec "diff meta_ruby_compiler.rb meta_ruby_compiler3.rb"
|
52
|
-
puts "Summary:\nCreated a #{loc('meta_ruby_compiler.rb')} line meta-II meta compiler from a #{loc(
|
57
|
+
puts "Summary:\nCreated a #{loc('meta_ruby_compiler.rb')} line meta-II meta compiler from a #{loc(syntaxf)} line meta-II spec\n\n"
|
58
|
+
ensure_is_meta "meta_ruby_compiler.rb", syntaxf, "../bin/meta_compile"
|
53
59
|
end
|
54
60
|
end
|
55
61
|
|
56
62
|
# Bootstrap a syntax from the meta_compile compiler
|
57
|
-
def
|
58
|
-
|
59
|
-
|
63
|
+
def bootstrap_from_ruby(syntaxFile, targetName = "tgen.rb")
|
64
|
+
syntaxf = File.join("syntaxes", syntaxFile)
|
65
|
+
# Assumes the ruby version has been bootstrapped from C and is installed as meta_compile
|
66
|
+
pexec "ruby bin/meta_compile #{syntaxf} > #{targetName}"
|
67
|
+
ensure_is_meta(targetName, syntaxf, targetName)
|
60
68
|
end
|
61
69
|
|
62
70
|
def diff_files(f1, f2)
|
63
71
|
File.read(f1) != File.read(f2)
|
64
72
|
end
|
65
73
|
|
66
|
-
def ensure_is_meta(generatedFile, specFile)
|
74
|
+
def ensure_is_meta(generatedFile, specFile, saveAsTarget = nil)
|
67
75
|
pexec "ruby #{generatedFile} #{specFile} > t.rb"
|
68
76
|
pexec "ruby t.rb #{specFile} > t2.rb"
|
69
77
|
# Obviously we should now be able to go on and on... :)
|
@@ -74,35 +82,74 @@ def ensure_is_meta(generatedFile, specFile)
|
|
74
82
|
exit(-1)
|
75
83
|
else
|
76
84
|
puts "YES #{generatedFile} is meta!!! (generated from #{specFile})"
|
85
|
+
if saveAsTarget
|
86
|
+
FileUtils.mv("t2.rb", saveAsTarget)
|
87
|
+
puts "Saved bootstrapped file as #{saveAsTarget}"
|
88
|
+
end
|
89
|
+
return true
|
77
90
|
end
|
78
91
|
ensure
|
79
92
|
FileUtils.rm_f Dir.glob("t*.rb")
|
80
93
|
end
|
81
94
|
end
|
82
95
|
|
83
|
-
|
84
|
-
|
85
|
-
FileUtils.cp "bootstrap/meta_ruby_compiler.rb", "bin/meta_compile"
|
96
|
+
def create_main_binary(sourceBinary)
|
97
|
+
FileUtils.cp sourceBinary, "bin/meta_compile"
|
86
98
|
FileUtils.chmod 0755, "bin/meta_compile"
|
87
|
-
puts "Created binary in bin/meta_compile"
|
88
|
-
end
|
89
|
-
|
90
|
-
desc "Ensure it is a meta compiler"
|
91
|
-
task :ensure_meta => [:make_bin] do
|
92
|
-
ensure_is_meta "bin/meta_compile", "syntaxes/meta_to_ruby_minimal.meta"
|
99
|
+
puts "Created main binary in bin/meta_compile from #{sourceBinary}"
|
93
100
|
end
|
94
101
|
|
95
102
|
def bootstrap_with_stepping_stone(syntaxFile, target)
|
96
103
|
syntaxf = File.join("syntaxes", syntaxFile)
|
97
104
|
stepstonef = File.join("syntaxes", "stepping_stone_" + syntaxFile)
|
98
|
-
pexec "meta_compile #{stepstonef} > tgen_stepstone.rb"
|
105
|
+
pexec "ruby bin/meta_compile #{stepstonef} > tgen_stepstone.rb"
|
99
106
|
pexec "ruby tgen_stepstone.rb #{syntaxf} > #{target}"
|
100
|
-
ensure_is_meta(target, syntaxf)
|
107
|
+
ensure_is_meta(target, syntaxf, target)
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "Bootstrap the Ruby meta compiler (uses the minimal syntax)"
|
111
|
+
task :boot do
|
112
|
+
bootstrap_from_c "meta_to_ruby_minimal.meta"
|
113
|
+
end
|
114
|
+
|
115
|
+
desc "Bootstrap and set binary file permissions"
|
116
|
+
task :bootstrap => :boot do
|
117
|
+
FileUtils.chmod 0755, "bin/meta_compile"
|
118
|
+
end
|
119
|
+
|
120
|
+
desc "Bootstrap the minimal Ruby meta compiler (from the Ruby meta compiler)"
|
121
|
+
task :boot_minimal => :bootstrap do
|
122
|
+
bootstrap_from_ruby "meta_to_ruby_minimal.meta", "bin/metac_minimal"
|
123
|
+
end
|
124
|
+
|
125
|
+
desc "Make main gem binary from the minimal ruby meta compiler"
|
126
|
+
task :make_bin_minimal => :boot_minimal do
|
127
|
+
create_main_binary "bin/metac_minimal"
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "Ensure minimal ruby meta it is a meta compiler"
|
131
|
+
task :ensure_meta_minimal => :make_bin_minimal do
|
132
|
+
ensure_is_meta "bin/meta_compile", "syntaxes/meta_to_ruby_minimal.meta"
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "Bootstrap the readable meta compiler (from the Ruby meta compiler)"
|
136
|
+
task :boot_readable => :make_bin_minimal do
|
137
|
+
bootstrap_from_ruby "meta_to_ruby.meta", "bin/metac_readable"
|
138
|
+
end
|
139
|
+
|
140
|
+
desc "Make main gem binary from the readable ruby meta compiler"
|
141
|
+
task :make_bin_readable => :boot_readable do
|
142
|
+
create_main_binary "bin/metac_readable"
|
143
|
+
end
|
144
|
+
|
145
|
+
desc "Ensure readable ruby meta it is a meta compiler"
|
146
|
+
task :ensure_meta_readable => :make_bin_readable do
|
147
|
+
ensure_is_meta "bin/meta_compile", "syntaxes/meta_to_ruby.meta"
|
101
148
|
end
|
102
149
|
|
103
150
|
desc "Bootstrap the meta compiler that accepts regexps"
|
104
|
-
task :
|
105
|
-
bootstrap_with_stepping_stone("meta_to_ruby_minimal_with_regexps.meta", "bin/
|
151
|
+
task :boot_regexp => :make_bin_minimal do
|
152
|
+
bootstrap_with_stepping_stone("meta_to_ruby_minimal_with_regexps.meta", "bin/metac_regexp")
|
106
153
|
end
|
107
154
|
|
108
155
|
def run_example(compiler, example)
|
@@ -112,8 +159,8 @@ def run_example(compiler, example)
|
|
112
159
|
pexec "ruby #{compiler} #{example}"
|
113
160
|
end
|
114
161
|
|
115
|
-
def
|
116
|
-
pexec "ruby bin/
|
162
|
+
def metac_regexp_run_examples(syntaxFile, *inputs)
|
163
|
+
pexec "ruby bin/metac_regexp #{syntaxFile} > tas.rb"
|
117
164
|
inputs.each do |i|
|
118
165
|
run_example("tas.rb", "inputs/#{i}")
|
119
166
|
end
|
@@ -121,22 +168,21 @@ end
|
|
121
168
|
|
122
169
|
desc "Compile all inputs for the assignments syntax"
|
123
170
|
task :ex_ass do
|
124
|
-
|
171
|
+
metac_regexp_run_examples "syntaxes/assignments.meta", "assignments.input1"
|
125
172
|
end
|
126
173
|
|
127
174
|
desc "Update line counts in README.template.md to make README.md"
|
128
|
-
task :update => [:
|
129
|
-
|
130
|
-
|
131
|
-
readme =
|
132
|
-
readme = readme.gsub("%%
|
175
|
+
task :update => [:boot_minimal, :boot_regexp, :boot_readable] do
|
176
|
+
readme = File.read("README.template.md")
|
177
|
+
readme = readme.gsub("%%RMetaII_SPEC_LOC%%", loc('syntaxes/meta_to_ruby_minimal.meta').to_s)
|
178
|
+
readme = readme.gsub("%%RMetaII_COMPILER_LOC%%", loc('bin/metac_minimal').to_s)
|
179
|
+
readme = readme.gsub("%%RMetaII_READABLE_SPEC_LOC%%", loc('syntaxes/meta_to_ruby.meta').to_s)
|
180
|
+
readme = readme.gsub("%%RMetaII_READABLE_COMPILER_LOC%%", loc('bin/metac_readable').to_s)
|
133
181
|
File.open("README.md", "w") {|f| f.puts readme}
|
134
182
|
end
|
135
183
|
|
136
|
-
task :default => :ensure_meta
|
137
|
-
|
138
184
|
desc "Build the gem"
|
139
|
-
task :build_gem => [:
|
185
|
+
task :build_gem => [:update, :make_bin_readable] do
|
140
186
|
Rake::Task["clean"].invoke
|
141
187
|
FileUtils.rm_f Dir.glob("meta_compile-*.gem")
|
142
188
|
pexec "gem build meta_compile.gemspec"
|
@@ -148,7 +194,7 @@ task :install_gem => [:build_gem] do
|
|
148
194
|
end
|
149
195
|
|
150
196
|
desc "Deploy gem"
|
151
|
-
task :deploy =>
|
197
|
+
task :deploy => :build_gem do
|
152
198
|
pexec "gem push meta_compile*.gem"
|
153
199
|
end
|
154
200
|
|
@@ -164,4 +210,6 @@ task :clobber => [:clean] do
|
|
164
210
|
FileUtils.rm_f %w{meta_ruby_compiler.rb meta_compile*.gem}
|
165
211
|
end
|
166
212
|
FileUtils.rm_f Dir.glob("meta_compile-*.gem")
|
167
|
-
end
|
213
|
+
end
|
214
|
+
|
215
|
+
task :default => :make_bin_readable
|
data/bin/meta_compile
CHANGED
@@ -1,231 +1,331 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require "strscan"
|
3
|
-
class
|
4
|
-
|
5
|
-
def
|
6
|
-
|
7
|
-
|
3
|
+
class StringScanner
|
4
|
+
# Add scan for string since base StringScanner lack that...
|
5
|
+
def scan_str(string)
|
6
|
+
return nil unless self.peek(string.length) == string
|
7
|
+
self.pos += string.length # Advance the position in the input string
|
8
|
+
return string
|
9
|
+
end
|
10
|
+
alias :old_scan :scan
|
11
|
+
def scan(strOrRegexp)
|
12
|
+
String === strOrRegexp ? scan_str(strOrRegexp) : old_scan(strOrRegexp)
|
13
|
+
end
|
8
14
|
end
|
15
|
+
class MetaCompiler_program
|
16
|
+
$compiler_class = self # Save class in global var for later reference below
|
17
|
+
def compile_string(string, outFile)
|
18
|
+
@in, @out = StringScanner.new(string), outFile
|
19
|
+
compile_program # call the main compile method to start compiling
|
20
|
+
end
|
21
|
+
# Scan for a string or regexp and update state based on match. Skips leading whitespace.
|
22
|
+
def scan strOrRegexp
|
23
|
+
@in.scan /\s*/ # Skip whitespace
|
24
|
+
@match = @in.scan strOrRegexp
|
25
|
+
# Since nil is same as false in Ruby we can set the flag to the matched token
|
26
|
+
@last_matched_token = @match if @match # Update last matched only if a token was matched
|
27
|
+
end
|
28
|
+
def report_error
|
29
|
+
pre_lines = @in.string[0, @in.pos].split("\n") # lines of input up to current position
|
30
|
+
post_lines = @in.rest.split("\n") # lines of input after current position
|
31
|
+
message = "PARSE ERROR at line #{pre_lines.length}:\n " + pre_lines.last.inspect + " @ "
|
32
|
+
message += post_lines.first.inspect
|
33
|
+
message += "\n Last matched token: #{@last_matched_token}"
|
34
|
+
raise message
|
35
|
+
end
|
36
|
+
def self.compile_file(inFile, out = nil)
|
37
|
+
outfh = (out == nil ? STDOUT : File.open(out, "w"))
|
38
|
+
self.new.compile_string(File.read(inFile), outfh)
|
39
|
+
outfh.close if out == nil
|
40
|
+
end
|
9
41
|
def compile_outarg
|
10
42
|
begin
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
15
|
-
@o.print "\n"
|
43
|
+
scan '$'
|
44
|
+
if @match
|
45
|
+
@out.print '@out.print @match # Print last matched token on output stream' # Print literal string on output stream
|
46
|
+
@out.print "\n" # Print newline on output stream
|
16
47
|
end
|
17
|
-
break if @
|
18
|
-
|
19
|
-
if @
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
48
|
+
break if @match
|
49
|
+
scan /\047[^\047]*\047/
|
50
|
+
if @match
|
51
|
+
@out.print '@out.print ' # Print literal string on output stream
|
52
|
+
@out.print @match # Print last matched token on output stream
|
53
|
+
@out.print ' # Print literal string on output stream' # Print literal string on output stream
|
54
|
+
@out.print "\n" # Print newline on output stream
|
23
55
|
end
|
24
|
-
end while false
|
56
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
25
57
|
end
|
26
58
|
def compile_out
|
27
59
|
begin
|
28
|
-
|
29
|
-
|
30
|
-
if @f
|
60
|
+
scan '<'
|
61
|
+
if @match
|
31
62
|
begin
|
32
|
-
compile_outarg
|
33
|
-
end while @
|
34
|
-
@
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@
|
40
|
-
|
41
|
-
end
|
42
|
-
end while false
|
63
|
+
compile_outarg # Call the method for a rule
|
64
|
+
end while @match # Loop while there is a match
|
65
|
+
@match = true # Since also zero matches is ok set flag here.
|
66
|
+
report_error() unless @match
|
67
|
+
scan '>'
|
68
|
+
report_error() unless @match
|
69
|
+
@out.print '@out.print "\n" # Print newline on output stream' # Print literal string on output stream
|
70
|
+
@out.print "\n" # Print newline on output stream
|
71
|
+
end
|
72
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
43
73
|
end
|
44
74
|
def compile_exp3
|
45
75
|
begin
|
46
|
-
|
47
|
-
if @
|
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
|
-
if @
|
73
|
-
|
74
|
-
@
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@
|
83
|
-
@
|
84
|
-
|
85
|
-
end
|
86
|
-
break if @
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
@
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
@
|
95
|
-
@
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
compile_exp3
|
100
|
-
raise("error at: " + @i.rest.split("\n")[0]) if !@f
|
101
|
-
@o.print 'end while @f'
|
102
|
-
@o.print "\n"
|
103
|
-
@o.print '@f = true'
|
104
|
-
@o.print "\n"
|
105
|
-
end
|
106
|
-
end while false
|
76
|
+
scan /[A-Za-z]+[A-Za-z0-9_]+/
|
77
|
+
if @match
|
78
|
+
@out.print 'compile_' # Print literal string on output stream
|
79
|
+
@out.print @match # Print last matched token on output stream
|
80
|
+
@out.print ' # Call the method for a rule' # Print literal string on output stream
|
81
|
+
@out.print "\n" # Print newline on output stream
|
82
|
+
end
|
83
|
+
break if @match
|
84
|
+
scan /\047[^\047]*\047/
|
85
|
+
if @match
|
86
|
+
@out.print 'scan ' # Print literal string on output stream
|
87
|
+
@out.print @match # Print last matched token on output stream
|
88
|
+
@out.print "\n" # Print newline on output stream
|
89
|
+
end
|
90
|
+
break if @match
|
91
|
+
scan '.id'
|
92
|
+
if @match
|
93
|
+
@out.print 'scan /[A-Za-z]+[A-Za-z0-9_]+/' # Print literal string on output stream
|
94
|
+
@out.print "\n" # Print newline on output stream
|
95
|
+
end
|
96
|
+
break if @match
|
97
|
+
scan '.string'
|
98
|
+
if @match
|
99
|
+
@out.print 'scan /\047[^\047]*\047/' # Print literal string on output stream
|
100
|
+
@out.print "\n" # Print newline on output stream
|
101
|
+
end
|
102
|
+
break if @match
|
103
|
+
scan '('
|
104
|
+
if @match
|
105
|
+
compile_exp1 # Call the method for a rule
|
106
|
+
report_error() unless @match
|
107
|
+
scan ')'
|
108
|
+
report_error() unless @match
|
109
|
+
end
|
110
|
+
break if @match
|
111
|
+
scan '.e'
|
112
|
+
if @match
|
113
|
+
@out.print '@match = true # .e means empty so always matches => set flag.' # Print literal string on output stream
|
114
|
+
@out.print "\n" # Print newline on output stream
|
115
|
+
end
|
116
|
+
break if @match
|
117
|
+
scan '*'
|
118
|
+
if @match
|
119
|
+
@out.print 'begin' # Print literal string on output stream
|
120
|
+
@out.print "\n" # Print newline on output stream
|
121
|
+
compile_exp3 # Call the method for a rule
|
122
|
+
report_error() unless @match
|
123
|
+
@out.print 'end while @match # Loop while there is a match' # Print literal string on output stream
|
124
|
+
@out.print "\n" # Print newline on output stream
|
125
|
+
@out.print '@match = true # Since also zero matches is ok set flag here.' # Print literal string on output stream
|
126
|
+
@out.print "\n" # Print newline on output stream
|
127
|
+
end
|
128
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
107
129
|
end
|
108
130
|
def compile_exp2
|
109
131
|
begin
|
110
132
|
begin
|
111
|
-
compile_exp3
|
112
|
-
if @
|
113
|
-
@
|
114
|
-
@
|
115
|
-
end
|
116
|
-
break if @
|
117
|
-
compile_out
|
118
|
-
if @
|
119
|
-
@
|
120
|
-
@
|
121
|
-
end
|
122
|
-
end while false
|
123
|
-
if @
|
133
|
+
compile_exp3 # Call the method for a rule
|
134
|
+
if @match
|
135
|
+
@out.print 'if @match' # Print literal string on output stream
|
136
|
+
@out.print "\n" # Print newline on output stream
|
137
|
+
end
|
138
|
+
break if @match
|
139
|
+
compile_out # Call the method for a rule
|
140
|
+
if @match
|
141
|
+
@out.print 'if true' # Print literal string on output stream
|
142
|
+
@out.print "\n" # Print newline on output stream
|
143
|
+
end
|
144
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
145
|
+
if @match
|
124
146
|
begin
|
125
147
|
begin
|
126
|
-
compile_exp3
|
127
|
-
if @
|
128
|
-
@
|
129
|
-
@
|
148
|
+
compile_exp3 # Call the method for a rule
|
149
|
+
if @match
|
150
|
+
@out.print 'report_error() unless @match' # Print literal string on output stream
|
151
|
+
@out.print "\n" # Print newline on output stream
|
130
152
|
end
|
131
|
-
break if @
|
132
|
-
compile_out
|
133
|
-
if @
|
153
|
+
break if @match
|
154
|
+
compile_out # Call the method for a rule
|
155
|
+
if @match
|
134
156
|
end
|
135
|
-
end while false
|
136
|
-
end while @
|
137
|
-
@
|
138
|
-
|
139
|
-
@
|
140
|
-
@
|
157
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
158
|
+
end while @match # Loop while there is a match
|
159
|
+
@match = true # Since also zero matches is ok set flag here.
|
160
|
+
report_error() unless @match
|
161
|
+
@out.print 'end' # Print literal string on output stream
|
162
|
+
@out.print "\n" # Print newline on output stream
|
141
163
|
end
|
142
|
-
end while false
|
164
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
143
165
|
end
|
144
166
|
def compile_exp1
|
145
167
|
begin
|
146
|
-
@
|
147
|
-
@
|
168
|
+
@out.print 'begin' # Print literal string on output stream
|
169
|
+
@out.print "\n" # Print newline on output stream
|
148
170
|
if true
|
149
|
-
compile_exp2
|
150
|
-
|
171
|
+
compile_exp2 # Call the method for a rule
|
172
|
+
report_error() unless @match
|
151
173
|
begin
|
152
174
|
begin
|
153
|
-
|
154
|
-
|
155
|
-
if @
|
156
|
-
@
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
161
|
-
end while
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
@
|
166
|
-
|
167
|
-
end
|
168
|
-
end while false
|
175
|
+
scan '|'
|
176
|
+
if @match
|
177
|
+
@out.print 'break if @match' # Print literal string on output stream
|
178
|
+
@out.print "\n" # Print newline on output stream
|
179
|
+
compile_exp2 # Call the method for a rule
|
180
|
+
report_error() unless @match
|
181
|
+
end
|
182
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
183
|
+
end while @match # Loop while there is a match
|
184
|
+
@match = true # Since also zero matches is ok set flag here.
|
185
|
+
report_error() unless @match
|
186
|
+
@out.print 'end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise' # Print literal string on output stream
|
187
|
+
@out.print "\n" # Print newline on output stream
|
188
|
+
end
|
189
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
169
190
|
end
|
170
191
|
def compile_rule
|
171
192
|
begin
|
172
|
-
|
173
|
-
if @
|
174
|
-
@
|
175
|
-
@
|
176
|
-
@
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
@
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
end
|
188
|
-
end while false
|
193
|
+
scan /[A-Za-z]+[A-Za-z0-9_]+/
|
194
|
+
if @match
|
195
|
+
@out.print 'def compile_' # Print literal string on output stream
|
196
|
+
@out.print @match # Print last matched token on output stream
|
197
|
+
@out.print "\n" # Print newline on output stream
|
198
|
+
scan '='
|
199
|
+
report_error() unless @match
|
200
|
+
compile_exp1 # Call the method for a rule
|
201
|
+
report_error() unless @match
|
202
|
+
scan ';'
|
203
|
+
report_error() unless @match
|
204
|
+
@out.print 'end' # Print literal string on output stream
|
205
|
+
@out.print "\n" # Print newline on output stream
|
206
|
+
end
|
207
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
189
208
|
end
|
190
209
|
def compile_program
|
191
210
|
begin
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
@
|
198
|
-
@
|
199
|
-
@
|
200
|
-
@
|
201
|
-
@
|
202
|
-
@
|
203
|
-
@
|
204
|
-
@
|
205
|
-
@
|
206
|
-
@
|
207
|
-
@
|
208
|
-
@
|
209
|
-
@
|
210
|
-
@
|
211
|
-
@
|
212
|
-
@
|
213
|
-
@
|
214
|
-
@
|
211
|
+
scan '.syntax'
|
212
|
+
if @match
|
213
|
+
scan /[A-Za-z]+[A-Za-z0-9_]+/
|
214
|
+
report_error() unless @match
|
215
|
+
@out.print '#!/usr/bin/env ruby' # Print literal string on output stream
|
216
|
+
@out.print "\n" # Print newline on output stream
|
217
|
+
@out.print 'require "strscan"' # Print literal string on output stream
|
218
|
+
@out.print "\n" # Print newline on output stream
|
219
|
+
@out.print 'class StringScanner' # Print literal string on output stream
|
220
|
+
@out.print "\n" # Print newline on output stream
|
221
|
+
@out.print ' # Add scan for string since base StringScanner lack that...' # Print literal string on output stream
|
222
|
+
@out.print "\n" # Print newline on output stream
|
223
|
+
@out.print ' def scan_str(string)' # Print literal string on output stream
|
224
|
+
@out.print "\n" # Print newline on output stream
|
225
|
+
@out.print ' return nil unless self.peek(string.length) == string' # Print literal string on output stream
|
226
|
+
@out.print "\n" # Print newline on output stream
|
227
|
+
@out.print ' self.pos += string.length # Advance the position in the input string' # Print literal string on output stream
|
228
|
+
@out.print "\n" # Print newline on output stream
|
229
|
+
@out.print ' return string' # Print literal string on output stream
|
230
|
+
@out.print "\n" # Print newline on output stream
|
231
|
+
@out.print ' end' # Print literal string on output stream
|
232
|
+
@out.print "\n" # Print newline on output stream
|
233
|
+
@out.print ' alias :old_scan :scan' # Print literal string on output stream
|
234
|
+
@out.print "\n" # Print newline on output stream
|
235
|
+
@out.print ' def scan(strOrRegexp)' # Print literal string on output stream
|
236
|
+
@out.print "\n" # Print newline on output stream
|
237
|
+
@out.print ' String === strOrRegexp ? scan_str(strOrRegexp) : old_scan(strOrRegexp)' # Print literal string on output stream
|
238
|
+
@out.print "\n" # Print newline on output stream
|
239
|
+
@out.print ' end' # Print literal string on output stream
|
240
|
+
@out.print "\n" # Print newline on output stream
|
241
|
+
@out.print 'end' # Print literal string on output stream
|
242
|
+
@out.print "\n" # Print newline on output stream
|
243
|
+
@out.print 'class MetaCompiler_' # Print literal string on output stream
|
244
|
+
@out.print @match # Print last matched token on output stream
|
245
|
+
@out.print "\n" # Print newline on output stream
|
246
|
+
@out.print ' $compiler_class = self # Save class in global var for later reference below' # Print literal string on output stream
|
247
|
+
@out.print "\n" # Print newline on output stream
|
248
|
+
@out.print ' def compile_string(string, outFile)' # Print literal string on output stream
|
249
|
+
@out.print "\n" # Print newline on output stream
|
250
|
+
@out.print ' @in, @out = StringScanner.new(string), outFile' # Print literal string on output stream
|
251
|
+
@out.print "\n" # Print newline on output stream
|
252
|
+
@out.print ' compile_' # Print literal string on output stream
|
253
|
+
@out.print @match # Print last matched token on output stream
|
254
|
+
@out.print ' # call the main compile method to start compiling' # Print literal string on output stream
|
255
|
+
@out.print "\n" # Print newline on output stream
|
256
|
+
@out.print ' end' # Print literal string on output stream
|
257
|
+
@out.print "\n" # Print newline on output stream
|
258
|
+
@out.print ' # Scan for a string or regexp and update state based on match. Skips leading whitespace.' # Print literal string on output stream
|
259
|
+
@out.print "\n" # Print newline on output stream
|
260
|
+
@out.print ' def scan strOrRegexp' # Print literal string on output stream
|
261
|
+
@out.print "\n" # Print newline on output stream
|
262
|
+
@out.print ' @in.scan /\s*/ # Skip whitespace' # Print literal string on output stream
|
263
|
+
@out.print "\n" # Print newline on output stream
|
264
|
+
@out.print ' @match = @in.scan strOrRegexp' # Print literal string on output stream
|
265
|
+
@out.print "\n" # Print newline on output stream
|
266
|
+
@out.print ' # Since nil is same as false in Ruby we can set the flag to the matched token' # Print literal string on output stream
|
267
|
+
@out.print "\n" # Print newline on output stream
|
268
|
+
@out.print ' @last_matched_token = @match if @match # Update last matched only if a token was matched' # Print literal string on output stream
|
269
|
+
@out.print "\n" # Print newline on output stream
|
270
|
+
@out.print ' end' # Print literal string on output stream
|
271
|
+
@out.print "\n" # Print newline on output stream
|
272
|
+
@out.print ' def report_error' # Print literal string on output stream
|
273
|
+
@out.print "\n" # Print newline on output stream
|
274
|
+
@out.print ' pre_lines = @in.string[0, @in.pos].split("\n") # lines of input up to current position' # Print literal string on output stream
|
275
|
+
@out.print "\n" # Print newline on output stream
|
276
|
+
@out.print ' post_lines = @in.rest.split("\n") # lines of input after current position' # Print literal string on output stream
|
277
|
+
@out.print "\n" # Print newline on output stream
|
278
|
+
@out.print ' message = "PARSE ERROR at line #{pre_lines.length}:\n " + pre_lines.last.inspect + " @ "' # Print literal string on output stream
|
279
|
+
@out.print "\n" # Print newline on output stream
|
280
|
+
@out.print ' message += post_lines.first.inspect' # Print literal string on output stream
|
281
|
+
@out.print "\n" # Print newline on output stream
|
282
|
+
@out.print ' message += "\n Last matched token: #{@last_matched_token}"' # Print literal string on output stream
|
283
|
+
@out.print "\n" # Print newline on output stream
|
284
|
+
@out.print ' raise message' # Print literal string on output stream
|
285
|
+
@out.print "\n" # Print newline on output stream
|
286
|
+
@out.print ' end' # Print literal string on output stream
|
287
|
+
@out.print "\n" # Print newline on output stream
|
288
|
+
@out.print ' def self.compile_file(inFile, out = nil)' # Print literal string on output stream
|
289
|
+
@out.print "\n" # Print newline on output stream
|
290
|
+
@out.print ' outfh = (out == nil ? STDOUT : File.open(out, "w"))' # Print literal string on output stream
|
291
|
+
@out.print "\n" # Print newline on output stream
|
292
|
+
@out.print ' self.new.compile_string(File.read(inFile), outfh)' # Print literal string on output stream
|
293
|
+
@out.print "\n" # Print newline on output stream
|
294
|
+
@out.print ' outfh.close if out == nil' # Print literal string on output stream
|
295
|
+
@out.print "\n" # Print newline on output stream
|
296
|
+
@out.print ' end' # Print literal string on output stream
|
297
|
+
@out.print "\n" # Print newline on output stream
|
215
298
|
begin
|
216
|
-
compile_rule
|
217
|
-
end while @
|
218
|
-
@
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
@
|
224
|
-
@
|
225
|
-
@
|
226
|
-
@
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
299
|
+
compile_rule # Call the method for a rule
|
300
|
+
end while @match # Loop while there is a match
|
301
|
+
@match = true # Since also zero matches is ok set flag here.
|
302
|
+
report_error() unless @match
|
303
|
+
scan '.end'
|
304
|
+
report_error() unless @match
|
305
|
+
@out.print 'end' # Print literal string on output stream
|
306
|
+
@out.print "\n" # Print newline on output stream
|
307
|
+
@out.print 'if ARGV.length < 1 || ARGV.length > 2' # Print literal string on output stream
|
308
|
+
@out.print "\n" # Print newline on output stream
|
309
|
+
@out.print ' puts "ERROR: wrong number of parameters\n\n"' # Print literal string on output stream
|
310
|
+
@out.print "\n" # Print newline on output stream
|
311
|
+
@out.print ' puts "Usage: #{File.basename($0)} <input_file> [output_file]"' # Print literal string on output stream
|
312
|
+
@out.print "\n" # Print newline on output stream
|
313
|
+
@out.print ' exit(-1)' # Print literal string on output stream
|
314
|
+
@out.print "\n" # Print newline on output stream
|
315
|
+
@out.print 'else' # Print literal string on output stream
|
316
|
+
@out.print "\n" # Print newline on output stream
|
317
|
+
@out.print ' $compiler_class.compile_file(ARGV[0], ARGV[1])' # Print literal string on output stream
|
318
|
+
@out.print "\n" # Print newline on output stream
|
319
|
+
@out.print 'end' # Print literal string on output stream
|
320
|
+
@out.print "\n" # Print newline on output stream
|
321
|
+
end
|
322
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
323
|
+
end
|
324
|
+
end
|
325
|
+
if ARGV.length < 1 || ARGV.length > 2
|
326
|
+
puts "ERROR: wrong number of parameters\n\n"
|
327
|
+
puts "Usage: #{File.basename($0)} <input_file> [output_file]"
|
328
|
+
exit(-1)
|
329
|
+
else
|
330
|
+
$compiler_class.compile_file(ARGV[0], ARGV[1])
|
331
|
+
end
|