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 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/meta_compile) is 231 lines of Ruby code.
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
- + Very bad/little error handling
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 bootstrap_re
65
+ rake boot_regexp
66
66
 
67
67
  Then we create a compiler for the assignments syntax:
68
68
 
69
- ruby bin/metacomp_re syntaxes/assignments.meta > tas.rb
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 :bootstrap_c do
13
- Dir.chdir "bootstrap"
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_for_c.txt description of itself"
17
- pexec "./bootstrapped_c meta_for_c.txt 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_for_c.txt 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"
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
- desc "Bootstrap the Ruby Meta-II compiler"
32
- task :bootstrap do
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 ../syntaxes/meta_to_ruby_minimal.meta compile_to_ruby.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 ../syntaxes/meta_to_ruby_minimal.meta meta_ruby_compiler_from_c.rb"
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 ../syntaxes/meta_to_ruby_minimal.meta > meta_ruby_compiler.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 ../syntaxes/meta_to_ruby_minimal.meta > meta_ruby_compiler2.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 ../syntaxes/meta_to_ruby_minimal.meta > meta_ruby_compiler3.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('../syntaxes/meta_to_ruby_minimal.meta')} line meta-II spec\n\n"
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 bootstrap_from_meta_compile(syntaxFile)
58
- pexec "meta_compile #{syntaxFile} > tgen.rb"
59
- ensure_is_meta("tgen.rb", syntaxFile)
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
- desc "Make binary from the bootstrapped ruby meta-II compiler"
84
- task :make_bin => [:bootstrap] do
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 :bootstrap_re do
105
- bootstrap_with_stepping_stone("meta_to_ruby_minimal_with_regexps.meta", "bin/metacomp_re")
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 metacomp_re_run_examples(syntaxFile, *inputs)
116
- pexec "ruby bin/metacomp_re #{syntaxFile} > tas.rb"
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
- metacomp_re_run_examples "syntaxes/assignments.meta", "assignments.input1"
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 => [:bootstrap] do
129
- rmeta2_compiler_loc = loc('bootstrap/meta_ruby_compiler.rb')
130
- rmeta2_spec_loc = loc('syntaxes/meta_to_ruby_minimal.meta')
131
- readme = File.read("README.template.md").gsub("%%RMetaII_SPEC_LOC%%", rmeta2_spec_loc.to_s)
132
- readme = readme.gsub("%%RMetaII_COMPILER_LOC%%", rmeta2_compiler_loc.to_s)
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 => [:make_bin] do
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 => [:update, :install_gem] do
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
@@ -1,231 +1,331 @@
1
1
  #!/usr/bin/env ruby
2
2
  require "strscan"
3
- class C_program
4
- $c = self
5
- def compile(str, out)
6
- @i, @o = StringScanner.new(str), out
7
- compile_program
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
- @i.scan /\s*/; s='$'; l=s.length;
12
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
13
- if @f
14
- @o.print '@o.print @t'
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 @f
18
- @i.scan /\s*/; @f = @t = @i.scan /\047[^\047]*\047/
19
- if @f
20
- @o.print '@o.print '
21
- @o.print @t
22
- @o.print "\n"
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
- @i.scan /\s*/; s='<'; l=s.length;
29
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
30
- if @f
60
+ scan '<'
61
+ if @match
31
62
  begin
