PyLexYacc.jl

A Julia wrapper for the Python Lex-Yacc package.
Popularity
2 Stars
Updated Last
5 Years Ago
Started In
November 2013

PyLexYacc.jl

Description

A Julia wrapper for the Python Lex-Yacc package maintained by David Beazley.

Requirements

Depends on the PyCall julia package and the PLY and attrdict Python packages (both available from PyPI).

Example

This example mirrors this one, in Python.

using PyLexYacc


module rules
	using PyLexYacc

	tokens = (
		"NAME","NUMBER",
		"PLUS","MINUS","TIMES","DIVIDE","EQUALS",
		"LPAREN","RPAREN"
		)

	# Tokens

	t_PLUS    = "\\+"
	t_MINUS   = "-"
	t_TIMES   = "\\*"
	t_DIVIDE  = "/"
	t_EQUALS  = "="
	t_LPAREN  = "\\("
	t_RPAREN  = "\\)"
	t_NAME    = "[a-zA-Z_][a-zA-Z0-9_]*"
	
	t_NUMBER = rule("\\d+") do t, lexer
			t["value"] = int(t["value"])
			return t
		end

	t_ignore  = " \t"

	t_newline = rule("[\\r\\n]+") do t, lexer
			lexer["lineno"] += count((x)->(x == '\n'),t["value"])
			return nothing
		end

	t_error = rule() do t, lexer
			@printf("Illegal character '%s'\n", t["value"][1])
			skip(lexer, 1)
			return t
		end

	precedence = (
			("left", "PLUS", "MINUS"),
			("left", "TIMES", "DIVIDE"),
			("right", "UMINUS")
		)
	
	vars = Dict()  # symbol table

	p_statement_assign = parserule("statement : NAME EQUALS expression") do t
			vars[t[2]] = t[4]
		end

	p_statement_expr = parserule("statement : expression") do t
			println(t[2])
		end

	p_expression_binop = parserule(
			"""expression : expression PLUS expression
			              | expression MINUS expression
			              | expression TIMES expression
			              | expression DIVIDE expression""") do t
			if t[3] == "+"
				t[1] = t[2] + t[4]
			elseif t[3] == "-"
				t[1] = t[2] - t[4]
			elseif t[3] == "*"
				t[1] = t[2] * t[4]
			elseif t[3] == "/"
				t[1] = t[2] / t[4]
			end
		end

	p_expression_uminus = parserule("expression : MINUS expression %prec UMINUS") do t
			t[1] = -t[3]
		end

	p_expression_group = parserule("expression : LPAREN expression RPAREN") do t
			t[1] = t[3]
		end

	p_expression_number = parserule("expression : NUMBER") do t
			t[1] = t[2]
		end

	p_expression_name = parserule("expression : NAME") do t
			try
				t[1] = vars[t[2]]
			catch
				@printf("Undefined name '%s'\n", t[2])
				t[1] = 0
			end
		end

	p_error = rule() do t, lexer  # the error rule actually takes a LexToken so make it a rule()
			@printf("Syntax error at '%s'\n", t["value"][1])
		end
end

l = lexer(rules)
p = parser(rules, "statement")  # specifiy start rule here or through start variable in module

# replicates the input function using in calc.py
function input(str::String)
	print(str)
	return readline(STDIN)
end

println("\\q to quit")
line = input("calc > ")
while chomp(line) != "\\q"
	parse(p, l, line)
	line = input("calc > ")
end

Noteworthy differences from PLY

  • Indexing is Julia-style 1-indexing
  • Rule functions are created using the rule() and parserule() functions
  • A function passed to rule() must accept two arguments (the LexToken and the Lexer), while the function passed to parserule() just accepts a YaccProduction instance (as in PLY)
  • You cannot simply call lex.lex() or yacc.yacc() to get variables in the calling module--this functionality in PLY uses Python reflection functions that can't be implemented cross-language
  • lexer matching patterns and parser grammar rules are passed as arguments to rule() and parserule(), not as docstrings (there are no docstrings in Julia)
  • the parse() method takes a lexer as a mandatory second argument

Licence

MIT