197 lines
6.2 KiB
Plaintext
197 lines
6.2 KiB
Plaintext
######################################################################
|
|
# The remainder of this file is from parsedesc.{g,py}
|
|
|
|
def append(lst, x):
|
|
"Imperative append"
|
|
lst.append(x)
|
|
return lst
|
|
|
|
def add_inline_token(tokens, str):
|
|
tokens.insert( 0, (str, eval(str, {}, {})) )
|
|
return Terminal(str)
|
|
|
|
def cleanup_choice(lst):
|
|
if len(lst) == 0: return Sequence([])
|
|
if len(lst) == 1: return lst[0]
|
|
return apply(Choice, tuple(lst))
|
|
|
|
def cleanup_sequence(lst):
|
|
if len(lst) == 1: return lst[0]
|
|
return apply(Sequence, tuple(lst))
|
|
|
|
def cleanup_rep(node, rep):
|
|
if rep == 'star': return Star(node)
|
|
elif rep == 'plus': return Plus(node)
|
|
else: return node
|
|
|
|
def resolve_name(tokens, id, args):
|
|
if id in map(lambda x: x[0], tokens):
|
|
# It's a token
|
|
if args:
|
|
print 'Warning: ignoring parameters on TOKEN %s<<%s>>' % (id, args)
|
|
return Terminal(id)
|
|
else:
|
|
# It's a name, so assume it's a nonterminal
|
|
return NonTerminal(id, args)
|
|
|
|
%%
|
|
parser ParserDescription:
|
|
option: "context-insensitive-scanner"
|
|
|
|
ignore: "[ \t\r\n]+"
|
|
ignore: "#.*?\r?\n"
|
|
token END: "$"
|
|
token ATTR: "<<.+?>>"
|
|
token STMT: "{{.+?}}"
|
|
token ID: '[a-zA-Z_][a-zA-Z_0-9]*'
|
|
token STR: '[rR]?\'([^\\n\'\\\\]|\\\\.)*\'|[rR]?"([^\\n"\\\\]|\\\\.)*"'
|
|
token LP: '\\('
|
|
token RP: '\\)'
|
|
token LB: '\\['
|
|
token RB: '\\]'
|
|
token OR: '[|]'
|
|
token STAR: '[*]'
|
|
token PLUS: '[+]'
|
|
token QUEST: '[?]'
|
|
token COLON: ':'
|
|
|
|
rule Parser: "parser" ID ":"
|
|
Options
|
|
Tokens
|
|
Rules<<Tokens>>
|
|
END
|
|
{{ return Generator(ID,Options,Tokens,Rules) }}
|
|
|
|
rule Options: {{ opt = {} }}
|
|
( "option" ":" Str {{ opt[Str] = 1 }} )*
|
|
{{ return opt }}
|
|
|
|
rule Tokens: {{ tok = [] }}
|
|
(
|
|
"token" ID ":" Str {{ tok.append( (ID,Str) ) }}
|
|
| "ignore" ":" Str {{ tok.append( ('#ignore',Str) ) }}
|
|
)*
|
|
{{ return tok }}
|
|
|
|
rule Rules<<tokens>>:
|
|
{{ rul = [] }}
|
|
(
|
|
"rule" ID OptParam ":" ClauseA<<tokens>>
|
|
{{ rul.append( (ID,OptParam,ClauseA) ) }}
|
|
)*
|
|
{{ return rul }}
|
|
|
|
rule ClauseA<<tokens>>:
|
|
ClauseB<<tokens>>
|
|
{{ v = [ClauseB] }}
|
|
( OR ClauseB<<tokens>> {{ v.append(ClauseB) }} )*
|
|
{{ return cleanup_choice(v) }}
|
|
|
|
rule ClauseB<<tokens>>:
|
|
{{ v = [] }}
|
|
( ClauseC<<tokens>> {{ v.append(ClauseC) }} )*
|
|
{{ return cleanup_sequence(v) }}
|
|
|
|
rule ClauseC<<tokens>>:
|
|
ClauseD<<tokens>>
|
|
( PLUS {{ return Plus(ClauseD) }}
|
|
| STAR {{ return Star(ClauseD) }}
|
|
| {{ return ClauseD }} )
|
|
|
|
rule ClauseD<<tokens>>:
|
|
STR {{ t = (STR, eval(STR,{},{})) }}
|
|
{{ if t not in tokens: tokens.insert( 0, t ) }}
|
|
{{ return Terminal(STR) }}
|
|
| ID OptParam {{ return resolve_name(tokens, ID, OptParam) }}
|
|
| LP ClauseA<<tokens>> RP {{ return ClauseA }}
|
|
| LB ClauseA<<tokens>> RB {{ return Option(ClauseA) }}
|
|
| STMT {{ return Eval(STMT[2:-2]) }}
|
|
|
|
rule OptParam: [ ATTR {{ return ATTR[2:-2] }} ] {{ return '' }}
|
|
rule Str: STR {{ return eval(STR,{},{}) }}
|
|
%%
|
|
|
|
# This replaces the default main routine
|
|
|
|
yapps_options = [
|
|
('context-insensitive-scanner', 'context-insensitive-scanner',
|
|
'Scan all tokens (see docs)')
|
|
]
|
|
|
|
def generate(inputfilename, outputfilename='', dump=0, **flags):
|
|
"""Generate a grammar, given an input filename (X.g)
|
|
and an output filename (defaulting to X.py)."""
|
|
|
|
if not outputfilename:
|
|
if inputfilename[-2:]=='.g': outputfilename = inputfilename[:-2]+'.py'
|
|
else: raise "Invalid Filename", outputfilename
|
|
|
|
print 'Input Grammar:', inputfilename
|
|
print 'Output File:', outputfilename
|
|
|
|
DIVIDER = '\n%%\n' # This pattern separates the pre/post parsers
|
|
preparser, postparser = None, None # Code before and after the parser desc
|
|
|
|
# Read the entire file
|
|
s = open(inputfilename,'r').read()
|
|
|
|
# See if there's a separation between the pre-parser and parser
|
|
f = find(s, DIVIDER)
|
|
if f >= 0: preparser, s = s[:f]+'\n\n', s[f+len(DIVIDER):]
|
|
|
|
# See if there's a separation between the parser and post-parser
|
|
f = find(s, DIVIDER)
|
|
if f >= 0: s, postparser = s[:f], '\n\n'+s[f+len(DIVIDER):]
|
|
|
|
# Create the parser and scanner
|
|
p = ParserDescription(ParserDescriptionScanner(s))
|
|
if not p: return
|
|
|
|
# Now parse the file
|
|
t = wrap_error_reporter(p, 'Parser')
|
|
if not t: return # Error
|
|
if preparser is not None: t.preparser = preparser
|
|
if postparser is not None: t.postparser = postparser
|
|
|
|
# Check the options
|
|
for f in t.options.keys():
|
|
for opt,_,_ in yapps_options:
|
|
if f == opt: break
|
|
else:
|
|
print 'Warning: unrecognized option', f
|
|
# Add command line options to the set
|
|
for f in flags.keys(): t.options[f] = flags[f]
|
|
|
|
# Generate the output
|
|
if dump:
|
|
t.dump_information()
|
|
else:
|
|
t.output = open(outputfilename, 'w')
|
|
t.generate_output()
|
|
|
|
if __name__=='__main__':
|
|
import sys, getopt
|
|
optlist, args = getopt.getopt(sys.argv[1:], 'f:', ['dump'])
|
|
if not args or len(args) > 2:
|
|
print 'Usage:'
|
|
print ' python', sys.argv[0], '[flags] input.g [output.py]'
|
|
print 'Flags:'
|
|
print (' --dump' + ' '*40)[:35] + 'Dump out grammar information'
|
|
for flag, _, doc in yapps_options:
|
|
print (' -f' + flag + ' '*40)[:35] + doc
|
|
else:
|
|
# Read in the options and create a list of flags
|
|
flags = {}
|
|
for opt in optlist:
|
|
for flag, name, _ in yapps_options:
|
|
if opt == ('-f', flag):
|
|
flags[name] = 1
|
|
break
|
|
else:
|
|
if opt == ('--dump', ''):
|
|
flags['dump'] = 1
|
|
else:
|
|
print 'Warning - unrecognized option: ', opt[0], opt[1]
|
|
|
|
apply(generate, tuple(args), flags)
|