What Grammar Based Parser-Generator Tools Exist for Ruby

What grammar based parser-generator tools exist for ruby?

Have you looked at rex and racc, the gem versions of lex and yacc?

Is there a parser generator for ruby that can generate a parser with no gem dependencies?

After further searching I came across the rexical gem, which is itself a renamed-and-slightly-maintained version of rex. This is an old-school lexer-generator thats only dependency is on racc/parser, which has been part of ruby-core for long enough that I don't have to worry about it.

The documentation is sparse, but there were enough blog posts touching on the topic that I was able to get what I needed working.

In case you're curious enough to have read this far, here is my example .rex specification:

require 'generator'

class OptionSpecsLexer
rules
\d+(\.\d*) { [:number, text] }
\w+: { [:syntax_hash_key, ":#{text[0, text.length - 1]} =>"] }
\:\w+ { [:symbol, text] }
\w+\( { [:funcall_open_paren, text] }
\w+ { [:identifier, text] }
\"(\\.|[^\\"])*\" { [:string, text] }
=> { [:rocket, text] }
, { [:comma, text] }
\{ { [:open_curly, text] }
\} { [:close_curly, text] }
\( { [:open_paren, text] }
\) { [:close_paren, text] }
\[ { [:close_square, text] }
\] { [:close_square, text] }
\\\s+ { }
\n { [:eol, text] }
\s+ { }

inner

def enumerate_tokens
Generator.new { |token|
loop {
t = next_token
break if t.nil?
token.yield(t)
}
}
end

def normalize(source)
scan_setup source
out = ""
enumerate_tokens.each do |token|
out += ' ' + token[1]
end
out
end

end

This lexer understands just enough ruby syntax to preprocess specifications written in my vMATCodeMonkey DSL, replacing the new keyword-style hash key syntax with the old rocket operator syntax. [This was done to allow vMATCodeMonkey to work on un-updated Mac OS X 10.8 which still ships with a deprecated version of ruby.]

Which is the best counterpart to ANTLR to create parsers in ruby?

You could also generate the parser with ANTLR for Java or C and call it from your Ruby program with JRuby or FFI.

This should also give you a performance boost which might be a big advantage if you have a lot of input to parse.

What tools exist for parsing Javascript and reading the results in Javascript or Ruby?

Check out UglifyJS: https://github.com/mishoo/UglifyJS

Bison grammar that accepts every prefix of a defined language

It's fairly straight-forward to take a CFG and transform it into a CFG for the language that matches all prefixes:

  • for every non-terminal, add an additional -prefix version of the non-terminal

  • for every rule of the form X := A B C, add rules of the form X_prefix := A B C_prefix | A B | A B_prefix | A | A_prefix

  • delete all the rules that refer to terminal_prefix, and then recursively for Y_prefix where Y_prefix has no rules left.

Of course, this new CFG might not be LALR(1) so can't easily be used directly by bison -- you may need to refactor it to make it LALR(1), or use a GLR parser with appropriate merging rules.

Help me find an appropriate ruby/python parser generator

Python is a pretty easy language to debug. You can just do import pdb pdb.settrace().

However, these parser generators supposedly come with good debugging facilities.

http://www.antlr.org/

http://www.dabeaz.com/ply/

http://pyparsing.wikispaces.com/

In response to bounty

Here is PLY debugging in action.

Source Code

tokens = (
'NAME','NUMBER',
)

literals = ['=','+','-','*','/', '(',')']

# Tokens

t_NAME = r'[a-zA-Z_][a-zA-Z0-9_]*'

def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t

t_ignore = " \t"

def t_newline(t):
r'\n+'
t.lexer.lineno += t.value.count("\n")

def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)

# Build the lexer
import ply.lex as lex
lex.lex(debug=1)

# Parsing rules

precedence = (
('left','+','-'),
('left','*','/'),
('right','UMINUS'),
)

# dictionary of names
names = { }

def p_statement_assign(p):
'statement : NAME "=" expression'
names[p[1]] = p[3]

def p_statement_expr(p):
'statement : expression'
print(p[1])

