├── README ├── Rakefile ├── lib ├── ometa.rb └── ometa │ ├── bootstrap.rb │ └── runtime.rb ├── ometa ├── andor_opt.ometa ├── bsruby_parser.ometa ├── bsruby_translator.ometa ├── calc.ometa ├── js_parser.ometa ├── null_opt.ometa ├── ometa_opt.ometa ├── ometa_parser.ometa └── ometa_translator.ometa ├── smalltalk.syntax └── test.rb /README: -------------------------------------------------------------------------------- 1 | = About 2 | 3 | Briefly what OMeta is, some links to the ometajs stuff. 4 | 5 | = TODO 6 | 7 | * perhaps cache compiled rb files as "#{ometa_filename}.rb.cache" or 8 | similar, and use them based on mtime / flag combination. 9 | * try to optimize some more. profile wasn't particularly illuminating. 10 | * then write some actual tests. 11 | * i think that super is actually broken at the moment, and its not used 12 | in the standard grammar. the js one uses it, so could test with that. 13 | 14 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $:.unshift File.dirname(__FILE__) + '/lib' 2 | require 'ometa' 3 | 4 | module Bootstrap 5 | class BootstrapError < StandardError 6 | end 7 | 8 | BOOTSTRAP_GRAMMARS = %w[ 9 | null_opt.ometa 10 | andor_opt.ometa 11 | ometa_opt.ometa 12 | bsruby_parser.ometa 13 | bsruby_translator.ometa 14 | ometa_parser.ometa 15 | ometa_translator.ometa 16 | ] 17 | 18 | module_function 19 | 20 | class V0 < Object 21 | end 22 | 23 | def single root=V0, quiet=false 24 | puts "bootstrapping ometa using #{root.inspect}" unless quiet 25 | all = '' 26 | 27 | BOOTSTRAP_GRAMMARS.each do |filename| 28 | puts " - #{filename}" unless quiet 29 | grammar = File.read(File.dirname(__FILE__) + '/ometa/' + filename) 30 | ast = root.const_get('OMetaParser').matchAllwith(grammar, 'grammar') 31 | ast = root.const_get('OMetaOptimizer').matchwith(ast, 'optimizeGrammar') 32 | str = root.const_get('RubyOMetaTranslator').matchwith(ast, 'trans') 33 | # this hack remains at the moment... 34 | str.gsub! /initialize/, 'initialize_hook' 35 | # test its evaluable 36 | obj = eval(str) 37 | # that gives us its name 38 | name = obj.instance_variable_get :@name 39 | all << "#{name} = " << str << "\n\n" 40 | end 41 | 42 | all 43 | end 44 | 45 | # we could also check for AST equivalence, but for now source equivalence will do. 46 | # note that we create the bootstrapped objects within the Bootstrap module, not as 47 | # anonymous objects as they refer to each other by name. anonymous module didn't 48 | # seem to work, but maybe i was doing something wrong... 49 | def bootstrap 50 | v1 = single 51 | eval "module V1; #{v1}; end" 52 | v2 = single V1 53 | eval "module V2; #{v2}; end" 54 | v3 = single V2 55 | eval "module V3; #{v3}; end" 56 | raise BootstrapError, 'second and third builds not equivalent' unless v2 == v3 57 | v3 58 | end 59 | end 60 | 61 | desc 'rebuild bootstrap.rb from the core ometa grammar files' 62 | task :bootstrap do 63 | # this is kind of like make bootstrap in gcc. the idea is to go through 64 | # the whole "compilation" of ometa -> rb 3 times, and ensure that the 65 | # 2nd and 3rd produce the same results. then we replace with our new 66 | # bootstrap code. 67 | begin 68 | str = Bootstrap.bootstrap 69 | rescue Bootstrap::BootstrapError 70 | puts "unable to bootstrap - #{$!}" 71 | exit 1 72 | end 73 | puts 'bootstrap successful!' 74 | open File.dirname(__FILE__) + '/lib/ometa/bootstrap.rb', 'w' do |f| 75 | f.puts <<-end 76 | # 77 | # this file was automatically generated by `rake bootstrap' at #{Time.now}. 78 | # do not modify 79 | # 80 | 81 | end 82 | f.puts str 83 | end 84 | end 85 | 86 | desc 'Simple benchmark' 87 | task :bench do 88 | # all we do is run the complete bootstrap 3 times. 89 | # this currently takes about 44-48 seconds currently on my laptop (~1.4Ghz single core). 90 | # they're pretty simple grammars, so ideally this'd take more like a few seconds... 91 | # ok after using _x* variant functions which use blocks instead of procs, and removing 92 | # some other unnecessary procs, we get this down to 30 s. this also has the nice side 93 | # effect of reducing the size of bootstrap.rb from 13kb down to 10kb. 94 | # can get it down to 28 with a bit more of that. will now try inlining _or() alternations 95 | # to avoid the only remaining need for procs. if that doesn't have an appreciable impact, 96 | # (eg > 5s), i'll revert it and focus on runtime.rb, which is probably where the slowness 97 | # is - implementation of streams, memoization, the _apply function etc. 98 | # just tested it on ruby1.9, and the speed was the same. 99 | t = Time.now 100 | 3.times { Bootstrap.single Bootstrap::V0, true } 101 | puts "3 bootstraps took #{Time.now - t} seconds" 102 | end 103 | 104 | -------------------------------------------------------------------------------- /lib/ometa.rb: -------------------------------------------------------------------------------- 1 | require 'ometa/runtime' 2 | require 'ometa/bootstrap' 3 | -------------------------------------------------------------------------------- /lib/ometa/bootstrap.rb: -------------------------------------------------------------------------------- 1 | # 2 | # this file was automatically generated by `rake bootstrap' at Thu Sep 18 20:24:39 +1000 2008. 3 | # do not modify 4 | # 5 | 6 | NullOptimizer = Class.new(OMeta) do 7 | @name = "NullOptimizer" 8 | def setHelped 9 | 10 | @_didSomething = true 11 | end 12 | 13 | def helped 14 | 15 | _pred(@_didSomething) 16 | end 17 | 18 | def trans 19 | t = ans = nil 20 | _or(proc { (_xform { (t = _apply("anything");_pred(respond_to?(t));ans = _applyWithArgs("apply", t)) }; ans ) }, proc { _apply("anything") }) 21 | end 22 | 23 | def optimize 24 | x = nil 25 | (x = _apply("trans");_apply("helped"); x ) 26 | end 27 | 28 | def Or 29 | xs = nil 30 | (xs = _xmany { _apply("trans") }; ['Or', *xs] ) 31 | end 32 | 33 | def And 34 | xs = nil 35 | (xs = _xmany { _apply("trans") }; ['And', *xs] ) 36 | end 37 | 38 | def Many 39 | x = nil 40 | (x = _apply("trans"); ['Many', x] ) 41 | end 42 | 43 | def Many1 44 | x = nil 45 | (x = _apply("trans"); ['Many1', x] ) 46 | end 47 | 48 | def Set 49 | n = v = nil 50 | (n = _apply("anything");v = _apply("trans"); ['Set', n, v] ) 51 | end 52 | 53 | def Not 54 | x = nil 55 | (x = _apply("trans"); ['Not', x] ) 56 | end 57 | 58 | def Lookahead 59 | x = nil 60 | (x = _apply("trans"); ['Lookahead', x] ) 61 | end 62 | 63 | def Form 64 | x = nil 65 | (x = _apply("trans"); ['Form', x] ) 66 | end 67 | 68 | def Rule 69 | name = ls = body = nil 70 | (name = _apply("anything");ls = _apply("anything");body = _apply("trans"); ['Rule', name, ls, body] ) 71 | end 72 | 73 | def initialize_hook 74 | 75 | @_didSomething = false 76 | end 77 | end 78 | 79 | AndOrOptimizer = Class.new(NullOptimizer) do 80 | @name = "AndOrOptimizer" 81 | def And 82 | x = xs = nil 83 | _or(proc { (x = _apply("trans");_apply("end");_apply("setHelped"); x ) }, proc { (xs = _applyWithArgs("transInside", "And"); ['And', *xs] ) }) 84 | end 85 | 86 | def Or 87 | x = xs = nil 88 | _or(proc { (x = _apply("trans");_apply("end");_apply("setHelped"); x ) }, proc { (xs = _applyWithArgs("transInside", "Or"); ['Or', *xs] ) }) 89 | end 90 | 91 | def transInside 92 | t = xs = ys = x = xs = nil 93 | (t = _apply("anything");_or(proc { (_xform { (_applyWithArgs("exactly", t);xs = _applyWithArgs("transInside", t)) };ys = _applyWithArgs("transInside", t);_apply("setHelped"); xs + ys ) }, proc { (x = _apply("trans");xs = _applyWithArgs("transInside", t); [x, *xs] ) }, proc { [] })) 94 | end 95 | end 96 | 97 | OMetaOptimizer = Class.new(OMeta) do 98 | @name = "OMetaOptimizer" 99 | def optimizeGrammar 100 | n = sn = rs = nil 101 | (_xform { (_applyWithArgs("exactly", "Grammar");n = _apply("anything");sn = _apply("anything");rs = _xmany { _apply("optimizeRule") }) }; ['Grammar', n, sn, *rs] ) 102 | end 103 | 104 | def optimizeRule 105 | r = r = nil 106 | (r = _apply("anything");_xmany { r = _applyWithArgs("foreign", AndOrOptimizer, "optimize", r) }; r ) 107 | end 108 | end 109 | 110 | BSRubyParser = Class.new(OMeta) do 111 | @name = "BSRubyParser" 112 | def eChar 113 | c = nil 114 | _or(proc { (_applyWithArgs("exactly", "\\");c = _apply("char"); unescapeChar c ) }, proc { _apply("char") }) 115 | end 116 | 117 | def tsString 118 | xs = nil 119 | (_applyWithArgs("exactly", "'");xs = _xmany { (_xnot { _applyWithArgs("exactly", "'") };_apply("eChar")) };_applyWithArgs("exactly", "'"); xs.join('') ) 120 | end 121 | 122 | def expr 123 | 124 | (_apply("spaces");_apply("tsString")) 125 | end 126 | 127 | def semAction1 128 | xs = nil 129 | (_apply("spaces");xs = _xmany { (_xnot { _applyWithArgs("seq", "\n") };_apply("anything")) }; xs.join('') ) 130 | end 131 | 132 | def nonBraceChar 133 | 134 | (_xnot { _applyWithArgs("exactly", "{") };_xnot { _applyWithArgs("exactly", "}") };_apply("char")) 135 | end 136 | 137 | def inside 138 | 139 | _or(proc { _apply("innerBraces") }, proc { _apply("nonBraceChar") }) 140 | end 141 | 142 | def innerBraces 143 | xs = nil 144 | (_applyWithArgs("exactly", "{");xs = _xmany { _apply("inside") };_applyWithArgs("exactly", "}"); "{#{xs.join('')}}" ) 145 | end 146 | 147 | def outerBraces 148 | xs = nil 149 | (_applyWithArgs("exactly", "{");xs = _xmany { _apply("inside") };_applyWithArgs("exactly", "}"); xs.join('') ) 150 | end 151 | 152 | def semAction2 153 | 154 | (_apply("spaces");_apply("outerBraces")) 155 | end 156 | 157 | def semAction 158 | 159 | _or(proc { _apply("semAction2") }, proc { _apply("semAction1") }) 160 | end 161 | end 162 | 163 | BSRubyTranslator = Class.new(OMeta) do 164 | @name = "BSRubyTranslator" 165 | def trans 166 | 167 | _apply("anything") 168 | end 169 | end 170 | 171 | OMetaParser = Class.new(OMeta) do 172 | @name = "OMetaParser" 173 | def nameFirst 174 | 175 | _or(proc { _applyWithArgs("exactly", "_") }, proc { _applyWithArgs("exactly", "$") }, proc { _apply("letter") }) 176 | end 177 | 178 | def nameRest 179 | 180 | _or(proc { _apply("nameFirst") }, proc { _apply("digit") }) 181 | end 182 | 183 | def tsName 184 | xs = nil 185 | (xs = _applyWithArgs("firstAndRest", "nameFirst", "nameRest"); xs.join('') ) 186 | end 187 | 188 | def name 189 | 190 | (_apply("spaces");_apply("tsName")) 191 | end 192 | 193 | def eChar 194 | c = nil 195 | _or(proc { (_applyWithArgs("exactly", "\\");c = _apply("char"); unescapeChar c ) }, proc { _apply("char") }) 196 | end 197 | 198 | def tsString 199 | xs = nil 200 | (_applyWithArgs("exactly", "'");xs = _xmany { (_xnot { _applyWithArgs("exactly", "'") };_apply("eChar")) };_applyWithArgs("exactly", "'"); xs.join('') ) 201 | end 202 | 203 | def characters 204 | xs = nil 205 | (_applyWithArgs("exactly", "`");_applyWithArgs("exactly", "`");xs = _xmany { (_xnot { (_applyWithArgs("exactly", "'");_applyWithArgs("exactly", "'")) };_apply("eChar")) };_applyWithArgs("exactly", "'");_applyWithArgs("exactly", "'"); ['App', 'seq', xs.join('').inspect] ) 206 | end 207 | 208 | def sCharacters 209 | xs = nil 210 | (_applyWithArgs("exactly", "\"");xs = _xmany { (_xnot { _applyWithArgs("exactly", "\"") };_apply("eChar")) };_applyWithArgs("exactly", "\""); ['App', 'token', xs.join('').inspect] ) 211 | end 212 | 213 | def string 214 | xs = nil 215 | (xs = _or(proc { (_or(proc { _applyWithArgs("exactly", "#") }, proc { _applyWithArgs("exactly", "`") });_apply("tsName")) }, proc { _apply("tsString") }); ['App', 'exactly', xs.inspect] ) 216 | end 217 | 218 | def number 219 | sign = ds = nil 220 | (sign = _or(proc { _applyWithArgs("exactly", "-") }, proc { (_apply("empty"); '' ) });ds = _xmany1 { _apply("digit") }; ['App', 'exactly', sign + ds.join('')] ) 221 | end 222 | 223 | def keyword 224 | xs = nil 225 | (xs = _apply("anything");_applyWithArgs("token", xs);_xnot { _apply("letterOrDigit") }; xs ) 226 | end 227 | 228 | def hostExpr 229 | r = nil 230 | (r = _applyWithArgs("foreign", BSRubyParser, "expr");_applyWithArgs("foreign", BSRubyTranslator, "trans", r)) 231 | end 232 | 233 | def atomicHostExpr 234 | r = nil 235 | (r = _applyWithArgs("foreign", BSRubyParser, "semAction");_applyWithArgs("foreign", BSRubyTranslator, "trans", r)) 236 | end 237 | 238 | def args 239 | xs = nil 240 | _or(proc { (_applyWithArgs("token", "(");xs = _applyWithArgs("listOf", "hostExpr", ",");_applyWithArgs("token", ")"); xs ) }, proc { (_apply("empty"); [] ) }) 241 | end 242 | 243 | def application 244 | rule = as = nil 245 | (rule = _apply("name");as = _apply("args"); ['App', rule, *as] ) 246 | end 247 | 248 | def semAction 249 | x = nil 250 | (_or(proc { _applyWithArgs("token", "!") }, proc { _applyWithArgs("token", "->") });x = _apply("atomicHostExpr"); ['Act', x] ) 251 | end 252 | 253 | def semPred 254 | x = nil 255 | (_applyWithArgs("token", "?");x = _apply("atomicHostExpr"); ['Pred', x] ) 256 | end 257 | 258 | def expr 259 | xs = nil 260 | (xs = _applyWithArgs("listOf", "expr4", "|"); ['Or', *xs] ) 261 | end 262 | 263 | def expr4 264 | xs = nil 265 | (xs = _xmany { _apply("expr3") }; ['And', *xs] ) 266 | end 267 | 268 | def optIter 269 | x = nil 270 | (x = _apply("anything");_or(proc { (_applyWithArgs("token", "*"); ['Many', x] ) }, proc { (_applyWithArgs("token", "+"); ['Many1', x] ) }, proc { (_apply("empty"); x ) })) 271 | end 272 | 273 | def expr3 274 | x = x = n = n = nil 275 | _or(proc { (x = _apply("expr2");x = _applyWithArgs("optIter", x);_or(proc { (_applyWithArgs("exactly", ":");n = _apply("name"); (@locals << n; ['Set', n, x]) ) }, proc { (_apply("empty");x) })) }, proc { (_applyWithArgs("token", ":");n = _apply("name"); (@locals << n; ['Set', n, ['App', 'anything']]) ) }) 276 | end 277 | 278 | def expr2 279 | x = x = nil 280 | _or(proc { (_applyWithArgs("token", "~");x = _apply("expr2"); ['Not', x] ) }, proc { (_applyWithArgs("token", "&");x = _apply("expr1"); ['Lookahead', x] ) }, proc { _apply("expr1") }) 281 | end 282 | 283 | def expr1 284 | x = x = x = nil 285 | _or(proc { _apply("application") }, proc { _apply("semAction") }, proc { _apply("semPred") }, proc { (x = _or(proc { _applyWithArgs("keyword", "undefined") }, proc { _applyWithArgs("keyword", "nil") }, proc { _applyWithArgs("keyword", "true") }, proc { _applyWithArgs("keyword", "false") }); ['App', 'exactly', x] ) }, proc { (_apply("spaces");_or(proc { _apply("characters") }, proc { _apply("sCharacters") }, proc { _apply("string") }, proc { _apply("number") })) }, proc { (_applyWithArgs("token", "[");x = _apply("expr");_applyWithArgs("token", "]"); ['Form', x] ) }, proc { (_applyWithArgs("token", "(");x = _apply("expr");_applyWithArgs("token", ")"); x ) }) 286 | end 287 | 288 | def ruleName 289 | 290 | _or(proc { _apply("name") }, proc { (_apply("spaces");_apply("tsString")) }) 291 | end 292 | 293 | def rule 294 | n = x = xs = nil 295 | (_xlookahead { n = _apply("ruleName") };@locals = [];x = _applyWithArgs("rulePart", n);xs = _xmany { (_applyWithArgs("token", ",");_applyWithArgs("rulePart", n)) }; ['Rule', n, @locals, ['Or', x, *xs]] ) 296 | end 297 | 298 | def rulePart 299 | rn = n = b1 = b2 = nil 300 | (rn = _apply("anything");n = _apply("ruleName");_pred(n == rn);b1 = _apply("expr4");_or(proc { (_applyWithArgs("token", "=");b2 = _apply("expr"); ['And', b1, b2] ) }, proc { (_apply("empty"); b1 ) })) 301 | end 302 | 303 | def grammar 304 | n = sn = rs = nil 305 | (_applyWithArgs("keyword", "ometa");n = _apply("name");sn = _or(proc { (_applyWithArgs("token", "<:");_apply("name")) }, proc { (_apply("empty"); 'OMeta' ) });_applyWithArgs("token", "{");rs = _applyWithArgs("listOf", "rule", ",");_applyWithArgs("token", "}"); ['Grammar', n, sn, *rs] ) 306 | end 307 | end 308 | 309 | RubyOMetaTranslator = Class.new(OMeta) do 310 | @name = "RubyOMetaTranslator" 311 | def trans 312 | t = ans = nil 313 | (_xform { (t = _apply("anything");ans = _applyWithArgs("apply", t)) }; ans ) 314 | end 315 | 316 | def App 317 | args = rule = args = rule = nil 318 | _or(proc { (_applyWithArgs("exactly", "super");args = _xmany1 { _apply("anything") }; "_superApplyWithArgs(#{args * ', '})" ) }, proc { (rule = _apply("anything");args = _xmany1 { _apply("anything") }; "_applyWithArgs(#{rule.inspect}, #{args * ', '})" ) }, proc { (rule = _apply("anything"); "_apply(#{rule.inspect})" ) }) 319 | end 320 | 321 | def Act 322 | expr = nil 323 | (expr = _apply("anything"); expr ) 324 | end 325 | 326 | def Pred 327 | expr = nil 328 | (expr = _apply("anything"); "_pred(#{expr})" ) 329 | end 330 | 331 | def Or 332 | xs = nil 333 | (xs = _xmany { _apply("transFn") }; "_or(#{xs * ', '})" ) 334 | end 335 | 336 | def And 337 | xs = y = nil 338 | _or(proc { (xs = _xmany { _applyWithArgs("notLast", "trans") };y = _apply("trans"); "(#{(xs + [y]) * ';'})" ) }, proc { "proc {}" }) 339 | end 340 | 341 | def Many 342 | x = nil 343 | (x = _apply("trans"); "_xmany { #{x} }" ) 344 | end 345 | 346 | def Many1 347 | x = nil 348 | (x = _apply("trans"); "_xmany1 { #{x} }" ) 349 | end 350 | 351 | def Set 352 | n = v = nil 353 | (n = _apply("anything");v = _apply("trans"); "#{n} = #{v}" ) 354 | end 355 | 356 | def Not 357 | x = nil 358 | (x = _apply("trans"); "_xnot { #{x} }" ) 359 | end 360 | 361 | def Lookahead 362 | x = nil 363 | (x = _apply("trans"); "_xlookahead { #{x} }" ) 364 | end 365 | 366 | def Form 367 | x = nil 368 | (x = _apply("trans"); "_xform { #{x} }" ) 369 | end 370 | 371 | def Rule 372 | name = ls = body = nil 373 | (name = _apply("anything");ls = _apply("locals");body = _apply("trans"); "def #{name}\n#{ls}\n#{body}\nend\n" ) 374 | end 375 | 376 | def Grammar 377 | name = sName = rules = nil 378 | (name = _apply("anything");sName = _apply("anything");rules = _xmany { _apply("trans") }; "Class.new(#{sName}) do\n@name = #{name.inspect}\n#{rules * "\n"}end" ) 379 | end 380 | 381 | def locals 382 | vs = nil 383 | _or(proc { (_xform { vs = _xmany1 { _apply("anything") } }; vs.map { |v| "#{v} = " }.join('') + 'nil' ) }, proc { (_xform { proc {} }; '' ) }) 384 | end 385 | 386 | def transFn 387 | x = nil 388 | (x = _apply("trans"); "proc { #{x} }" ) 389 | end 390 | end 391 | 392 | -------------------------------------------------------------------------------- /lib/ometa/runtime.rb: -------------------------------------------------------------------------------- 1 | UNDEFINED = Object.new 2 | 3 | class Fail < StandardError 4 | attr_accessor :matcher 5 | attr_accessor :failPos 6 | end 7 | 8 | class Character < String 9 | undef_method :each if ''.respond_to?(:each) 10 | 11 | class StringWrapper 12 | include Enumerable 13 | 14 | def initialize str 15 | @str = str 16 | end 17 | 18 | def length 19 | @str.length 20 | end 21 | 22 | def [] i 23 | Character.new @str[i..i] 24 | end 25 | 26 | def each 27 | length.times { |i| yield self[i] } 28 | end 29 | 30 | def to_s 31 | @str.to_s 32 | end 33 | 34 | def inspect 35 | @str.inspect 36 | end 37 | end 38 | 39 | # make them display with single quotes 40 | def inspect 41 | "'" + super[1..-2] + "'" 42 | end 43 | end 44 | 45 | class Stream 46 | def copy 47 | self 48 | end 49 | end 50 | 51 | class ReadStream < Stream 52 | attr_reader :pos 53 | def initialize obj 54 | if String === obj 55 | # puts 'note, wrapping string in a string wrapper' 56 | obj = Character::StringWrapper.new obj 57 | end 58 | @src = obj 59 | @pos = 0 60 | end 61 | 62 | def eof? 63 | @pos >= @src.length 64 | end 65 | 66 | def next 67 | @src[@pos] 68 | ensure 69 | @pos += 1 70 | end 71 | end 72 | 73 | class LazyStream < Stream 74 | def self.new head, tail, stream 75 | if stream and stream.eof? 76 | LazyInputStreamEnd.new stream 77 | else 78 | LazyInputStream.new head, tail, stream 79 | end 80 | end 81 | end 82 | 83 | class LazyInputStream < Stream 84 | attr_reader :memo 85 | def initialize head=UNDEFINED, tail=UNDEFINED, stream=nil 86 | @head = head 87 | @tail = tail 88 | @stream = stream 89 | @memo = {} 90 | end 91 | 92 | def head 93 | if @head == UNDEFINED 94 | @head = @stream.next 95 | end 96 | @head 97 | end 98 | 99 | def tail 100 | if @tail == UNDEFINED 101 | @tail = LazyStream.new UNDEFINED, UNDEFINED, @stream 102 | end 103 | @tail 104 | end 105 | 106 | # also interesting, due to backtracking, would possibly be to store 107 | # @stream.pos in initialize, which would presumably be the actual stream 108 | # pos at which any potential error occurred. 109 | def realPos 110 | @stream.pos 111 | end 112 | end 113 | 114 | class LazyInputStreamEnd < Stream 115 | attr_reader :memo 116 | 117 | def initialize stream 118 | @stream = stream 119 | @memo = {} 120 | end 121 | 122 | def realPos 123 | @stream.pos 124 | end 125 | 126 | def head 127 | #throw :fail, true #raise Fail #EOFError 128 | raise Fail 129 | end 130 | 131 | def tail 132 | raise NoMethodError 133 | end 134 | end 135 | 136 | class LazyStreamWrapper < Stream 137 | attr_reader :memo, :stream 138 | def initialize stream 139 | @stream = stream 140 | @memo = {} 141 | end 142 | 143 | def realPos 144 | @stream.pos 145 | end 146 | 147 | def head 148 | @stream.head 149 | end 150 | 151 | def tail 152 | self.class.new @stream.tail 153 | end 154 | end 155 | 156 | class LeftRecursion 157 | attr_accessor :detected 158 | end 159 | 160 | #// the OMeta "class" and basic functionality 161 | #// TODO: make apply support indirect left recursion 162 | 163 | class OMeta 164 | attr_reader :input 165 | 166 | def _apply(rule) 167 | #p rule 168 | memoRec = @input.memo[rule] 169 | if not memoRec 170 | oldInput = @input 171 | lr = LeftRecursion.new 172 | @input.memo[rule] = memoRec = lr 173 | # should these be copies too? 174 | @input.memo[rule] = memoRec = {:ans => method(rule).call, :nextInput => @input} 175 | if lr.detected 176 | sentinel = @input 177 | while true 178 | begin 179 | @input = oldInput 180 | ans = send rule 181 | raise Fail if @input == sentinel 182 | oldInput.memo[rule] = memoRec = {:ans => ans, :nextInput => @input} 183 | rescue Fail 184 | break 185 | end 186 | end 187 | end 188 | elsif LeftRecursion === memoRec 189 | memoRec.detected = true 190 | raise Fail #throw :fail, true 191 | end 192 | @input = memoRec[:nextInput] 193 | return memoRec[:ans] 194 | end 195 | 196 | def _applyWithArgs(rule, *args) 197 | #p :app_wit_arg => [rule, args] 198 | args.reverse_each do |arg| 199 | @input = LazyStream.new arg, @input, nil 200 | end 201 | send rule 202 | end 203 | 204 | def _superApplyWithArgs(rule, *args) 205 | #for (var idx = arguments.length - 1; idx > 1; idx--) 206 | # $elf.input = makeOMInputStream(arguments[idx], $elf.input, null) 207 | #return this[rule].apply($elf) 208 | # would probably be easier to use realsuper in the caller, rather than do this 209 | ##classes = self.class.ancestors.select { |a| Class === a } 210 | #methods = classes.map { |a| a.instance_method rule rescue nil }.compact 211 | #method = methods.first.bind(self) 212 | args.reverse_each do |arg| 213 | @input = LazyStream.new arg, @input, nil 214 | end 215 | # we leverage the inbuild ruby super, by means of the block passed to this function 216 | # which calls super 217 | yield 218 | #method.call 219 | end 220 | 221 | def _pred(b) 222 | if (b) 223 | return true 224 | end 225 | raise Fail 226 | end 227 | 228 | def _xnot 229 | oldInput = @input.copy 230 | begin 231 | yield 232 | rescue Fail 233 | @input = oldInput 234 | return true 235 | end 236 | raise Fail 237 | end 238 | 239 | def _xlookahead 240 | oldInput = @input.copy 241 | r = yield 242 | @input = oldInput 243 | r 244 | end 245 | 246 | def _or(*args) 247 | oldInput = @input.copy 248 | args.each do |arg| 249 | begin 250 | @input = oldInput 251 | return arg.call 252 | rescue Fail 253 | end 254 | end 255 | raise Fail 256 | end 257 | 258 | def _xmany(*ans) 259 | while true 260 | oldInput = @input.copy 261 | begin 262 | ans << yield 263 | next 264 | rescue Fail 265 | end 266 | @input = oldInput 267 | break 268 | end 269 | ans 270 | end 271 | 272 | def _xmany1(&block) 273 | _xmany block.call, &block 274 | end 275 | 276 | def _xform 277 | v = _apply "anything" 278 | raise Fail unless v.respond_to? :each 279 | oldInput = @input 280 | @input = LazyStream.new UNDEFINED, UNDEFINED, ReadStream.new(v) 281 | r = yield 282 | _apply "end" 283 | @input = oldInput 284 | v 285 | end 286 | 287 | #// some basic rules 288 | def anything 289 | r = @input.head 290 | @input = @input.tail 291 | return r 292 | end 293 | 294 | def end 295 | _xnot { _apply("anything") } 296 | end 297 | 298 | def empty 299 | return true 300 | end 301 | 302 | def apply 303 | _apply _apply('anything') 304 | end 305 | 306 | def foreign 307 | g = _apply("anything") 308 | r = _apply("anything") 309 | fis = LazyStreamWrapper.new @input 310 | gi = g.new(fis) 311 | ans = gi._apply(r) 312 | @input = gi.input.stream 313 | #p :foreign => ans 314 | return ans 315 | end 316 | 317 | #// some useful "derived" rules 318 | def exactly 319 | wanted = _apply("anything") 320 | if wanted == _apply("anything") 321 | return wanted 322 | end 323 | raise Fail #throw :fail, true 324 | end 325 | 326 | def char 327 | r = _apply("anything") 328 | _pred(Character === r) 329 | return r 330 | end 331 | 332 | def space 333 | r = _apply("char") 334 | _pred(r[0] <= 32) 335 | return r 336 | end 337 | 338 | def spaces 339 | _xmany { _apply("space") } 340 | end 341 | 342 | def digit 343 | r = _apply("char") 344 | _pred(r =~ /[0-9]/) 345 | return r 346 | end 347 | 348 | def lower 349 | r = _apply("char") 350 | _pred(r =~ /[a-z]/) 351 | return r 352 | end 353 | 354 | def upper 355 | r = _apply("char") 356 | _pred(r =~ /[A-Z]/) 357 | return r 358 | end 359 | 360 | def letter 361 | _or( 362 | proc { _apply 'lower' }, 363 | proc { _apply 'upper' }) 364 | end 365 | 366 | def letterOrDigit 367 | _or( 368 | proc { _apply 'letter' }, 369 | proc { _apply 'digit' }) 370 | end 371 | 372 | def firstAndRest 373 | first = _apply 'anything' 374 | rest = _apply 'anything' 375 | _xmany(_apply(first)) { _apply rest } 376 | end 377 | 378 | def seq 379 | xs = _apply 'anything' 380 | if String === xs 381 | xs = Character::StringWrapper.new xs 382 | end 383 | xs.each { |obj| _applyWithArgs 'exactly', obj } 384 | xs 385 | end 386 | 387 | def notLast 388 | rule = _apply("anything") 389 | r = _apply(rule) 390 | _xlookahead { _apply(rule) } 391 | return r 392 | end 393 | 394 | def initialize(input) 395 | @input = input 396 | initialize_hook 397 | end 398 | 399 | def initialize_hook 400 | end 401 | 402 | # #match:with: and #matchAll:with: are a grammar's "public interface" 403 | def self.genericMatch(input, rule, *args) 404 | m = new(input) 405 | e = nil 406 | begin 407 | if args.empty? 408 | return m._apply(rule) 409 | else 410 | return m._applyWithArgs(rule, *args) 411 | end 412 | rescue Fail 413 | e = $! 414 | end 415 | #e = Fail.new 416 | e.matcher = m 417 | e.message.replace "Unable to match at pos #{m.input.instance_variable_get(:@stream).pos.inspect}" 418 | raise e 419 | end 420 | 421 | def self.matchwith(obj, rule, *args) 422 | genericMatch LazyStream.new(UNDEFINED, UNDEFINED, ReadStream.new([obj])), rule, *args 423 | end 424 | 425 | def self.matchAllwith(listyObj, rule, *args) 426 | genericMatch LazyStream.new(UNDEFINED, UNDEFINED, ReadStream.new(listyObj)), rule, *args 427 | end 428 | 429 | # ---- 430 | 431 | def listOf 432 | rule = _apply("anything") 433 | delim = _apply("anything") 434 | _or(proc { 435 | r = _apply(rule) 436 | _xmany(r) { 437 | _applyWithArgs("token", delim) 438 | _apply(rule) 439 | } 440 | }, 441 | proc { [] } 442 | ) 443 | end 444 | 445 | def token 446 | cs = _apply("anything") 447 | _apply("spaces") 448 | return _applyWithArgs("seq", cs) 449 | end 450 | 451 | def parse 452 | rule = _apply("anything"), 453 | ans = _apply(rule) 454 | _apply("end") 455 | return ans 456 | end 457 | 458 | NICER_FAILURE_METHODS = { 459 | '_or' => 'No matching alternative', 460 | nil => 'Failure' 461 | } 462 | 463 | def self.parsewith(text, rule) 464 | begin 465 | return matchAllwith(text, rule) 466 | rescue Fail => e 467 | e.failPos = e.matcher.input.realPos() - 1 468 | cause = NICER_FAILURE_METHODS[$@.first[/`(.*?)'/, 1]] || NICER_FAILURE_METHODS[nil] 469 | rule = $@.find { |l| l !~ /runtime/ }[/`(.*?)'/, 1] 470 | lines = text[0, e.failPos].to_a 471 | lines = [''] if lines.empty? 472 | message = "#{cause} in rule #{rule.inspect}, at line #{lines.length} character #{lines.last.length + 1}" 473 | raise e, message #, $@ 474 | end 475 | end 476 | 477 | ESCAPE_LOOKUP = {'n' => "\n", 't' => "\t", 'r' => "\r", '\'' => "'", '\"' => '"', '\\' => '\\'} 478 | 479 | def unescapeChar c 480 | ESCAPE_LOOKUP[c] or raise NotImplementedError 481 | end 482 | end 483 | 484 | -------------------------------------------------------------------------------- /ometa/andor_opt.ometa: -------------------------------------------------------------------------------- 1 | ometa AndOrOptimizer <: NullOptimizer { 2 | And trans:x end setHelped -> { x }, 3 | And transInside('"And"'):xs -> { ['And', *xs] }, 4 | Or trans:x end setHelped -> { x }, 5 | Or transInside('"Or"'):xs -> { ['Or', *xs] }, 6 | transInside :t = [exactly('t') transInside('t'):xs] transInside('t'):ys setHelped -> { xs + ys } 7 | | trans:x transInside('t'):xs -> { [x, *xs] } 8 | | -> { [] } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /ometa/bsruby_parser.ometa: -------------------------------------------------------------------------------- 1 | 2 | ometa BSRubyParser <: OMeta { 3 | eChar = '\\' char:c -> { unescapeChar c } 4 | | char, 5 | tsString = '\'' (~'\'' eChar)*:xs '\'' -> { xs.join('') }, 6 | expr = spaces tsString, 7 | semAction1 = spaces (~seq('"\\n"') anything)*:xs -> { xs.join('') }, 8 | nonBraceChar = ~'{' ~'}' char, 9 | inside = innerBraces | nonBraceChar, 10 | innerBraces = '{' inside*:xs '}' -> { "{#{xs.join('')}}" }, 11 | outerBraces = '{' inside*:xs '}' -> { xs.join('') }, 12 | semAction2 = spaces outerBraces, 13 | semAction = semAction2 | semAction1 14 | } 15 | 16 | -------------------------------------------------------------------------------- /ometa/bsruby_translator.ometa: -------------------------------------------------------------------------------- 1 | 2 | ometa BSRubyTranslator <: OMeta { 3 | trans = anything 4 | } 5 | 6 | -------------------------------------------------------------------------------- /ometa/calc.ometa: -------------------------------------------------------------------------------- 1 | ometa Calc { 2 | digit = char:c ?c =~ /[0-9]/ 3 | -> c[0] - ?0 4 | , 5 | number = number:n digit:d -> n * 10 + d 6 | | digit, 7 | priExpr = number 8 | | '(' expr:e ')' -> e 9 | , 10 | powExpr = priExpr:x '^' powExpr:y -> x ** y 11 | | priExpr, 12 | mulExpr = mulExpr:x '*' powExpr:y -> x * y 13 | | mulExpr:x '/' powExpr:y -> x / y 14 | | powExpr, 15 | addExpr = addExpr:x '+' mulExpr:y -> x + y 16 | | addExpr:x '-' mulExpr:y -> x - y 17 | | mulExpr, 18 | expr = addExpr 19 | } 20 | 21 | -------------------------------------------------------------------------------- /ometa/js_parser.ometa: -------------------------------------------------------------------------------- 1 | ometa JSParser <: OMeta { 2 | fromTo :x :y = seq('x') (~seq('y') char)* seq('y'), 3 | space = super('"space"') | fromTo('"//"', '"\n"') | fromTo('"/*"', '"*/"'), 4 | nameFirst = letter | '$' | '_', 5 | nameRest = nameFirst | digit, 6 | iName = firstAndRest('"nameFirst"', '"nameRest"'):r -> r.join('') 7 | , 8 | isKeyword :x = ?JSParser._isKeyword(x) 9 | , 10 | name = iName:n ~isKeyword('n') -> ['name', n] 11 | , 12 | keyword = iName:k isKeyword('k') -> [k, k] 13 | , 14 | number = digit+:ws ('.' digit+ | empty -> [] 15 | ):fs -> ['number', (ws.join('') + '.' + fs.join('')).to_f] 16 | , 17 | escapeChar = '\\' char:c -> eval("\"\\" + c + "\"") 18 | , 19 | str = '\'' (escapeChar | ~'\'' char)*:cs '\'' -> ['string', cs.join('')] 20 | | '"' (escapeChar | ~'"' char)*:cs '"' -> ['string', cs.join('')] 21 | | ('#' | '`') iName:n -> ['string', n] 22 | , 23 | special = ( '(' | ')' | '{' | '}' | '[' | ']' | ',' | ';' 24 | | '?' | ':' | ``!=='' | ``!='' | ``==='' | ``=='' | ``='' | ``>='' 25 | | '>' | ``<='' | '<' | ``++'' | ``+='' | '+' | ``--'' | ``-='' 26 | | '-' | ``*='' | '*' | ``/='' | '/' | ``%='' | '%' | ``&&='' 27 | | ``&&'' | ``||='' | ``||'' | '.' | '!' ):s -> [s, s] 28 | , 29 | tok = spaces (name | keyword | number | str | special), 30 | toks = token*:ts spaces end -> ts 31 | , 32 | token :tt = tok:t ?t[0] == tt 33 | -> t[1] 34 | , 35 | spacesNoNl = (~'\n' space)*, 36 | expr = orExpr:e ( "?" expr:t ":" expr:f -> ['condExpr', e, t, f] 37 | | "=" expr:rhs -> ['set', e, rhs] 38 | | "+=" expr:rhs -> ['mset', e, "+", rhs] 39 | | "-=" expr:rhs -> ['mset', e, "-", rhs] 40 | | "*=" expr:rhs -> ['mset', e, "*", rhs] 41 | | "/=" expr:rhs -> ['mset', e, "/", rhs] 42 | | "%=" expr:rhs -> ['mset', e, "%", rhs] 43 | | "&&=" expr:rhs -> ['mset', e, "&&", rhs] 44 | | "||=" expr:rhs -> ['mset', e, "||", rhs] 45 | | empty -> e 46 | ), 47 | 48 | orExpr = orExpr:x "||" andExpr:y -> ['binop', "||", x, y] 49 | | andExpr, 50 | andExpr = andExpr:x "&&" eqExpr:y -> ['binop', "&&", x, y] 51 | | eqExpr, 52 | eqExpr = eqExpr:x ( "==" relExpr:y -> ['binop', "==", x, y] 53 | | "!=" relExpr:y -> ['binop', "!=", x, y] 54 | | "===" relExpr:y -> ['binop', "===", x, y] 55 | | "!==" relExpr:y -> ['binop', "!==", x, y] 56 | ) 57 | | relExpr, 58 | relExpr = relExpr:x ( ">" addExpr:y -> ['binop', ">", x, y] 59 | | ">=" addExpr:y -> ['binop', ">=", x, y] 60 | | "<" addExpr:y -> ['binop', "<", x, y] 61 | | "<=" addExpr:y -> ['binop', "<=", x, y] 62 | | "instanceof" addExpr:y -> ['binop', "instanceof", x, y] 63 | ) 64 | | addExpr, 65 | addExpr = addExpr:x "+" mulExpr:y -> ['binop', "+", x, y] 66 | | addExpr:x "-" mulExpr:y -> ['binop', "-", x, y] 67 | | mulExpr, 68 | mulExpr = mulExpr:x "*" mulExpr:y -> ['binop', "*", x, y] 69 | | mulExpr:x "/" mulExpr:y -> ['binop', "/", x, y] 70 | | mulExpr:x "%" mulExpr:y -> ['binop', "%", x, y] 71 | | unary, 72 | unary = "-" postfix:p -> ['unop', "-", p] 73 | | "+" postfix:p -> p 74 | | "++" postfix:p -> ['preop', "++", p] 75 | | "--" postfix:p -> ['preop', "--", p] 76 | | "!" postfix:p -> ['unop', "!", p] 77 | | postfix, 78 | postfix = primExpr:p ( spacesNoNl "++" -> ['postop', "++", p] 79 | | spacesNoNl "--" -> ['postop', "--", p] 80 | | empty -> p 81 | ), 82 | primExpr = primExpr:p ( "[" expr:i "]" -> ['getp', i, p] 83 | | "." "name":m "(" listOf('"expr"', '","'):as ")" -> ['send', m, p].concat(as) 84 | | "." "name":f -> ['getp', ['string', f], p] 85 | | "(" listOf('"expr"', '","'):as ")" -> ['call', p].concat(as) 86 | ) 87 | | primExprHd, 88 | primExprHd = "(" expr:e ")" -> e 89 | | "this" -> ['this'] 90 | | "name":n -> ['get', n] 91 | | "number":n -> ['number', n] 92 | | "string":s -> ['string', s] 93 | | "function" funcRest 94 | | "new" "name":n "(" listOf('"expr"', '","'):as ")" -> ['new', n].concat(as) 95 | | "[" listOf('"expr"', '","'):es "]" -> ['arr'].concat(es) 96 | | json, 97 | 98 | json = "{" listOf('"jsonBinding"', '","'):bs "}" -> ['json'].concat(bs) 99 | , 100 | jsonBinding = jsonPropName:n ":" expr:v -> ['binding', n, v] 101 | , 102 | jsonPropName = "name" | "number" | "string", 103 | formal = spaces "name", 104 | funcRest = "(" listOf('"formal"', '","'):fs ")" "{" srcElems:body "}" -> ['func', fs, body] 105 | , 106 | sc = spacesNoNl ('\n' | &'}' | end) 107 | | ";", 108 | binding = "name":n ( "=" expr 109 | | empty -> ['get', 'undefined'] 110 | ):v -> ['var', n, v] 111 | , 112 | block = "{" srcElems:ss "}" -> ss 113 | 114 | , 115 | stmt = block 116 | | "var" listOf('"binding"', '","'):bs sc -> ['begin'].concat(bs) 117 | | "if" "(" expr:c ")" stmt:t ( "else" stmt 118 | | empty -> ['get', 'undefined'] 119 | ):f -> ['if', c, t, f] 120 | | "while" "(" expr:c ")" stmt:s -> ['while', c, s] 121 | | "do" stmt:s "while" "(" expr:c ")" sc -> ['doWhile', s, c] 122 | | "for" "(" ( "var" binding 123 | | expr 124 | | empty -> ['get', 'undefined'] 125 | ):i 126 | ";" ( expr 127 | | empty -> ['get', 'true'] 128 | ):c 129 | ";" ( expr 130 | | empty -> ['get', 'undefined'] 131 | ):u 132 | ")" stmt:s -> ['for', i, c, u, s] 133 | | "for" "(" ( "var" "name":n -> ['var', n, ['get', 'undefined']] 134 | | expr ):v 135 | "in" expr:e 136 | ")" stmt:s -> ['forIn', v, e, s] 137 | | "switch" "(" expr:e ")" "{" 138 | ( "case" expr:c ":" srcElems:cs -> ['case', c, cs] 139 | | "default" ":" srcElems:cs -> ['default', cs] 140 | )*:cs 141 | "}" -> ['switch', e].concat(cs) 142 | | "break" sc -> ['break'] 143 | | "continue" sc -> ['continue'] 144 | | "throw" spacesNoNl expr:e sc -> ['throw', e] 145 | | "try" block:t "catch" "(" "name":e ")" block:c 146 | ( "finally" block 147 | | empty -> ['get', 'undefined'] 148 | ):f -> ['try', t, e, c, f] 149 | | "return" ( expr 150 | | empty -> ['get', 'undefined'] 151 | ):e sc -> ['return', e] 152 | | expr:e sc -> e 153 | , 154 | srcElem = "function" "name":n funcRest:f -> ['var', n, f] 155 | | stmt, 156 | srcElems = srcElem*:ss -> ['begin'].concat(ss) 157 | , 158 | 159 | topLevel = srcElems:r spaces end -> r 160 | , 161 | semAction = "{" (srcElem:s &srcElem -> s 162 | )+:ss expr:r sc "}" spaces -> ( ss.push(['return', r]); ['call', ['func', [], ['begin'].concat(ss)]] ) 163 | | "{" primExpr:r "}" spaces -> r 164 | | primExpr:r spaces -> r 165 | } 166 | 167 | -------------------------------------------------------------------------------- /ometa/null_opt.ometa: -------------------------------------------------------------------------------- 1 | ometa NullOptimizer { 2 | setHelped = !{@_didSomething = true}, 3 | helped = ?{@_didSomething}, 4 | trans = [:t ?{respond_to?(t)} 5 | apply('t'):ans] -> { ans } 6 | | anything, 7 | optimize = trans:x helped -> { x }, 8 | Or trans*:xs -> { ['Or', *xs] }, 9 | And trans*:xs -> { ['And', *xs] }, 10 | Many trans:x -> { ['Many', x] }, 11 | Many1 trans:x -> { ['Many1', x] }, 12 | Set :n trans:v -> { ['Set', n, v] }, 13 | Not trans:x -> { ['Not', x] }, 14 | Lookahead trans:x -> { ['Lookahead', x] }, 15 | Form trans:x -> { ['Form', x] }, 16 | Rule :name :ls trans:body -> { ['Rule', name, ls, body] }, 17 | initialize -> { @_didSomething = false } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /ometa/ometa_opt.ometa: -------------------------------------------------------------------------------- 1 | ometa OMetaOptimizer { 2 | optimizeGrammar = ['Grammar' :n :sn optimizeRule*:rs] -> { ['Grammar', n, sn, *rs] }, 3 | optimizeRule = :r (foreign('AndOrOptimizer', '"optimize"', 'r'):r)* -> { r } 4 | } 5 | 6 | -------------------------------------------------------------------------------- /ometa/ometa_parser.ometa: -------------------------------------------------------------------------------- 1 | 2 | ometa OMetaParser <: OMeta { 3 | nameFirst = '_' | '$' | letter, 4 | nameRest = nameFirst | digit, 5 | tsName = firstAndRest('"nameFirst"', '"nameRest"'):xs -> { xs.join('') }, 6 | name = spaces tsName, 7 | eChar = '\\' char:c -> { unescapeChar c } 8 | | char, 9 | tsString = '\'' (~'\'' eChar)*:xs '\'' -> { xs.join('') }, 10 | characters = '`' '`' (~('\'' '\'') eChar)*:xs '\'' '\'' -> { ['App', 'seq', xs.join('').inspect] }, 11 | sCharacters = '"' (~'"' eChar)*:xs '"' -> { ['App', 'token', xs.join('').inspect] }, 12 | string = (('#' | '`') tsName | tsString):xs -> { ['App', 'exactly', xs.inspect] }, 13 | number = ('-' | empty -> { '' }):sign digit+:ds -> { ['App', 'exactly', sign + ds.join('')] }, 14 | keyword :xs = token('xs') ~letterOrDigit -> { xs }, 15 | hostExpr = foreign('BSRubyParser', '"expr"'):r foreign('BSRubyTranslator', '"trans"', 'r'), 16 | atomicHostExpr = foreign('BSRubyParser', '"semAction"'):r foreign('BSRubyTranslator', '"trans"', 'r'), 17 | args = "(" listOf('"hostExpr"', '","'):xs ")" -> { xs } 18 | | empty -> { [] }, 19 | application = name:rule args:as -> { ['App', rule, *as] }, 20 | semAction = ("!" | "->") atomicHostExpr:x -> { ['Act', x] }, 21 | semPred = "?" atomicHostExpr:x -> { ['Pred', x] }, 22 | expr = listOf('"expr4"', '"|"'):xs -> { ['Or', *xs] }, 23 | expr4 = expr3*:xs -> { ['And', *xs] }, 24 | optIter :x = "*" -> { ['Many', x] } 25 | | "+" -> { ['Many1', x] } 26 | | empty -> { x }, 27 | expr3 = expr2:x optIter('x'):x ( ':' name:n -> { (@locals << n; ['Set', n, x]) } 28 | | empty -> x 29 | ) 30 | | ":" name:n -> { (@locals << n; ['Set', n, ['App', 'anything']]) }, 31 | expr2 = "~" expr2:x -> { ['Not', x] } 32 | | "&" expr1:x -> { ['Lookahead', x] } 33 | | expr1, 34 | expr1 = application | semAction | semPred 35 | | ( keyword('"undefined"') | keyword('"nil"') 36 | | keyword('"true"') | keyword('"false"') ):x -> { ['App', 'exactly', x] } 37 | | spaces (characters | sCharacters | string | number) 38 | | "[" expr:x "]" -> { ['Form', x] } 39 | | "(" expr:x ")" -> { x }, 40 | ruleName = name 41 | | spaces tsString, 42 | rule = &(ruleName:n) !{@locals = []} 43 | rulePart('n'):x ("," rulePart('n'))*:xs -> { ['Rule', n, @locals, ['Or', x, *xs]] }, 44 | rulePart :rn = ruleName:n ?{n == rn} expr4:b1 ( "=" expr:b2 -> { ['And', b1, b2] } 45 | | empty -> { b1 } 46 | ), 47 | grammar = keyword('"ometa"') name:n 48 | ( "<:" name | empty -> { 'OMeta' }):sn 49 | "{" listOf('"rule"', '","'):rs "}" -> { ['Grammar', n, sn, *rs] } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /ometa/ometa_translator.ometa: -------------------------------------------------------------------------------- 1 | 2 | ometa RubyOMetaTranslator { 3 | trans [:t apply('t'):ans] -> { ans }, 4 | App 'super' anything+:args -> { "_superApplyWithArgs(#{args * ', '})" }, 5 | App :rule anything+:args -> { "_applyWithArgs(#{rule.inspect}, #{args * ', '})" }, 6 | App :rule -> { "_apply(#{rule.inspect})" }, 7 | Act :expr -> { expr }, 8 | Pred :expr -> { "_pred(#{expr})" }, 9 | Or transFn*:xs -> { "_or(#{xs * ', '})" }, 10 | And notLast('"trans"')*:xs trans:y -> { "(#{(xs + [y]) * ';'})" }, 11 | And -> { "proc {}" }, 12 | Many trans:x -> { "_xmany { #{x} }" }, 13 | Many1 trans:x -> { "_xmany1 { #{x} }" }, 14 | Set :n trans:v -> { "#{n} = #{v}" }, 15 | Not trans:x -> { "_xnot { #{x} }" }, 16 | Lookahead trans:x -> { "_xlookahead { #{x} }" }, 17 | Form trans:x -> { "_xform { #{x} }" }, 18 | Rule :name locals:ls trans:body -> { "def #{name}\n#{ls}\n#{body}\nend\n" }, 19 | Grammar :name :sName 20 | trans*:rules -> { "Class.new(#{sName}) do\n@name = #{name.inspect}\n#{rules * "\n"}end" }, 21 | locals = [anything+:vs] -> { vs.map { |v| "#{v} = " }.join('') + 'nil' } 22 | | [] -> { '' }, 23 | transFn = trans:x -> { "proc { #{x} }" } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /smalltalk.syntax: -------------------------------------------------------------------------------- 1 | http://chronos-st.blogspot.com/2007/12/smalltalk-in-one-page.html 2 | http://www.csci.csusb.edu/dick/samples/smalltalk.syntax.html 3 | 4 | Formal EBNF Specification of Smalltalk Syntax 5 | 6 | 1. Character = ? Any Unicode character ?; 7 | 2. WhitespaceCharacter = ? Any space, newline or horizontal tab character ?; 8 | 3. DecimalDigit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; 9 | 4. Letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" 10 | | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" 11 | | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" 12 | | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"; 13 | 5. CommentCharacter = Character - '"'; 14 | (* Any character other than a double quote *) 15 | 6. Comment = '"', {CommentCharacter}, '"'; 16 | 7. OptionalWhitespace = {WhitespaceCharacter | Comment}; 17 | 8. Whitespace = (WhitespaceCharacter | Comment), OptionalWhitespace; 18 | 9. LetterOrDigit = 19 | DecimalDigit 20 | | Letter; 21 | 10. Identifier = (Letter | "_"), {(LetterOrDigit | "_")}; 22 | 11. Reference = Identifier; 23 | 12. ConstantReference = 24 | "nil" 25 | | "false" 26 | | "true"; 27 | 13. PseudoVariableReference = 28 | "self" 29 | | "super" 30 | | "thisContext"; 31 | (* "thisContext" is not defined by the ANSI Standard, but is widely used anyway *) 32 | 14. ReservedIdentifier = 33 | PseudoVariableReference 34 | | ConstantReference; 35 | 15. BindableIdentifier = Identifier - ReservedIdentifier; 36 | 16. UnaryMessageSelector = Identifier; 37 | 17. Keyword = Identifier, ":"; 38 | 18. KeywordMessageSelector = Keyword, {Keyword}; 39 | 19. BinarySelectorChar = "~" | "!" | "@" | "%" | "&" | "*" | "-" | "+" | "=" | "|" | "\" | "<" | ">" | "," | "?" | "/"; 40 | 20. BinaryMessageSelector = BinarySelectorChar, [BinarySelectorChar]; 41 | 42 | 21. IntegerLiteral = ["-"], UnsignedIntegerLiteral; 43 | 22. UnsignedIntegerLiteral = 44 | DecimalIntegerLiteral 45 | | Radix, "r", BaseNIntegerLiteral; 46 | 23. DecimalIntegerLiteral = DecimalDigit, {DecimalDigit}; 47 | 24. Radix = DecimalIntegerLiteral; 48 | 25. BaseNIntegerLiteral = LetterOrDigit, {LetterOrDigit}; 49 | 26. ScaledDecimalLiteral = ["-"], DecimalIntegerLiteral, [".", DecimalIntegerLiteral], "s", [DecimalIntegerLiteral]; 50 | 27. FloatingPointLiteral = ["-"], DecimalIntegerLiteral, (".", DecimalIntegerLiteral, [Exponent] | Exponent); 51 | 28. Exponent = ("e" | "d" | "q"), [["-"], DecimalIntegerLiteral]; 52 | 29. CharacterLiteral = "$", Character; 53 | 30. StringLiteral = "'", {StringLiteralCharacter | "''"}, "'"; 54 | (* To embed a "'" character in a String literal, use two consecutive single quotes *) 55 | 31. StringLiteralCharacter = Character - "'"; 56 | (* Any character other than a single quote *) 57 | 32. SymbolInArrayLiteral = 58 | UnaryMessageSelector - ConstantReference 59 | | KeywordMessageSelector 60 | | BinaryMessageSelector; 61 | 33. SymbolLiteral = "#", (SymbolInArrayLiteral | ConstantReference | StringLiteral); 62 | 34. ArrayLiteral = 63 | ObjectArrayLiteral 64 | | ByteArrayLiteral; 65 | 35. ObjectArrayLiteral = "#", NestedObjectArrayLiteral; 66 | 36. NestedObjectArrayLiteral = "(", OptionalWhitespace, [LiteralArrayElement, {Whitespace, LiteralArrayElement}], OptionalWhitespace, ")"; 67 | 37. LiteralArrayElement = 68 | Literal - BlockLiteral 69 | | NestedObjectArrayLiteral 70 | | SymbolInArrayLiteral 71 | | ConstantReference; 72 | 38. ByteArrayLiteral = "#[", OptionalWhitespace, [UnsignedIntegerLiteral, {Whitespace, UnsignedIntegerLiteral}], OptionalWhitespace,"]"; 73 | 74 | (* The preceding production rules would usually be handled by the lexical analyzer; 75 | the following production rules would usually be handled by the parser *) 76 | 39. FormalBlockArgumentDeclaration = ":", BindableIdentifier; 77 | 40. FormalBlockArgumentDeclarationList = FormalBlockArgumentDeclaration, {Whitespace, FormalBlockArgumentDeclaration}; 78 | 41. BlockLiteral = "[", [OptionalWhitespace, FormalBlockArgumentDeclarationList, OptionalWhitespace, "|"], ExecutableCode, OptionalWhitespace, "]"; 79 | 80 | 42. Literal = ConstantReference 81 | | IntegerLiteral 82 | | ScaledDecimalLiteral 83 | | FloatingPointLiteral 84 | | CharacterLiteral 85 | | StringLiteral 86 | | SymbolLiteral 87 | | ArrayLiteral 88 | | BlockLiteral; 89 | 90 | 43. NestedExpression = "(", Statement, OptionalWhitespace, ")"; 91 | 44. Operand = 92 | Literal 93 | | Reference 94 | | NestedExpression; 95 | 96 | 45. UnaryMessage = UnaryMessageSelector; 97 | 46. UnaryMessageChain = {OptionalWhitespace, UnaryMessage}; 98 | 47. BinaryMessageOperand = Operand, UnaryMessageChain; 99 | 48. BinaryMessage = BinaryMessageSelector, OptionalWhitespace, BinaryMessageOperand; 100 | 49. BinaryMessageChain = {OptionalWhitespace, BinaryMessage}; 101 | 50. KeywordMessageArgument = BinaryMessageOperand, BinaryMessageChain; 102 | 51. KeywordMessageSegment = Keyword, OptionalWhitespace, KeywordMessageArgument; 103 | 52. KeywordMessage = KeywordMessageSegment, {OptionalWhitespace, KeywordMessageSegment}; 104 | 53. MessageChain = 105 | UnaryMessage, UnaryMessageChain, BinaryMessageChain, [KeywordMessage] 106 | | BinaryMessage, BinaryMessageChain, [KeywordMessage] 107 | | KeywordMessage; 108 | 54. CascadedMessage = ";", OptionalWhitespace, MessageChain; 109 | 55. Expression = Operand, [OptionalWhitespace, MessageChain, {OptionalWhitespace, CascadedMessage}]; 110 | 111 | 56. AssignmentOperation = OptionalWhitespace, BindableIdentifier, OptionalWhitespace, ":="; 112 | 57. Statement = {AssignmentOperation}, OptionalWhitespace, Expression; 113 | 58. MethodReturnOperator = OptionalWhitespace, "^"; 114 | 59. FinalStatement = [MethodReturnOperator], Statement; 115 | 60. LocalVariableDeclarationList = OptionalWhitespace, "|", OptionalWhitespace, [BindableIdentifier, {Whitespace, BindableIdentifier}], OptionalWhitespace, "|"; 116 | 61. ExecutableCode = [LocalVariableDeclarationList], [{Statement, OptionalWhitespace, "."}, FinalStatement, ["."]]; 117 | 118 | 62. UnaryMethodHeader = UnaryMessageSelector; 119 | 63. BinaryMethodHeader = BinaryMessageSelector, OptionalWhitespace, BindableIdentifier; 120 | 64. KeywordMethodHeaderSegment = Keyword, OptionalWhitespace, BindableIdentifier; 121 | 65. KeywordMethodHeader = KeywordMethodHeaderSegment, {Whitespace, KeywordMethodHeaderSegment}; 122 | 66. MethodHeader = 123 | UnaryMethodHeader 124 | | BinaryMethodHeader 125 | | KeywordMethodHeader; 126 | 67. MethodDeclaration = OptionalWhiteSpace, MethodHeader, ExecutableCode; 127 | 128 | -------------------------------------------------------------------------------- /test.rb: -------------------------------------------------------------------------------- 1 | 2 | __END__ 3 | 4 | # 5 | # later on the idea is to add a Ometa::Grammar function or something similar to wrap 6 | # up the process of creating a ruby class object from an ometa grammar file. 7 | # 8 | # as an example (this is missing the optimization pass): 9 | # 10 | class Ometa 11 | def self.Grammar(filename) 12 | grammar = File.read(filename) 13 | ast = OMetaParser.parsewith(grammar, 'grammar') 14 | ruby = RubyOMetaTranslator.matchwith(ast, 'trans') 15 | #ruby = File.read('_debug.rb') 16 | begin 17 | open('_debug.rb', 'w') { |f| f << ruby } 18 | eval ruby 19 | rescue SyntaxError 20 | puts '* error compiling grammar' 21 | puts '* ast:' 22 | require 'pp' 23 | pp ast 24 | puts '* ruby:' 25 | puts ruby 26 | open('_debug.rb', 'w') { |f| f << ruby } 27 | raise 28 | end 29 | end 30 | end 31 | 32 | class Calc < Ometa::Grammar('calc.ometa') 33 | def self.calc str 34 | matchAllwith str, 'expr' 35 | end 36 | end 37 | 38 | class JSParser < Ometa::Grammar('js_parser.ometa') 39 | KEYWORDS = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in", 40 | "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with", "ometa"] 41 | 42 | KEYWORDS_HASH = KEYWORDS.inject({}) { |h, k| h.update k => true } 43 | 44 | def self._isKeyword k 45 | KEYWORDS_HASH[k.to_s] 46 | end 47 | end 48 | 49 | p JSParser.parsewith(" x += 1;\n", 'expr') 50 | 51 | --------------------------------------------------------------------------------