32
- compile_outarg
33
- end while @f
34
- @f = true
35
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
36
- @i.scan /\s*/; s='>'; l=s.length;
37
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
38
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
39
- @o.print '@o.print "\n"'
40
- @o.print "\n"
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
- @i.scan /\s*/; @f = @t = @i.scan /[A-Za-z]+[A-Za-z0-9_]+/
47
- if @f
48
- @o.print 'compile_'
49
- @o.print @t
50
- @o.print "\n"
51
- end
52
- break if @f
53
- @i.scan /\s*/; @f = @t = @i.scan /\047[^\047]*\047/
54
- if @f
55
- @o.print '@i.scan /\s*/; s='
56
- @o.print @t
57
- @o.print '; l=s.length;'
58
- @o.print "\n"
59
- @o.print '@f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil'
60
- @o.print "\n"
61
- end
62
- break if @f
63
- @i.scan /\s*/; s='.id'; l=s.length;
64
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
65
- if @f
66
- @o.print '@i.scan /\s*/; @f = @t = @i.scan /[A-Za-z]+[A-Za-z0-9_]+/'
67
- @o.print "\n"
68
- end
69
- break if @f
70
- @i.scan /\s*/; s='.string'; l=s.length;
71
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
72
- if @f
73
- @o.print '@i.scan /\s*/; @f = @t = @i.scan /\047[^\047]*\047/'
74
- @o.print "\n"
75
- end
76
- break if @f
77
- @i.scan /\s*/; s='('; l=s.length;
78
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
79
- if @f
80
- compile_exp1
81
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
82
- @i.scan /\s*/; s=')'; l=s.length;
83
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
84
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
85
- end
86
- break if @f
87
- @i.scan /\s*/; s='.e'; l=s.length;
88
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
89
- if @f
90
- @o.print '@f = true'
91
- @o.print "\n"
92
- end
93
- break if @f
94
- @i.scan /\s*/; s='*'; l=s.length;
95
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
96
- if @f
97
- @o.print 'begin'
98
- @o.print "\n"
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 @f
113
- @o.print 'if @f'
114
- @o.print "\n"
115
- end
116
- break if @f
117
- compile_out
118
- if @f
119
- @o.print 'if true'
120
- @o.print "\n"
121
- end
122
- end while false
123
- if @f
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 @f
128
- @o.print 'raise("error at: " + @i.rest.split("\n")[0]) if !@f'
129
- @o.print "\n"
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 @f
132
- compile_out
133
- if @f
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 @f
137
- @f = true
138
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
139
- @o.print 'end'
140
- @o.print "\n"
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
- @o.print 'begin'
147
- @o.print "\n"
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
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
171
+ compile_exp2 # Call the method for a rule
172
+ report_error() unless @match
151
173
  begin
152
174
  begin
153
- @i.scan /\s*/; s='|'; l=s.length;
154
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
155
- if @f
156
- @o.print 'break if @f'
157
- @o.print "\n"
158
- compile_exp2
159
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
160
- end
161
- end while false
162
- end while @f
163
- @f = true
164
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
165
- @o.print 'end while false'
166
- @o.print "\n"
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
- @i.scan /\s*/; @f = @t = @i.scan /[A-Za-z]+[A-Za-z0-9_]+/
173
- if @f
174
- @o.print 'def compile_'
175
- @o.print @t
176
- @o.print "\n"
177
- @i.scan /\s*/; s='='; l=s.length;
178
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
179
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
180
- compile_exp1
181
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
182
- @i.scan /\s*/; s=';'; l=s.length;
183
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
184
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
185
- @o.print 'end'
186
- @o.print "\n"
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
- @i.scan /\s*/; s='.syntax'; l=s.length;
193
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
194
- if @f
195
- @i.scan /\s*/; @f = @t = @i.scan /[A-Za-z]+[A-Za-z0-9_]+/
196
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
197
- @o.print '#!/usr/bin/env ruby'
198
- @o.print "\n"
199
- @o.print 'require "strscan"'
200
- @o.print "\n"
201
- @o.print 'class C_'
202
- @o.print @t
203
- @o.print "\n"
204
- @o.print '$c = self'
205
- @o.print "\n"
206
- @o.print 'def compile(str, out)'
207
- @o.print "\n"
208
- @o.print '@i, @o = StringScanner.new(str), out'
209
- @o.print "\n"
210
- @o.print 'compile_'
211
- @o.print @t
212
- @o.print "\n"
213
- @o.print 'end'
214
- @o.print "\n"
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 @f
218
- @f = true
219
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
220
- @i.scan /\s*/; s='.end'; l=s.length;
221
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
222
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
223
- @o.print 'end'
224
- @o.print "\n"
225
- @o.print '$c.new.compile(File.read(ARGV[0]), STDOUT)'
226
- @o.print "\n"
227
- end
228
- end while false
229
- end
230
- end
231
- $c.new.compile(File.read(ARGV[0]), STDOUT)
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