def p_expression_binop(p):
'''expression : expression '+' expression
| expression '-' expression
| expression '*' expression
| expression '/' expression'''
if p[2] == '+' : p[0] = p[1] + p[3]
elif p[2] == '-': p[0] = p[1] - p[3]
elif p[2] == '*': p[0] = p[1] * p[3]
elif p[2] == '/': p[0] = p[1] / p[3]

def p_expression_uminus(p):
"expression : '-' expression %prec UMINUS"
p[0] = -p[2]

def p_expression_group(p):
"expression : '(' expression ')'"
p[0] = p[2]

def p_expression_number(p):
"expression : NUMBER"
p[0] = p[1]

def p_expression_name(p):
"expression : NAME"
try:
p[0] = names[p[1]]
except LookupError:
print("Undefined name '%s'" % p[1])
p[0] = 0

def p_error(p):
if p:
print("Syntax error at '%s'" % p.value)
else:
print("Syntax error at EOF")

import ply.yacc as yacc
yacc.yacc()

import logging
logging.basicConfig(
level=logging.INFO,
filename="parselog.txt"
)

while 1:
try:
s = raw_input('calc > ')
except EOFError:
break
if not s: continue
yacc.parse(s, debug=1)

Output

lex: tokens   = ('NAME', 'NUMBER')
lex: literals = ['=', '+', '-', '*', '/', '(', ')']
lex: states = {'INITIAL': 'inclusive'}
lex: Adding rule t_NUMBER -> '\d+' (state 'INITIAL')
lex: Adding rule t_newline -> '\n+' (state 'INITIAL')
lex: Adding rule t_NAME -> '[a-zA-Z_][a-zA-Z0-9_]*' (state 'INITIAL')
lex: ==== MASTER REGEXS FOLLOW ====
lex: state 'INITIAL' : regex[0] = '(?P<t_NUMBER>\d+)|(?P<t_newline>\n+)|(?P<t_NAME>[a-zA-Z
_][a-zA-Z0-9_]*)'
calc > 2+3
PLY: PARSE DEBUG START

State : 0
Stack : . LexToken(NUMBER,2,1,0)
Action : Shift and goto state 3

State : 3
Stack : NUMBER . LexToken(+,'+',1,1)
Action : Reduce rule [expression -> NUMBER] with [2] and goto state 9
Result : <int @ 0x1a1896c> (2)

State : 6
Stack : expression . LexToken(+,'+',1,1)
Action : Shift and goto state 12

State : 12
Stack : expression + . LexToken(NUMBER,3,1,2)
Action : Shift and goto state 3

State : 3
Stack : expression + NUMBER . $end
Action : Reduce rule [expression -> NUMBER] with [3] and goto state 9
Result : <int @ 0x1a18960> (3)

State : 18
Stack : expression + expression . $end
Action : Reduce rule [expression -> expression + expression] with [2,'+',3] and goto state
3
Result : <int @ 0x1a18948> (5)

State : 6
Stack : expression . $end
Action : Reduce rule [statement -> expression] with [5] and goto state 2
5
Result : <NoneType @ 0x1e1ccef4> (None)

State : 4
Stack : statement . $end
Done : Returning <NoneType @ 0x1e1ccef4> (None)
PLY: PARSE DEBUG END
calc >

Parse Table generated at parser.out

Created by PLY version 3.2 (http://www.dabeaz.com/ply)

Grammar

Rule 0 S' -> statement
Rule 1 statement -> NAME = expression
Rule 2 statement -> expression
Rule 3 expression -> expression + expression
Rule 4 expression -> expression - expression
Rule 5 expression -> expression * expression
Rule 6 expression -> expression / expression
Rule 7 expression -> - expression
Rule 8 expression -> ( expression )
Rule 9 expression -> NUMBER
Rule 10 expression -> NAME

Terminals, with rules where they appear

( : 8
) : 8
* : 5
+ : 3
- : 4 7
/ : 6
= : 1
NAME : 1 10
NUMBER : 9
error :

Nonterminals, with rules where they appear

expression : 1 2 3 3 4 4 5 5 6 6 7 8
statement : 0

Parsing method: LALR

state 0

(0) S' -> . statement
(1) statement -> . NAME = expression
(2) statement -> . expression
(3) expression -> . expression + expression
(4) expression -> . expression - expression
(5) expression -> . expression * expression
(6) expression -> . expression / expression
(7) expression -> . - expression
(8) expression -> . ( expression )
(9) expression -> . NUMBER
(10) expression -> . NAME

NAME shift and go to state 1
- shift and go to state 2
( shift and go to state 5
NUMBER shift and go to state 3

expression shift and go to state 6
statement shift and go to state 4

state 1

(1) statement -> NAME . = expression
(10) expression -> NAME .

= shift and go to state 7
+ reduce using rule 10 (expression -> NAME .)
- reduce using rule 10 (expression -> NAME .)
* reduce using rule 10 (expression -> NAME .)
/ reduce using rule 10 (expression -> NAME .)
$end reduce using rule 10 (expression -> NAME .)

state 2

(7) expression -> - . expression
(3) expression -> . expression + expression
(4) expression -> . expression - expression
(5) expression -> . expression * expression
(6) expression -> . expression / expression
(7) expression -> . - expression
(8) expression -> . ( expression )
(9) expression -> . NUMBER
(10) expression -> . NAME

- shift and go to state 2
( shift and go to state 5
NUMBER shift and go to state 3
NAME shift and go to state 8

expression shift and go to state 9

state 3

(9) expression -> NUMBER .

+ reduce using rule 9 (expression -> NUMBER .)
- reduce using rule 9 (expression -> NUMBER .)
* reduce using rule 9 (expression -> NUMBER .)
/ reduce using rule 9 (expression -> NUMBER .)
$end reduce using rule 9 (expression -> NUMBER .)
) reduce using rule 9 (expression -> NUMBER .)

state 4

(0) S' -> statement .

state 5

(8) expression -> ( . expression )
(3) expression -> . expression + expression
(4) expression -> . expression - expression
(5) expression -> . expression * expression
(6) expression -> . expression / expression
(7) expression -> . - expression
(8) expression -> . ( expression )
(9) expression -> . NUMBER
(10) expression -> . NAME

- shift and go to state 2
( shift and go to state 5
NUMBER shift and go to state 3
NAME shift and go to state 8

expression shift and go to state 10

state 6

(2) statement -> expression .
(3) expression -> expression . + expression
(4) expression -> expression . - expression
(5) expression -> expression . * expression
(6) expression -> expression . / expression

$end reduce using rule 2 (statement -> expression .)
+ shift and go to state 12
- shift and go to state 11
* shift and go to state 13
/ shift and go to state 14

state 7

(1) statement -> NAME = . expression
(3) expression -> . expression + expression
(4) expression -> . expression - expression
(5) expression -> . expression * expression
(6) expression -> . expression / expression
(7) expression -> . - expression
(8) expression -> . ( expression )
(9) expression -> . NUMBER
(10) expression -> . NAME

- shift and go to state 2
( shift and go to state 5
NUMBER shift and go to state 3
NAME shift and go to state 8

expression shift and go to state 15

state 8

(10) expression -> NAME .

+ reduce using rule 10 (expression -> NAME .)
- reduce using rule 10 (expression -> NAME .)
* reduce using rule 10 (expression -> NAME .)
/ reduce using rule 10 (expression -> NAME .)
$end reduce using rule 10 (expression -> NAME .)
) reduce using rule 10 (expression -> NAME .)

state 9

(7) expression -> - expression .
(3) expression -> expression . + expression
(4) expression -> expression . - expression
(5) expression -> expression . * expression
(6) expression -> expression . / expression

+ reduce using rule 7 (expression -> - expression .)
- reduce using rule 7 (expression -> - expression .)
* reduce using rule 7 (expression -> - expression .)
/ reduce using rule 7 (expression -> - expression .)
$end reduce using rule 7 (expression -> - expression .)
) reduce using rule 7 (expression -> - expression .)

! + [ shift and go to state 12 ]
! - [ shift and go to state 11 ]
! * [ shift and go to state 13 ]
! / [ shift and go to state 14 ]

state 10

(8) expression -> ( expression . )
(3) expression -> expression . + expression
(4) expression -> expression . - expression
(5) expression -> expression . * expression
(6) expression -> expression . / expression

) shift and go to state 16
+ shift and go to state 12
- shift and go to state 11
* shift and go to state 13
/ shift and go to state 14

state 11

(4) expression -> expression - . expression
(3) expression -> . expression + expression
(4) expression -> . expression - expression
(5) expression -> . expression * expression
(6) expression -> . expression / expression
(7) expression -> . - expression
(8) expression -> . ( expression )
(9) expression -> . NUMBER
(10) expression -> . NAME

- shift and go to state 2
( shift and go to state 5
NUMBER shift and go to state 3
NAME shift and go to state 8

expression shift and go to state 17

state 12

(3) expression -> expression + . expression
(3) expression -> . expression + expression
(4) expression -> . expression - expression
(5) expression -> . expression * expression
(6) expression -> . expression / expression
(7) expression -> . - expression
(8) expression -> . ( expression )
(9) expression -> . NUMBER
(10) expression -> . NAME

- shift and go to state 2
( shift and go to state 5
NUMBER shift and go to state 3
NAME shift and go to state 8

expression shift and go to state 18

state 13

(5) expression -> expression * . expression
(3) expression -> . expression + expression
(4) expression -> . expression - expression
(5) expression -> . expression * expression
(6) expression -> . expression / expression
(7) expression -> . - expression
(8) expression -> . ( expression )
(9) expression -> . NUMBER
(10) expression -> . NAME

- shift and go to state 2
( shift and go to state 5
NUMBER shift and go to state 3
NAME shift and go to state 8

expression shift and go to state 19

state 14

(6) expression -> expression / . expression
(3) expression -> . expression + expression
(4) expression -> . expression - expression
(5) expression -> . expression * expression
(6) expression -> . expression / expression
(7) expression -> . - expression
(8) expression -> . ( expression )
(9) expression -> . NUMBER
(10) expression -> . NAME

- shift and go to state 2
( shift and go to state 5
NUMBER shift and go to state 3
NAME shift and go to state 8

expression shift and go to state 20

state 15

(1) statement -> NAME = expression .
(3) expression -> expression . + expression
(4) expression -> expression . - expression
(5) expression -> expression . * expression
(6) expression -> expression . / expression

$end reduce using rule 1 (statement -> NAME = expression .)
+ shift and go to state 12
- shift and go to state 11
* shift and go to state 13
/ shift and go to state 14

state 16

(8) expression -> ( expression ) .

+ reduce using rule 8 (expression -> ( expression ) .)
- reduce using rule 8 (expression -> ( expression ) .)
* reduce using rule 8 (expression -> ( expression ) .)
/ reduce using rule 8 (expression -> ( expression ) .)
$end reduce using rule 8 (expression -> ( expression ) .)
) reduce using rule 8 (expression -> ( expression ) .)

state 17

(4) expression -> expression - expression .
(3) expression -> expression . + expression
(4) expression -> expression . - expression
(5) expression -> expression . * expression
(6) expression -> expression . / expression

+ reduce using rule 4 (expression -> expression - expression .)
- reduce using rule 4 (expression -> expression - expression .)
$end reduce using rule 4 (expression -> expression - expression .)
) reduce using rule 4 (expression -> expression - expression .)
* shift and go to state 13
/ shift and go to state 14

! * [ reduce using rule 4 (expression -> expression - expression .) ]
! / [ reduce using rule 4 (expression -> expression - expression .) ]
! + [ shift and go to state 12 ]
! - [ shift and go to state 11 ]

state 18

(3) expression -> expression + expression .
(3) expression -> expression . + expression
(4) expression -> expression . - expression
(5) expression -> expression . * expression
(6) expression -> expression . / expression

+ reduce using rule 3 (expression -> expression + expression .)
- reduce using rule 3 (expression -> expression + expression .)
$end reduce using rule 3 (expression -> expression + expression .)
) reduce using rule 3 (expression -> expression + expression .)
* shift and go to state 13
/ shift and go to state 14

! * [ reduce using rule 3 (expression -> expression + expression .) ]
! / [ reduce using rule 3 (expression -> expression + expression .) ]
! + [ shift and go to state 12 ]
! - [ shift and go to state 11 ]

state 19

(5) expression -> expression * expression .
(3) expression -> expression . + expression
(4) expression -> expression . - expression
(5) expression -> expression . * expression
(6) expression -> expression . / expression

+ reduce using rule 5 (expression -> expression * expression .)
- reduce using rule 5 (expression -> expression * expression .)
* reduce using rule 5 (expression -> expression * expression .)
/ reduce using rule 5 (expression -> expression * expression .)
$end reduce using rule 5 (expression -> expression * expression .)
) reduce using rule 5 (expression -> expression * expression .)

! + [ shift and go to state 12 ]
! - [ shift and go to state 11 ]
! * [ shift and go to state 13 ]
! / [ shift and go to state 14 ]

state 20

(6) expression -> expression / expression .
(3) expression -> expression . + expression
(4) expression -> expression . - expression
(5) expression -> expression . * expression
(6) expression -> expression . / expression

+ reduce using rule 6 (expression -> expression / expression .)
- reduce using rule 6 (expression -> expression / expression .)
* reduce using rule 6 (expression -> expression / expression .)
/ reduce using rule 6 (expression -> expression / expression .)
$end reduce using rule 6 (expression -> expression / expression .)
) reduce using rule 6 (expression -> expression / expression .)

! + [ shift and go to state 12 ]
! - [ shift and go to state 11 ]
! * [ shift and go to state 13 ]
! / [ shift and go to state 14 ]

Does Ruby have an addon similar to Perl 6 grammars?

No. And, since Perl6 grammars are a language feature, and Ruby doesn't allow the language to be extended, it is actually impossible to implement this in an "addon".

However, there are numerous libraries for Ruby which implement different kinds of Parsing or Grammar Systems. The standard library already contains racc, which is an LALR(1) parser generator (comparable to and somewhat compatible with the venerable yacc). Then there is the ANTLR parser generator, which has a Ruby backend (although I am not sure whether that actually works).

The closest thing to Perl6 grammars in Ruby would be the Ruby-OMeta project (make sure to also take a look at Ryan Davis's fork), which unfortunately ist still under development. (Or rather, no longer under active development.)

So, keeping to stuff that actually exists, I recommend you take a look at the Grammar project and Treetop.

Tools to build a UI markup language parser

If your input is, as suggested, XML and your output is HTML, then that is the basic use case for XSLT. The whole point of XML is that you don't have to write your own parser, so if this was being done as a job of work rather than as a school project that would be the first technique to use. If you can't express it as a transformation, then you might look elsewhere.

If you don't want to use XML, then modern tools for plain-text languages include parser expression grammars and DSL synthesis tools such as Microsoft M.

PEGs free you from having to separately lex and parse, so a token can be context sensitive without complicating the grammar (as many tokens are in many languages), and some implementations mean you don't have to worry about left/right recursive loops.

DSL synthesis tools combine an IDE based grammar with a runtime semantics. Martin Fowler has a book on DSLs on his site.

But for a UI definition language that is the input to a transformation, either XML or some other standard mapping of structure (JSON, YAML) which can act as the input to an XSLT processor via the SAX interface would be the first thing I would try.

is there some code for a basic R parser?

You should have a better look:
https://github.com/halpo/parser/blob/master/inst/grammar/gram.y

What's the best way to parse a body of text against multiple (15+) regexes on each line?

I would suggest

  • Boost Spirit or
  • Antlr if the grammar is complex;
  • Xpressive if it's a little simpler,
  • Tokenizer and handmade code if it's trivial.

Good luck



Related Topics



Leave a reply



Submit