[Populate] Added pybars and pymeta

This commit is contained in:
Salvador E. Tropea 2022-10-28 07:16:05 -03:00
parent f3049351da
commit 8512b41feb
10 changed files with 3010 additions and 2 deletions

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3
# Author: Jan Mrázek
# License: MIT
@ -21,7 +20,7 @@ try:
except ModuleNotFoundError:
InlineParser = mistune.InlineLexer
HTMLRenderer = mistune.Renderer
import pybars # type: ignore
from . import pybars # type: ignore
import yaml
from . import mdrenderer

View File

@ -0,0 +1,50 @@
#
# Copyright (c) 2012, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# GNU Lesser General Public License version 3 (see the file LICENSE).
# same format as sys.version_info: "A tuple containing the five components of
# the version number: major, minor, micro, releaselevel, and serial. All
# values except releaselevel are integers; the release level is 'alpha',
# 'beta', 'candidate', or 'final'. The version_info value corresponding to the
# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
# releaselevel of 'dev' for unreleased under-development code.
#
# If the releaselevel is 'alpha' then the major/minor/micro components are not
# established at this point, and setup.py will use a version of next-$(revno).
# If the releaselevel is 'final', then the tarball will be major.minor.micro.
# Otherwise it is major.minor.micro~$(revno).
from ._compiler import (
Compiler,
strlist,
Scope,
PybarsError
)
__version__ = '0.9.7'
__version_info__ = (0, 9, 7, 'final', 0)
__all__ = [
'Compiler',
'log',
'strlist',
'Scope',
'PybarsError'
]
def log(value):
return None

View File

@ -0,0 +1,943 @@
#
# Copyright (c) 2015 Will Bond, Mjumbe Wawatu Ukweli, 2012 Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# GNU Lesser General Public License version 3 (see the file LICENSE).
"""The compiler for pybars."""
import os
import re
import sys
from types import ModuleType
import linecache
# The following code allows importing pybars from the current dir
prev_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if sys.path[0] != prev_dir:
try:
sys.path.remove(prev_dir)
except ValueError:
pass
sys.path.insert(0, prev_dir)
import pybars
from pybars import _templates
from .pymeta.grammar import OMeta
__all__ = [
'Compiler',
'strlist',
'Scope'
]
__metaclass__ = type
# This allows the code to run on Python 2 and 3 by
# creating a consistent reference for the appropriate
# string class
try:
str_class = unicode
except NameError:
# Python 3 support
str_class = str
# backward compatibility for basestring for python < 2.3
try:
basestring
except NameError:
basestring = str
# Flag for testing
debug = False
# Note that unless we presume handlebars is only generating valid html, we have
# to accept anything - so a broken template won't be all that visible - it will
# just render literally (because the anything rule matches it).
# this grammar generates a tokenised tree
handlebars_grammar = r"""
template ::= (<text> | <templatecommand>)*:body => ['template'] + body
text ::= <newline_text> | <whitespace_text> | <other_text>
newline_text ::= (~(<start>) ('\r'?'\n'):char) => ('newline', u'' + char)
whitespace_text ::= (~(<start>) (' '|'\t'))+:text => ('whitespace', u''.join(text))
other_text ::= (~(<start>) <anything>)+:text => ('literal', u''.join(text))
other ::= <anything>:char => ('literal', u'' + char)
templatecommand ::= <blockrule>
| <comment>
| <escapedexpression>
| <expression>
| <partial>
| <rawblock>
start ::= '{' '{'
finish ::= '}' '}'
comment ::= <start> '!' (~(<finish>) <anything>)* <finish> => ('comment', )
space ::= ' '|'\t'|'\r'|'\n'
arguments ::= (<space>+ (<kwliteral>|<literal>|<path>|<subexpression>))*:arguments => arguments
subexpression ::= '(' <spaces> <path>:p (<space>+ (<kwliteral>|<literal>|<path>|<subexpression>))*:arguments <spaces> ')' => ('subexpr', p, arguments)
expression_inner ::= <spaces> <path>:p <arguments>:arguments <spaces> <finish> => (p, arguments)
expression ::= <start> '{' <expression_inner>:e '}' => ('expand', ) + e
| <start> '&' <expression_inner>:e => ('expand', ) + e
escapedexpression ::= <start> <expression_inner>:e => ('escapedexpand', ) + e
block_inner ::= <spaces> <symbol>:s <arguments>:args <spaces> <finish>
=> (u''.join(s), args)
partial_inner ::= <spaces> <partialname>:s <arguments>:args <spaces> <finish>
=> (s, args)
alt_inner ::= <spaces> ('^' | 'e' 'l' 's' 'e') <spaces> <finish>
partial ::= <start> '>' <partial_inner>:i => ('partial',) + i
path ::= ~('/') <pathseg>+:segments => ('path', segments)
kwliteral ::= <safesymbol>:s '=' (<literal>|<path>|<subexpression>):v => ('kwparam', s, v)
literal ::= (<string>|<integer>|<boolean>|<null>|<undefined>):thing => ('literalparam', thing)
string ::= '"' <notdquote>*:ls '"' => u'"' + u''.join(ls) + u'"'
| "'" <notsquote>*:ls "'" => u"'" + u''.join(ls) + u"'"
integer ::= '-'?:sign <digit>+:ds => int((sign if sign else '') + ''.join(ds))
boolean ::= <false>|<true>
false ::= 'f' 'a' 'l' 's' 'e' => False
true ::= 't' 'r' 'u' 'e' => True
null ::= ('n' 'u' 'l' 'l') => None
undefined ::= ('u' 'n' 'd' 'e' 'f' 'i' 'n' 'e' 'd') => None
notdquote ::= <escapedquote>
| '\n' => '\\n'
| '\r' => '\\r'
| '\\' => '\\\\'
| (~('"') <anything>)
notsquote ::= <escapedquote>
| '\n' => '\\n'
| '\r' => '\\r'
| '\\' => '\\\\'
| (~("'") <anything>)
escapedquote ::= '\\' '"' => '\\"'
| "\\" "'" => "\\'"
notclosebracket ::= (~(']') <anything>)
safesymbol ::= ~<alt_inner> '['? (<letter>|'_'):start (<letterOrDigit>|'_')+:symbol ']'? => start + u''.join(symbol)
symbol ::= ~<alt_inner> '['? (<letterOrDigit>|'-'|'@')+:symbol ']'? => u''.join(symbol)
partialname ::= <subexpression>:s => s
| ~<alt_inner> ('['|'"')? (~(<space>|<finish>|']'|'"' ) <anything>)+:symbol (']'|'"')? => ('literalparam', '"' + u''.join(symbol) + '"')
pathseg ::= '[' <notclosebracket>+:symbol ']' => u''.join(symbol)
| ('@' '.' '.' '/') => u'@@_parent'
| <symbol>
| '/' => u''
| ('.' '.' '/') => u'@_parent'
| '.' => u''
pathfinish :expected ::= <start> '/' <path>:found ?(found == expected) <finish>
symbolfinish :expected ::= <start> '/' <symbol>:found ?(found == expected) <finish>
blockrule ::= <start> '#' <block_inner>:i
<template>:t <alttemplate>:alt_t <symbolfinish i[0]> => ('block',) + i + (t, alt_t)
| <start> '^' <block_inner>:i
<template>:t <alttemplate>:alt_t <symbolfinish i[0]> => ('invertedblock',) + i + (t, alt_t)
alttemplate ::= (<start> <alt_inner> <template>)?:alt_t => alt_t or []
rawblockstart ::= <start> <start> <block_inner>:i <finish> => i
rawblockfinish :expected ::= <start> <symbolfinish expected> <finish>
rawblock ::= <rawblockstart>:i (~(<rawblockfinish i[0]>) <anything>)*:r <rawblockfinish i[0]>
=> ('rawblock',) + i + (''.join(r),)
"""
# this grammar compiles the template to python
compile_grammar = """
compile ::= <prolog> <rule>* => builder.finish()
prolog ::= "template" => builder.start()
rule ::= <literal>
| <expand>
| <escapedexpand>
| <comment>
| <block>
| <invertedblock>
| <rawblock>
| <partial>
block ::= [ "block" <anything>:symbol [<arg>*:arguments] [<compile>:t] [<compile>?:alt_t] ] => builder.add_block(symbol, arguments, t, alt_t)
comment ::= [ "comment" ]
literal ::= [ ( "literal" | "newline" | "whitespace" ) :value ] => builder.add_literal(value)
expand ::= [ "expand" <path>:value [<arg>*:arguments]] => builder.add_expand(value, arguments)
escapedexpand ::= [ "escapedexpand" <path>:value [<arg>*:arguments]] => builder.add_escaped_expand(value, arguments)
invertedblock ::= [ "invertedblock" <anything>:symbol [<arg>*:arguments] [<compile>:t] [<compile>?:alt_t] ] => builder.add_invertedblock(symbol, arguments, t, alt_t)
rawblock ::= [ "rawblock" <anything>:symbol [<arg>*:arguments] <anything>:raw ] => builder.add_rawblock(symbol, arguments, raw)
partial ::= ["partial" <complexarg>:symbol [<arg>*:arguments]] => builder.add_partial(symbol, arguments)
path ::= [ "path" [<pathseg>:segment]] => ("simple", segment)
| [ "path" [<pathseg>+:segments] ] => ("complex", u"resolve(context, u'" + u"', u'".join(segments) + u"')" )
complexarg ::= [ "path" [<pathseg>+:segments] ] => u"resolve(context, u'" + u"', u'".join(segments) + u"')"
| [ "subexpr" ["path" <pathseg>:name] [<arg>*:arguments] ] => u'resolve_subexpr(helpers, "' + name + '", context' + (u', ' + u', '.join(arguments) if arguments else u'') + u')'
| [ "literalparam" <anything>:value ] => {str_class}(value)
arg ::= [ "kwparam" <anything>:symbol <complexarg>:a ] => {str_class}(symbol) + '=' + a
| <complexarg>
pathseg ::= "/" => ''
| "." => ''
| "" => ''
| "this" => ''
pathseg ::= <anything>:symbol => u''.join(symbol)
""" # noqa: E501
compile_grammar = compile_grammar.format(str_class=str_class.__name__)
class PybarsError(Exception):
pass
class strlist(list):
"""A quasi-list to let the template code avoid special casing."""
def __str__(self): # Python 3
return ''.join(self)
def __unicode__(self): # Python 2
return u''.join(self)
def grow(self, thing):
"""Make the list longer, appending for unicode, extending otherwise."""
if type(thing) == str_class:
self.append(thing)
# This will only ever match in Python 2 since str_class is str in
# Python 3.
elif type(thing) == str:
self.append(unicode(thing)) # noqa: F821 undefined name 'unicode'
else:
# Recursively expand to a flat list; may deserve a C accelerator at
# some point.
for element in thing:
self.grow(element)
_map = {
'&': '&amp;',
'"': '&quot;',
"'": '&#x27;',
'`': '&#x60;',
'<': '&lt;',
'>': '&gt;',
}
def substitute(match, _map=_map):
return _map[match.group(0)]
_escape_re = re.compile(r"&|\"|'|`|<|>")
def escape(something, _escape_re=_escape_re, substitute=substitute):
return _escape_re.sub(substitute, something)
def pick(context, name, default=None):
try:
return context[name]
except (KeyError, TypeError, AttributeError):
if isinstance(name, basestring):
try:
exists = hasattr(context, name)
except UnicodeEncodeError:
# Python 2 raises UnicodeEncodeError on non-ASCII strings
pass
else:
if exists:
return getattr(context, name)
if hasattr(context, 'get'):
return context.get(name)
return default
sentinel = object()
class Scope:
def __init__(self, context, parent, root, overrides=None, index=None, key=None, first=None, last=None):
self.context = context
self.parent = parent
self.root = root
# Must be dict of keys and values
self.overrides = overrides
self.index = index
self.key = key
self.first = first
self.last = last
def get(self, name, default=None):
if name == '@root':
return self.root
if name == '@_parent':
return self.parent
if name == '@index' and self.index is not None:
return self.index
if name == '@key' and self.key is not None:
return self.key
if name == '@first' and self.first is not None:
return self.first
if name == '@last' and self.last is not None:
return self.last
if name == 'this':
return self.context
if self.overrides and name in self.overrides:
return self.overrides[name]
return pick(self.context, name, default)
__getitem__ = get
def __len__(self):
return len(self.context)
# Added for Python 3
def __str__(self):
return str(self.context)
# Only called in Python 2
def __unicode__(self):
return unicode(self.context) # noqa: F821 undefined name 'unicode'
def resolve(context, *segments):
carryover_data = False
# This makes sure that bare "this" paths don't return a Scope object
if segments == ('',) and isinstance(context, Scope):
return context.get('this')
for segment in segments:
# Handle @../index syntax by popping the extra @ along the segment path
if carryover_data:
carryover_data = False
segment = u'@%s' % segment
if len(segment) > 1 and segment[0:2] == '@@':
segment = segment[1:]
carryover_data = True
if context is None:
return None
if segment in (None, ""):
continue
if type(context) in (list, tuple):
if segment == 'length':
return len(context)
offset = int(segment)
context = context[offset]
elif isinstance(context, Scope):
context = context.get(segment)
else:
context = pick(context, segment)
return context
def resolve_subexpr(helpers, name, context, *args, **kwargs):
if name not in helpers:
raise PybarsError(u"Could not find property %s" % (name,))
return helpers[name](context, *args, **kwargs)
def prepare(value, should_escape):
"""
Prepares a value to be added to the result
:param value:
The value to add to the result
:param should_escape:
If the string should be HTML-escaped
:return:
A unicode string or strlist
"""
if value is None:
return u''
type_ = type(value)
if type_ is not strlist:
if type_ is not str_class:
if type_ is bool:
value = u'true' if value else u'false'
else:
value = str_class(value)
if should_escape:
value = escape(value)
return value
def ensure_scope(context, root):
return context if isinstance(context, Scope) else Scope(context, context, root)
def _each(this, options, context):
result = strlist()
# All sequences in python have a length
try:
last_index = len(context) - 1
# If there are no items, we want to trigger the else clause
if last_index < 0:
raise IndexError()
except (TypeError, IndexError):
return options['inverse'](this)
# We use the presence of a keys method to determine if the
# key attribute should be passed to the block handler
has_keys = hasattr(context, 'keys')
index = 0
for value in context:
kwargs = {
'index': index,
'first': index == 0,
'last': index == last_index
}
if has_keys:
kwargs['key'] = value
value = context[value]
scope = Scope(value, this, options['root'], **kwargs)
# Necessary because of cases such as {{^each things}}test{{/each}}.
try:
result.grow(options['fn'](scope))
except TypeError:
pass
index += 1
return result
def _if(this, options, context):
if hasattr(context, '__call__'):
context = context(this)
if context:
return options['fn'](this)
else:
return options['inverse'](this)
def _log(this, context):
pybars.log(context)
def _unless(this, options, context):
if not context:
return options['fn'](this)
def _lookup(this, context, key):
try:
return context[key]
except (KeyError, IndexError, TypeError):
return
def _blockHelperMissing(this, options, context):
if hasattr(context, '__call__'):
context = context(this)
if context != u"" and not context:
return options['inverse'](this)
if type(context) in (list, strlist, tuple):
return _each(this, options, context)
if context is True:
callwith = this
else:
callwith = context
return options['fn'](callwith)
def _helperMissing(scope, name, *args):
if not args:
return None
raise PybarsError(u"Could not find property %s" % (name,))
def _with(this, options, context):
return options['fn'](context)
# scope for the compiled code to reuse globals
_pybars_ = {
'helpers': {
'blockHelperMissing': _blockHelperMissing,
'each': _each,
'if': _if,
'helperMissing': _helperMissing,
'log': _log,
'unless': _unless,
'with': _with,
'lookup': _lookup,
},
}
class FunctionContainer:
"""
Used as a container for functions by the CodeBuidler
"""
def __init__(self, name, code):
self.name = name
self.code = code
@property
def full_code(self):
headers = (
u'import pybars\n'
u'\n'
u'if pybars.__version__ != %s:\n'
u' raise pybars.PybarsError("This template was precompiled with pybars3 version %s, running version %%s" %% pybars.__version__)\n'
u'\n'
u'from pybars import strlist, Scope, PybarsError\n'
u'from pybars._compiler import _pybars_, escape, resolve, resolve_subexpr, prepare, ensure_scope\n'
u'\n'
u'from functools import partial\n'
u'\n'
u'\n'
) % (repr(pybars.__version__), pybars.__version__)
return headers + self.code
class CodeBuilder:
"""Builds code for a template."""
def __init__(self):
self._reset()
def _reset(self):
self.stack = []
self.var_counter = 1
self.render_counter = 0
def start(self):
function_name = 'render' if self.render_counter == 0 else 'block_%s' % self.render_counter
self.render_counter += 1
self.stack.append((strlist(), {}, function_name))
self._result, self._locals, _ = self.stack[-1]
# Context may be a user hash or a Scope (which injects '@_parent' to
# implement .. lookups). The JS implementation uses a vector of scopes
# and then interprets a linear walk-up, which is why there is a
# disabled test showing arbitrary complex path manipulation: the scope
# approach used here will probably DTRT but may be slower: reevaluate
# when profiling.
if len(self.stack) == 1:
self._result.grow([
u"def render(context, helpers=None, partials=None, root=None):\n"
u" _helpers = dict(_pybars_['helpers'])\n"
u" if helpers is not None:\n"
u" _helpers.update(helpers)\n"
u" helpers = _helpers\n"
u" if partials is None:\n"
u" partials = {}\n"
u" called = root is None\n"
u" if called:\n"
u" root = context\n"
])
else:
self._result.grow(u"def %s(context, helpers, partials, root):\n" % function_name)
self._result.grow(u" result = strlist()\n")
self._result.grow(u" context = ensure_scope(context, root)\n")
def finish(self):
lines, ns, function_name = self.stack.pop(-1)
# Ensure the result is a string and not a strlist
if len(self.stack) == 0:
self._result.grow(u" if called:\n")
self._result.grow(u" result = %s(result)\n" % str_class.__name__)
self._result.grow(u" return result\n")
source = str_class(u"".join(lines))
self._result = self.stack and self.stack[-1][0]
self._locals = self.stack and self.stack[-1][1]
code = ''
for key in ns:
if isinstance(ns[key], FunctionContainer):
code += ns[key].code + '\n'
else:
code += '%s = %s\n' % (key, repr(ns[key]))
code += source
result = FunctionContainer(function_name, code)
if debug and len(self.stack) == 0:
print('Compiled Python')
print('---------------')
print(result.full_code)
return result
def _wrap_nested(self, name):
return u"partial(%s, helpers=helpers, partials=partials, root=root)" % name
def add_block(self, symbol, arguments, nested, alt_nested):
name = nested.name
self._locals[name] = nested
if alt_nested:
alt_name = alt_nested.name
self._locals[alt_name] = alt_nested
call = self.arguments_to_call(arguments)
self._result.grow([
u" options = {'fn': %s}\n" % self._wrap_nested(name),
u" options['helpers'] = helpers\n"
u" options['partials'] = partials\n"
u" options['root'] = root\n"
])
if alt_nested:
self._result.grow([
u" options['inverse'] = ",
self._wrap_nested(alt_name),
u"\n"
])
else:
self._result.grow([
u" options['inverse'] = lambda this: None\n"
])
self._result.grow([
u" value = helper = helpers.get(u'%s')\n" % symbol,
u" if value is None:\n"
u" value = resolve(context, u'%s')\n" % symbol,
u" if helper and hasattr(helper, '__call__'):\n"
u" value = helper(context, options%s\n" % call,
u" else:\n"
u" value = helpers['blockHelperMissing'](context, options, value)\n"
u" result.grow(value or '')\n"
])
def add_literal(self, value):
self._result.grow(u" result.append(%s)\n" % repr(value))
def _lookup_arg(self, arg):
if not arg:
return u"context"
return arg
def arguments_to_call(self, arguments):
params = list(map(self._lookup_arg, arguments))
output = u', '.join(params) + u')'
if len(params) > 0:
output = u', ' + output
return output
def find_lookup(self, path, path_type, call):
if path_type == "simple": # simple names can reference helpers.
# TODO: compile this whole expression in the grammar; for now,
# fugly but only a compile time overhead.
# XXX: just rm.
realname = path.replace('.get("', '').replace('")', '')
self._result.grow([
u" value = helpers.get(u'%s')\n" % realname,
u" if value is None:\n"
u" value = resolve(context, u'%s')\n" % path,
])
else:
realname = None
self._result.grow(u" value = %s\n" % path)
self._result.grow([
u" if hasattr(value, '__call__'):\n"
u" value = value(context%s\n" % call,
])
if realname:
self._result.grow(
u" elif value is None:\n"
u" value = helpers['helperMissing'](context, u'%s'%s\n"
% (realname, call)
)
def add_escaped_expand(self, path_type_path, arguments):
(path_type, path) = path_type_path
call = self.arguments_to_call(arguments)
self.find_lookup(path, path_type, call)
self._result.grow([
u" result.grow(prepare(value, True))\n"
])
def add_expand(self, path_type_path, arguments):
(path_type, path) = path_type_path
call = self.arguments_to_call(arguments)
self.find_lookup(path, path_type, call)
self._result.grow([
u" result.grow(prepare(value, False))\n"
])
def _debug(self):
self._result.grow(u" import pdb;pdb.set_trace()\n")
def add_invertedblock(self, symbol, arguments, nested, alt_nested):
name = nested.name
self._locals[name] = nested
if alt_nested:
alt_name = alt_nested.name
self._locals[alt_name] = alt_nested
call = self.arguments_to_call(arguments)
self._result.grow([
u" options = {'inverse': %s}\n" % self._wrap_nested(name),
u" options['helpers'] = helpers\n"
u" options['partials'] = partials\n"
u" options['root'] = root\n"
])
if alt_nested:
self._result.grow([
u" options['fn'] = ",
self._wrap_nested(alt_name),
u"\n"
])
else:
self._result.grow([
u" options['fn'] = lambda this: None\n"
])
self._result.grow([
u" value = helper = helpers.get(u'%s')\n" % symbol,
u" if value is None:\n"
u" value = resolve(context, u'%s')\n" % symbol,
u" if helper and hasattr(helper, '__call__'):\n"
u" value = helper(context, options%s\n" % call,
u" else:\n"
u" value = helpers['blockHelperMissing'](context, options, value)\n"
u" result.grow(value or '')\n"
])
def add_rawblock(self, symbol, arguments, raw):
call = self.arguments_to_call(arguments)
self._result.grow([
u" options = {'fn': lambda this: %s}\n" % repr(raw),
u" options['helpers'] = helpers\n"
u" options['partials'] = partials\n"
u" options['root'] = root\n"
u" options['inverse'] = lambda this: None\n"
u" helper = helpers.get(u'%s')\n" % symbol,
u" if helper and hasattr(helper, '__call__'):\n"
u" value = helper(context, options%s\n" % call,
u" else:\n"
u" value = %s\n" % repr(raw),
u" result.grow(value or '')\n"
])
def _invoke_template(self, fn_name, this_name):
self._result.grow([
u" result.grow(",
fn_name,
u"(",
this_name,
u", helpers=helpers, partials=partials, root=root))\n"
])
def add_partial(self, symbol, arguments):
arg = ""
overrides = None
positional_args = 0
if arguments:
for argument in arguments:
kwmatch = re.match(r'(\w+)=(.+)$', argument)
if kwmatch:
if not overrides:
overrides = {}
overrides[kwmatch.group(1)] = kwmatch.group(2)
else:
if positional_args != 0:
raise PybarsError("An extra positional argument was passed to a partial")
positional_args += 1
arg = argument
overrides_literal = 'None'
if overrides:
overrides_literal = u'{'
for key in overrides:
overrides_literal += u'"%s": %s, ' % (key, overrides[key])
overrides_literal += u'}'
self._result.grow([u" overrides = %s\n" % overrides_literal])
self._result.grow([
u" partialName = %s\n" % symbol,
u" if partialName not in partials:\n",
u" raise PybarsError('The partial %s could not be found' % partialName)\n",
u" inner = partials[partialName]\n",
u" scope = Scope(%s, context, root, overrides=overrides)\n" % self._lookup_arg(arg)])
self._invoke_template("inner", "scope")
class Compiler:
"""A handlebars template compiler.
The compiler is not threadsafe: you need one per thread because of the
state in CodeBuilder.
"""
_handlebars = OMeta.makeGrammar(handlebars_grammar, {}, 'handlebars')
_builder = CodeBuilder()
_compiler = OMeta.makeGrammar(compile_grammar, {'builder': _builder})
def __init__(self):
self._helpers = {}
self.template_counter = 1
def _extract_word(self, source, position):
"""
Extracts the word that falls at or around a specific position
:param source:
The template source as a unicode string
:param position:
The position where the word falls
:return:
The word
"""
boundry = re.search(r'{{|{|\s|$', source[:position][::-1])
start_offset = boundry.end() if boundry.group(0).startswith('{') else boundry.start()
boundry = re.search(r'}}|}|\s|$', source[position:])
end_offset = boundry.end() if boundry.group(0).startswith('}') else boundry.start()
return source[position - start_offset:position + end_offset]
def _generate_code(self, source):
"""
Common compilation code shared between precompile() and compile()
:param source:
The template source as a unicode string
:return:
A tuple of (function, source_code)
"""
if not isinstance(source, str_class):
raise PybarsError("Template source must be a unicode string")
source = self.whitespace_control(source)
tree, (position, _) = self._handlebars(source).apply('template')
if debug:
print('\nAST')
print('---')
print(tree)
print('')
if position < len(source):
line_num = source.count('\n') + 1
beginning_of_line = source.rfind('\n', 0, position)
if beginning_of_line == -1:
char_num = position
else:
char_num = position - beginning_of_line
word = self._extract_word(source, position)
raise PybarsError("Error at character %s of line %s near %s" % (char_num, line_num, word))
# Ensure the builder is in a clean state - kinda gross
self._compiler.globals['builder']._reset()
output = self._compiler(tree).apply('compile')[0]
return output
def whitespace_control(self, source):
"""
Preprocess source to handle whitespace control and remove extra block
whitespaces.
:param source:
The template source as a unicode string
:return:
The processed template source as a unicode string
"""
cleanup_sub = re.compile(
# Clean-up whitespace control marks and spaces between blocks tags
r'(?<={{)~|~(?=}})|(?<=}})[ \t]+(?={{)').sub
return re.sub(
# Whitespace control using "~" mark
r'~}}\s*|\s*{{~|'
# Whitespace around alone blocks tags that in a line
r'(?<=\n)([ \t]*{{(#[^{}]+|/[^{}]+|![^{}]+|else|else if [^{}]+)}}[ \t]*)+\r?\n|'
# Whitespace aroud alone blocks tag on the first line
r'^([ \t]*{{(#[^{}]+|![^{}]+)}}[ \t]*)+\r?\n|'
# Whitespace aroud alone blocks tag on the last line
r'\r?\n([ \t]*{{(/[^{}]+|![^{}]+)}}[ \t]*)+$',
lambda match: cleanup_sub('', match.group(0).strip()), source)
def precompile(self, source):
"""
Generates python source code that can be saved to a file for caching
:param source:
The template to generate source for - should be a unicode string
:return:
Python code as a unicode string
"""
return self._generate_code(source).full_code
def compile(self, source, path=None):
"""Compile source to a ready to run template.
:param source:
The template to compile - should be a unicode string
:return:
A template function ready to execute
"""
container = self._generate_code(source)
def make_module_name(name, suffix=None):
output = 'pybars._templates.%s' % name
if suffix:
output += '_%s' % suffix
return output
if not path:
path = '_template'
generate_name = True
else:
path = path.replace('\\', '/')
path = path.replace('/', '_')
mod_name = make_module_name(path)
generate_name = mod_name in sys.modules
if generate_name:
mod_name = make_module_name(path, self.template_counter)
while mod_name in sys.modules:
self.template_counter += 1
mod_name = make_module_name(path, self.template_counter)
mod = ModuleType(mod_name)
filename = '%s.py' % mod_name.replace('pybars.', '').replace('.', '/')
exec(compile(container.full_code, filename, 'exec', dont_inherit=True), mod.__dict__)
sys.modules[mod_name] = mod
linecache.getlines(filename, mod.__dict__)
return mod.__dict__[container.name]
def template(self, code):
def _render(context, helpers=None, partials=None, root=None):
ns = {
'context': context,
'helpers': helpers,
'partials': partials,
'root': root
}
exec(code + '\nresult = render(context, helpers=helpers, partials=partials, root=root)', ns)
return ns['result']
return _render

View File

@ -0,0 +1 @@
# This exists to create the pybars._templates module for in-memory templates

View File

@ -0,0 +1,24 @@
Copyright (c) 2008-2010
Allen Short
Waldemar Kornewald
Soli Deo Gloria.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

View File

@ -0,0 +1,732 @@
# -*- test-case-name: pymeta.test.test_grammar -*-
"""
The definition of PyMeta's language is itself a PyMeta grammar, but something
has to be able to read that. Most of the code in this module is generated from
that grammar (in future versions, it will hopefully all be generated).
"""
import string
from .runtime import OMetaBase, ParseError, EOFError, expected
class BootOMetaGrammar(OMetaBase):
"""
The bootstrap grammar, generated from L{pymeta.grammar.OMetaGrammar} via
L{pymeta.builder.PythonBuilder}.
"""
globals = globals()
def __init__(self, input):
OMetaBase.__init__(self, input)
self._ruleNames = []
def parseGrammar(self, name, builder, *args):
"""
Entry point for converting a grammar to code (of some variety).
@param name: The name for this grammar.
@param builder: A class that implements the grammar-building interface
(interface to be explicitly defined later)
"""
self.builder = builder(name, self, *args)
res, err = self.apply("grammar")
try:
x = self.input.head()
except EOFError:
pass
else:
raise err
return res
def applicationArgs(self):
args = []
while True:
try:
(arg, endchar), err = self.pythonExpr(" >")
if not arg:
break
args.append(self.builder.expr(arg))
if endchar == '>':
break
except ParseError:
break
if args:
return args
else:
raise ParseError(self.input.position, expected("python expression"))
def ruleValueExpr(self):
(expr, endchar), err = self.pythonExpr(endChars="\r\n)]")
if str(endchar) in ")]":
self.input = self.input.prev()
return self.builder.expr(expr)
def semanticActionExpr(self):
return self.builder.action(self.pythonExpr(')')[0][0])
def semanticPredicateExpr(self):
expr = self.builder.expr(self.pythonExpr(')')[0][0])
return self.builder.pred(expr)
def eatWhitespace(self):
"""
Consume input until a non-whitespace character is reached.
"""
consumingComment = False
e = None
while True:
try:
c, e = self.input.head()
except EOFError:
break
t = self.input.tail()
if c.isspace() or consumingComment:
self.input = t
if c == '\n':
consumingComment = False
elif c == '#':
consumingComment = True
else:
break
return True, e
rule_spaces = eatWhitespace
def rule_number(self):
_locals = {'self': self}
self.locals['number'] = _locals
_G_apply_1, lastError = self._apply(self.rule_spaces, "spaces", [])
self.considerError(lastError)
def _G_or_2():
_G_exactly_1, lastError = self.exactly('-')
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_barenumber, "barenumber", [])
self.considerError(lastError)
_locals['x'] = _G_apply_2
_G_python_3, lastError = eval('self.builder.exactly(-x)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
def _G_or_3():
_G_apply_1, lastError = self._apply(self.rule_barenumber, "barenumber", [])
self.considerError(lastError)
_locals['x'] = _G_apply_1
_G_python_2, lastError = eval('self.builder.exactly(x)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
_G_or_4, lastError = self._or([_G_or_2, _G_or_3])
self.considerError(lastError)
return (_G_or_4, self.currentError)
def rule_barenumber(self):
_locals = {'self': self}
self.locals['barenumber'] = _locals
def _G_or_1():
_G_exactly_1, lastError = self.exactly('0')
self.considerError(lastError)
def _G_or_2():
def _G_or_1():
_G_exactly_1, lastError = self.exactly('x')
self.considerError(lastError)
return (_G_exactly_1, self.currentError)
def _G_or_2():
_G_exactly_1, lastError = self.exactly('X')
self.considerError(lastError)
return (_G_exactly_1, self.currentError)
_G_or_3, lastError = self._or([_G_or_1, _G_or_2])
self.considerError(lastError)
def _G_many_4():
_G_apply_1, lastError = self._apply(self.rule_hexdigit, "hexdigit", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
_G_many_5, lastError = self.many(_G_many_4)
self.considerError(lastError)
_locals['hs'] = _G_many_5
_G_python_6, lastError = eval("int(''.join(hs), 16)", self.globals, _locals), None
self.considerError(lastError)
return (_G_python_6, self.currentError)
def _G_or_3():
def _G_many_1():
_G_apply_1, lastError = self._apply(self.rule_octaldigit, "octaldigit", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
_G_many_2, lastError = self.many(_G_many_1)
self.considerError(lastError)
_locals['ds'] = _G_many_2
_G_python_3, lastError = eval("int('0'+''.join(ds), 8)", self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
_G_or_4, lastError = self._or([_G_or_2, _G_or_3])
self.considerError(lastError)
return (_G_or_4, self.currentError)
def _G_or_2():
def _G_many1_1():
_G_apply_1, lastError = self._apply(self.rule_digit, "digit", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
_G_many1_2, lastError = self.many(_G_many1_1, _G_many1_1())
self.considerError(lastError)
_locals['ds'] = _G_many1_2
_G_python_3, lastError = eval("int(''.join(ds))", self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
_G_or_3, lastError = self._or([_G_or_1, _G_or_2])
self.considerError(lastError)
return (_G_or_3, self.currentError)
def rule_octaldigit(self):
_locals = {'self': self}
self.locals['octaldigit'] = _locals
_G_apply_1, lastError = self._apply(self.rule_anything, "anything", [])
self.considerError(lastError)
_locals['x'] = _G_apply_1
def _G_pred_2():
_G_python_1, lastError = eval('x in string.octdigits', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_1, self.currentError)
_G_pred_3, lastError = self.pred(_G_pred_2)
self.considerError(lastError)
_G_python_4, lastError = eval('x', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_4, self.currentError)
def rule_hexdigit(self):
_locals = {'self': self}
self.locals['hexdigit'] = _locals
_G_apply_1, lastError = self._apply(self.rule_anything, "anything", [])
self.considerError(lastError)
_locals['x'] = _G_apply_1
def _G_pred_2():
_G_python_1, lastError = eval('x in string.hexdigits', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_1, self.currentError)
_G_pred_3, lastError = self.pred(_G_pred_2)
self.considerError(lastError)
_G_python_4, lastError = eval('x', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_4, self.currentError)
def rule_escapedChar(self):
_locals = {'self': self}
self.locals['escapedChar'] = _locals
_G_exactly_1, lastError = self.exactly('\\')
self.considerError(lastError)
def _G_or_2():
_G_exactly_1, lastError = self.exactly('n')
self.considerError(lastError)
_G_python_2, lastError = eval('"\\n"', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_3():
_G_exactly_1, lastError = self.exactly('r')
self.considerError(lastError)
_G_python_2, lastError = eval('"\\r"', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_4():
_G_exactly_1, lastError = self.exactly('t')
self.considerError(lastError)
_G_python_2, lastError = eval('"\\t"', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_5():
_G_exactly_1, lastError = self.exactly('b')
self.considerError(lastError)
_G_python_2, lastError = eval('"\\b"', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_6():
_G_exactly_1, lastError = self.exactly('f')
self.considerError(lastError)
_G_python_2, lastError = eval('"\\f"', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_7():
_G_exactly_1, lastError = self.exactly('"')
self.considerError(lastError)
_G_python_2, lastError = eval('\'"\'', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_8():
_G_exactly_1, lastError = self.exactly("'")
self.considerError(lastError)
_G_python_2, lastError = eval('"\'"', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_9():
_G_exactly_1, lastError = self.exactly('\\')
self.considerError(lastError)
_G_python_2, lastError = eval('"\\\\"', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
_G_or_10, lastError = self._or([_G_or_2, _G_or_3, _G_or_4, _G_or_5, _G_or_6, _G_or_7, _G_or_8, _G_or_9])
self.considerError(lastError)
return (_G_or_10, self.currentError)
def rule_character(self):
_locals = {'self': self}
self.locals['character'] = _locals
_G_python_1, lastError = eval('"\'"', self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
def _G_or_3():
_G_apply_1, lastError = self._apply(self.rule_escapedChar, "escapedChar", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_4():
_G_apply_1, lastError = self._apply(self.rule_anything, "anything", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
_G_or_5, lastError = self._or([_G_or_3, _G_or_4])
self.considerError(lastError)
_locals['c'] = _G_or_5
_G_python_6, lastError = eval('"\'"', self.globals, _locals), None
self.considerError(lastError)
_G_apply_7, lastError = self._apply(self.rule_token, "token", [_G_python_6])
self.considerError(lastError)
_G_python_8, lastError = eval('self.builder.exactly(c)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_8, self.currentError)
def rule_string(self):
_locals = {'self': self}
self.locals['string'] = _locals
_G_python_1, lastError = eval('\'"\'', self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
def _G_many_3():
def _G_or_1():
_G_apply_1, lastError = self._apply(self.rule_escapedChar, "escapedChar", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_2():
def _G_not_1():
_G_exactly_1, lastError = self.exactly('"')
self.considerError(lastError)
return (_G_exactly_1, self.currentError)
_G_not_2, lastError = self._not(_G_not_1)
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_anything, "anything", [])
self.considerError(lastError)
return (_G_apply_3, self.currentError)
_G_or_3, lastError = self._or([_G_or_1, _G_or_2])
self.considerError(lastError)
return (_G_or_3, self.currentError)
_G_many_4, lastError = self.many(_G_many_3)
self.considerError(lastError)
_locals['c'] = _G_many_4
_G_python_5, lastError = eval('\'"\'', self.globals, _locals), None
self.considerError(lastError)
_G_apply_6, lastError = self._apply(self.rule_token, "token", [_G_python_5])
self.considerError(lastError)
_G_python_7, lastError = eval("self.builder.exactly(''.join(c))", self.globals, _locals), None
self.considerError(lastError)
return (_G_python_7, self.currentError)
def rule_name(self):
_locals = {'self': self}
self.locals['name'] = _locals
_G_apply_1, lastError = self._apply(self.rule_letter, "letter", [])
self.considerError(lastError)
_locals['x'] = _G_apply_1
def _G_many_2():
_G_apply_1, lastError = self._apply(self.rule_letterOrDigit, "letterOrDigit", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
_G_many_3, lastError = self.many(_G_many_2)
self.considerError(lastError)
_locals['xs'] = _G_many_3
_G_python_4, lastError = eval('xs.insert(0, x)', self.globals, _locals), None
self.considerError(lastError)
_G_python_5, lastError = eval("''.join(xs)", self.globals, _locals), None
self.considerError(lastError)
return (_G_python_5, self.currentError)
def rule_application(self):
_locals = {'self': self}
self.locals['application'] = _locals
_G_python_1, lastError = eval("'<'", self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_spaces, "spaces", [])
self.considerError(lastError)
_G_apply_4, lastError = self._apply(self.rule_name, "name", [])
self.considerError(lastError)
_locals['name'] = _G_apply_4
def _G_or_5():
_G_exactly_1, lastError = self.exactly(' ')
self.considerError(lastError)
_G_python_2, lastError = eval('self.applicationArgs()', self.globals, _locals), None
self.considerError(lastError)
_locals['args'] = _G_python_2
_G_python_3, lastError = eval('self.builder.apply(name, self.name, *args)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
def _G_or_6():
_G_python_1, lastError = eval("'>'", self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_python_3, lastError = eval('self.builder.apply(name, self.name)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
_G_or_7, lastError = self._or([_G_or_5, _G_or_6])
self.considerError(lastError)
return (_G_or_7, self.currentError)
def rule_expr1(self):
_locals = {'self': self}
self.locals['expr1'] = _locals
def _G_or_1():
_G_apply_1, lastError = self._apply(self.rule_application, "application", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_2():
_G_apply_1, lastError = self._apply(self.rule_ruleValue, "ruleValue", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_3():
_G_apply_1, lastError = self._apply(self.rule_semanticPredicate, "semanticPredicate", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_4():
_G_apply_1, lastError = self._apply(self.rule_semanticAction, "semanticAction", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_5():
_G_apply_1, lastError = self._apply(self.rule_number, "number", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_6():
_G_apply_1, lastError = self._apply(self.rule_character, "character", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_7():
_G_apply_1, lastError = self._apply(self.rule_string, "string", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
def _G_or_8():
_G_python_1, lastError = eval("'('", self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_expr, "expr", [])
self.considerError(lastError)
_locals['e'] = _G_apply_3
_G_python_4, lastError = eval("')'", self.globals, _locals), None
self.considerError(lastError)
_G_apply_5, lastError = self._apply(self.rule_token, "token", [_G_python_4])
self.considerError(lastError)
_G_python_6, lastError = eval('e', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_6, self.currentError)
def _G_or_9():
_G_python_1, lastError = eval("'['", self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_expr, "expr", [])
self.considerError(lastError)
_locals['e'] = _G_apply_3
_G_python_4, lastError = eval("']'", self.globals, _locals), None
self.considerError(lastError)
_G_apply_5, lastError = self._apply(self.rule_token, "token", [_G_python_4])
self.considerError(lastError)
_G_python_6, lastError = eval('self.builder.listpattern(e)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_6, self.currentError)
_G_or_10, lastError = self._or([_G_or_1, _G_or_2, _G_or_3, _G_or_4, _G_or_5, _G_or_6, _G_or_7, _G_or_8, _G_or_9])
self.considerError(lastError)
return (_G_or_10, self.currentError)
def rule_expr2(self):
_locals = {'self': self}
self.locals['expr2'] = _locals
def _G_or_1():
_G_python_1, lastError = eval("'~'", self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
def _G_or_3():
_G_python_1, lastError = eval("'~'", self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_expr2, "expr2", [])
self.considerError(lastError)
_locals['e'] = _G_apply_3
_G_python_4, lastError = eval('self.builder.lookahead(e)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_4, self.currentError)
def _G_or_4():
_G_apply_1, lastError = self._apply(self.rule_expr2, "expr2", [])
self.considerError(lastError)
_locals['e'] = _G_apply_1
_G_python_2, lastError = eval('self.builder._not(e)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
_G_or_5, lastError = self._or([_G_or_3, _G_or_4])
self.considerError(lastError)
return (_G_or_5, self.currentError)
def _G_or_2():
_G_apply_1, lastError = self._apply(self.rule_expr1, "expr1", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
_G_or_3, lastError = self._or([_G_or_1, _G_or_2])
self.considerError(lastError)
return (_G_or_3, self.currentError)
def rule_expr3(self):
_locals = {'self': self}
self.locals['expr3'] = _locals
def _G_or_1():
_G_apply_1, lastError = self._apply(self.rule_expr2, "expr2", [])
self.considerError(lastError)
_locals['e'] = _G_apply_1
def _G_or_2():
_G_exactly_1, lastError = self.exactly('*')
self.considerError(lastError)
_G_python_2, lastError = eval('self.builder.many(e)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_3():
_G_exactly_1, lastError = self.exactly('+')
self.considerError(lastError)
_G_python_2, lastError = eval('self.builder.many1(e)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_4():
_G_exactly_1, lastError = self.exactly('?')
self.considerError(lastError)
_G_python_2, lastError = eval('self.builder.optional(e)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_2, self.currentError)
def _G_or_5():
_G_python_1, lastError = eval('e', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_1, self.currentError)
_G_or_6, lastError = self._or([_G_or_2, _G_or_3, _G_or_4, _G_or_5])
self.considerError(lastError)
_locals['r'] = _G_or_6
def _G_or_7():
_G_exactly_1, lastError = self.exactly(':')
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_name, "name", [])
self.considerError(lastError)
_locals['n'] = _G_apply_2
_G_python_3, lastError = eval('self.builder.bind(r, n)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
def _G_or_8():
_G_python_1, lastError = eval('r', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_1, self.currentError)
_G_or_9, lastError = self._or([_G_or_7, _G_or_8])
self.considerError(lastError)
return (_G_or_9, self.currentError)
def _G_or_2():
_G_python_1, lastError = eval("':'", self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_name, "name", [])
self.considerError(lastError)
_locals['n'] = _G_apply_3
_G_python_4, lastError = eval('self.builder.bind(self.builder.apply("anything", self.name), n)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_4, self.currentError)
_G_or_3, lastError = self._or([_G_or_1, _G_or_2])
self.considerError(lastError)
return (_G_or_3, self.currentError)
def rule_expr4(self):
_locals = {'self': self}
self.locals['expr4'] = _locals
def _G_many_1():
_G_apply_1, lastError = self._apply(self.rule_expr3, "expr3", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
_G_many_2, lastError = self.many(_G_many_1)
self.considerError(lastError)
_locals['es'] = _G_many_2
_G_python_3, lastError = eval('self.builder.sequence(es)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
def rule_expr(self):
_locals = {'self': self}
self.locals['expr'] = _locals
_G_apply_1, lastError = self._apply(self.rule_expr4, "expr4", [])
self.considerError(lastError)
_locals['e'] = _G_apply_1
def _G_many_2():
_G_python_1, lastError = eval("'|'", self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_expr4, "expr4", [])
self.considerError(lastError)
return (_G_apply_3, self.currentError)
_G_many_3, lastError = self.many(_G_many_2)
self.considerError(lastError)
_locals['es'] = _G_many_3
_G_python_4, lastError = eval('es.insert(0, e)', self.globals, _locals), None
self.considerError(lastError)
_G_python_5, lastError = eval('self.builder._or(es)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_5, self.currentError)
def rule_ruleValue(self):
_locals = {'self': self}
self.locals['ruleValue'] = _locals
_G_python_1, lastError = eval('"=>"', self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_python_3, lastError = eval('self.ruleValueExpr()', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
def rule_semanticPredicate(self):
_locals = {'self': self}
self.locals['semanticPredicate'] = _locals
_G_python_1, lastError = eval('"?("', self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_python_3, lastError = eval('self.semanticPredicateExpr()', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
def rule_semanticAction(self):
_locals = {'self': self}
self.locals['semanticAction'] = _locals
_G_python_1, lastError = eval('"!("', self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_python_3, lastError = eval('self.semanticActionExpr()', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
def rule_rulePart(self):
_locals = {'self': self}
self.locals['rulePart'] = _locals
_G_apply_1, lastError = self._apply(self.rule_anything, "anything", [])
self.considerError(lastError)
_locals['requiredName'] = _G_apply_1
_G_apply_2, lastError = self._apply(self.rule_spaces, "spaces", [])
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_name, "name", [])
self.considerError(lastError)
_locals['n'] = _G_apply_3
def _G_pred_4():
_G_python_1, lastError = eval('n == requiredName', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_1, self.currentError)
_G_pred_5, lastError = self.pred(_G_pred_4)
self.considerError(lastError)
_G_python_6, lastError = eval('setattr(self, "name", n)', self.globals, _locals), None
self.considerError(lastError)
_G_apply_7, lastError = self._apply(self.rule_expr4, "expr4", [])
self.considerError(lastError)
_locals['args'] = _G_apply_7
def _G_or_8():
_G_python_1, lastError = eval('"::="', self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_token, "token", [_G_python_1])
self.considerError(lastError)
_G_apply_3, lastError = self._apply(self.rule_expr, "expr", [])
self.considerError(lastError)
_locals['e'] = _G_apply_3
_G_python_4, lastError = eval('self.builder.sequence([args, e])', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_4, self.currentError)
def _G_or_9():
_G_python_1, lastError = eval('args', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_1, self.currentError)
_G_or_10, lastError = self._or([_G_or_8, _G_or_9])
self.considerError(lastError)
return (_G_or_10, self.currentError)
def rule_rule(self):
_locals = {'self': self}
self.locals['rule'] = _locals
_G_apply_1, lastError = self._apply(self.rule_spaces, "spaces", [])
self.considerError(lastError)
def _G_lookahead_2():
_G_apply_1, lastError = self._apply(self.rule_name, "name", [])
self.considerError(lastError)
_locals['n'] = _G_apply_1
return (_locals['n'], self.currentError)
_G_lookahead_3, lastError = self.lookahead(_G_lookahead_2)
self.considerError(lastError)
_G_python_4, lastError = eval('n', self.globals, _locals), None
self.considerError(lastError)
_G_apply_5, lastError = self._apply(self.rule_rulePart, "rulePart", [_G_python_4])
self.considerError(lastError)
_locals['r'] = _G_apply_5
def _G_or_6():
def _G_many1_1():
_G_python_1, lastError = eval('n', self.globals, _locals), None
self.considerError(lastError)
_G_apply_2, lastError = self._apply(self.rule_rulePart, "rulePart", [_G_python_1])
self.considerError(lastError)
return (_G_apply_2, self.currentError)
_G_many1_2, lastError = self.many(_G_many1_1, _G_many1_1())
self.considerError(lastError)
_locals['rs'] = _G_many1_2
_G_python_3, lastError = eval('self.builder.rule(n, self.builder._or([r] + rs))', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_3, self.currentError)
def _G_or_7():
_G_python_1, lastError = eval('self.builder.rule(n, r)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_1, self.currentError)
_G_or_8, lastError = self._or([_G_or_6, _G_or_7])
self.considerError(lastError)
return (_G_or_8, self.currentError)
def rule_grammar(self):
_locals = {'self': self}
self.locals['grammar'] = _locals
def _G_many_1():
_G_apply_1, lastError = self._apply(self.rule_rule, "rule", [])
self.considerError(lastError)
return (_G_apply_1, self.currentError)
_G_many_2, lastError = self.many(_G_many_1)
self.considerError(lastError)
_locals['rs'] = _G_many_2
_G_apply_3, lastError = self._apply(self.rule_spaces, "spaces", [])
self.considerError(lastError)
_G_python_4, lastError = eval('self.builder.makeGrammar(rs)', self.globals, _locals), None
self.considerError(lastError)
return (_G_python_4, self.currentError)

View File

@ -0,0 +1,326 @@
# -*- test-case-name: pymeta.test.test_builder -*-
import linecache, sys
from types import ModuleType as module
import itertools, linecache, sys
class TreeBuilder(object):
"""
Produce an abstract syntax tree of OMeta operations.
"""
def __init__(self, name, grammar=None, *args):
self.name = name
def makeGrammar(self, rules):
return ["Grammar", self.name, rules]
def rule(self, name, expr):
return ["Rule", name, expr]
def apply(self, ruleName, codeName, *exprs):
return ["Apply", ruleName, codeName, exprs]
def exactly(self, expr):
return ["Exactly", expr]
def many(self, expr):
return ["Many", expr]
def many1(self, expr):
return ["Many1", expr]
def optional(self, expr):
return ["Optional", expr]
def _or(self, exprs):
return ["Or", exprs]
def _not(self, expr):
return ["Not", expr]
def lookahead(self, expr):
return ["Lookahead", expr]
def sequence(self, exprs):
return ["And", exprs]
def bind(self, expr, name):
return ["Bind", name, expr]
def pred(self, expr):
return ["Predicate", expr]
def action(self, expr):
return ["Action", expr]
def expr(self, expr):
return ["Python", expr]
def listpattern(self, exprs):
return ["List", exprs]
class PythonWriter(object):
"""
Converts an OMeta syntax tree into Python source.
"""
def __init__(self, tree):
self.tree = tree
self.lines = []
self.gensymCounter = 0
def _generate(self, retrn=False):
result = self._generateNode(self.tree)
if retrn:
self.lines.append("return (%s, self.currentError)" % (result,))
elif result:
self.lines.append(result)
return self.lines
def output(self):
return '\n'.join(self._generate())
def _generateNode(self, node):
name = node[0]
args = node[1:]
return getattr(self, "generate_"+name)(*args)
def _gensym(self, name):
"""
Produce a unique name for a variable in generated code.
"""
self.gensymCounter += 1
return "_G_%s_%s" % (name, self.gensymCounter)
def _newThunkFor(self, name, expr):
"""
Define a new function of no arguments.
@param name: The name of the rule generating this thunk.
@param expr: A list of lines of Python code.
"""
subwriter = self.__class__(expr)
flines = subwriter._generate(retrn=True)
fname = self._gensym(name)
self._writeFunction(fname, (), flines)
return fname
def _expr(self, typ, e):
"""
Generate the code needed to execute the expression, and return the
variable name bound to its value.
"""
name = self._gensym(typ)
self.lines.append("%s, lastError = %s" % (name, e))
self.lines.append("self.considerError(lastError)")
return name
def _writeFunction(self, fname, arglist, flines):
"""
Generate a function.
@param head: The initial line defining the function.
@param body: A list of lines for the function body.
"""
self.lines.append("def %s(%s):" % (fname, ", ".join(arglist)))
for line in flines:
self.lines.append((" " * 4) + line)
return fname
def compilePythonExpr(self, expr):
"""
Generate code for running embedded Python expressions.
"""
return self._expr('python', 'eval(%r, self.globals, _locals), None' %(expr,))
def generate_Apply(self, ruleName, codeName, rawArgs):
"""
Create a call to self.apply(ruleName, *args).
"""
args = [self._generateNode(x) for x in rawArgs]
if ruleName == 'super':
return self._expr('apply', 'self.superApply("%s", %s)' % (codeName,
', '.join(args)))
return self._expr('apply', 'self._apply(self.rule_%s, "%s", [%s])' % (ruleName,
ruleName,
', '.join(args)))
def generate_Exactly(self, literal):
"""
Create a call to self.exactly(expr).
"""
return self._expr('exactly', 'self.exactly(%r)' % (literal,))
def generate_Many(self, expr):
"""
Create a call to self.many(lambda: expr).
"""
fname = self._newThunkFor("many", expr)
return self._expr('many', 'self.many(%s)' % (fname,))
def generate_Many1(self, expr):
"""
Create a call to self.many(lambda: expr).
"""
fname = self._newThunkFor("many1", expr)
return self._expr('many1', 'self.many(%s, %s())' % (fname, fname))
def generate_Optional(self, expr):
"""
Try to parse an expr and continue if it fails.
"""
realf = self._newThunkFor("optional", expr)
passf = self._gensym("optional")
self._writeFunction(passf, (), ["return (None, self.input.nullError())"])
return self._expr('or', 'self._or([%s])' % (', '.join([realf, passf])))
def generate_Or(self, exprs):
"""
Create a call to
self._or([lambda: expr1, lambda: expr2, ... , lambda: exprN]).
"""
if len(exprs) > 1:
fnames = [self._newThunkFor("or", expr) for expr in exprs]
return self._expr('or', 'self._or([%s])' % (', '.join(fnames)))
else:
return self._generateNode(exprs[0])
def generate_Not(self, expr):
"""
Create a call to self._not(lambda: expr).
"""
fname = self._newThunkFor("not", expr)
return self._expr("not", "self._not(%s)" % (fname,))
def generate_Lookahead(self, expr):
"""
Create a call to self.lookahead(lambda: expr).
"""
fname = self._newThunkFor("lookahead", expr)
return self._expr("lookahead", "self.lookahead(%s)" %(fname,))
def generate_And(self, exprs):
"""
Generate code for each statement in order.
"""
v = None
for ex in exprs:
v = self._generateNode(ex)
return v
def generate_Bind(self, name, expr):
"""
Bind the value of 'expr' to a name in the _locals dict.
"""
v = self._generateNode(expr)
ref = "_locals['%s']" % (name,)
self.lines.append("%s = %s" %(ref, v))
return ref
def generate_Predicate(self, expr):
"""
Generate a call to self.pred(lambda: expr).
"""
fname = self._newThunkFor("pred", expr)
return self._expr("pred", "self.pred(%s)" %(fname,))
def generate_Action(self, expr):
"""
Generate this embedded Python expression on its own line.
"""
return self.compilePythonExpr(expr)
def generate_Python(self, expr):
"""
Generate this embedded Python expression on its own line.
"""
return self.compilePythonExpr(expr)
def generate_List(self, expr):
"""
Generate a call to self.listpattern(lambda: expr).
"""
fname = self._newThunkFor("listpattern", expr)
return self._expr("listpattern", "self.listpattern(%s)" %(fname,))
def generate_Rule(self, name, expr):
rulelines = ["_locals = {'self': self}",
"self.locals[%r] = _locals" % (name,)]
subwriter = self.__class__(expr)
flines = subwriter._generate(retrn=True)
rulelines.extend(flines)
self._writeFunction("rule_" + name, ("self",), rulelines)
def generate_Grammar(self, name, rules):
self.lines.append("class %s(GrammarBase):" % (name,))
for rule in rules:
self._generateNode(rule)
self.lines.extend(['', ''])
self.lines[1:] = [line and (' ' * 4 + line) for line in self.lines[1:]]
del self.lines[-2:]
def writePython(tree):
pw = PythonWriter(tree)
return pw.output()
class GeneratedCodeLoader(object):
"""
Object for use as a module's __loader__, to display generated
source.
"""
def __init__(self, source):
self.source = source
def get_source(self, name):
return self.source
def moduleFromGrammar(tree, className, superclass, globalsDict):
source = writePython(tree)
modname = "pymeta_grammar__" + className
filename = "/pymeta_generated_code/" + modname + ".py"
mod = module(modname)
mod.__dict__.update(globalsDict)
mod.__name__ = modname
mod.__dict__[superclass.__name__] = superclass
mod.__dict__["GrammarBase"] = superclass
mod.__loader__ = GeneratedCodeLoader(source)
code = compile(source, filename, "exec")
eval(code, mod.__dict__)
fullGlobals = dict(getattr(mod.__dict__[className], "globals", None) or {})
fullGlobals.update(globalsDict)
mod.__dict__[className].globals = fullGlobals
sys.modules[modname] = mod
linecache.getlines(filename, mod.__dict__)
return mod.__dict__[className]

View File

@ -0,0 +1,321 @@
# -*- test-case-name: pymeta.test.test_pymeta -*-
"""
Public interface to OMeta, as well as the grammars used to compile grammar
definitions.
"""
import string
from .builder import TreeBuilder, moduleFromGrammar
from .boot import BootOMetaGrammar
from .runtime import OMetaBase, ParseError, EOFError
class OMeta(OMetaBase):
"""
Base class for grammar definitions.
"""
metagrammarClass = BootOMetaGrammar
def makeGrammar(cls, grammar, globals, name="Grammar"):
"""
Define a new subclass with the rules in the given grammar.
@param grammar: A string containing a PyMeta grammar.
@param globals: A dict of names that should be accessible by this
grammar.
@param name: The name of the class to be generated.
"""
g = cls.metagrammarClass(grammar)
tree = g.parseGrammar(name, TreeBuilder)
return moduleFromGrammar(tree, name, cls, globals)
makeGrammar = classmethod(makeGrammar)
ometaGrammar = r"""
number ::= <spaces> ('-' <barenumber>:x => -x
|<barenumber>:x => x)
barenumber ::= ('0' (('x'|'X') <hexdigit>*:hs => int(''.join(hs), 16)
|<octaldigit>*:ds => int('0'+''.join(ds), 8))
|<digit>+:ds => int(''.join(ds)))
octaldigit ::= :x ?(x in string.octdigits) => x
hexdigit ::= :x ?(x in string.hexdigits) => x
escapedChar ::= '\\' ('n' => "\n"
|'r' => "\r"
|'t' => "\t"
|'b' => "\b"
|'f' => "\f"
|'"' => '"'
|'\'' => "'"
|'\\' => '\\')
character ::= <token "'"> (<escapedChar> | <anything>):c <token "'"> => c
bareString ::= <token '"'> (<escapedChar> | ~('"') <anything>)*:c <token '"'> => ''.join(c)
string ::= <bareString>:s => self.builder.exactly(s)
name ::= <letter>:x <letterOrDigit>*:xs !(xs.insert(0, x)) => ''.join(xs)
application ::= (<token '<'> <spaces> <name>:name
(' ' !(self.applicationArgs(finalChar='>')):args
=> self.builder.apply(name, self.name, *args)
|<token '>'>
=> self.builder.apply(name, self.name)))
expr1 ::= (<application>
|<ruleValue>
|<semanticPredicate>
|<semanticAction>
|(<number> | <character>):lit => self.builder.exactly(lit)
|<string>
|<token '('> <expr>:e <token ')'> => e
|<token '['> <expr>:e <token ']'> => self.builder.listpattern(e))
expr2 ::= (<token '~'> (<token '~'> <expr2>:e => self.builder.lookahead(e)
|<expr2>:e => self.builder._not(e))
|<expr1>)
expr3 ::= ((<expr2>:e ('*' => self.builder.many(e)
|'+' => self.builder.many1(e)
|'?' => self.builder.optional(e)
| => e)):r
(':' <name>:n => self.builder.bind(r, n)
| => r)
|<token ':'> <name>:n
=> self.builder.bind(self.builder.apply("anything", self.name), n))
expr4 ::= <expr3>*:es => self.builder.sequence(es)
expr ::= <expr4>:e (<token '|'> <expr4>)*:es !(es.insert(0, e))
=> self.builder._or(es)
ruleValue ::= <token "=>"> => self.ruleValueExpr(False)
semanticPredicate ::= <token "?("> => self.semanticPredicateExpr()
semanticAction ::= <token "!("> => self.semanticActionExpr()
rulePart :requiredName ::= (<spaces> <name>:n ?(n == requiredName)
!(setattr(self, "name", n))
<expr4>:args
(<token "::="> <expr>:e
=> self.builder.sequence([args, e])
| => args))
rule ::= (<spaces> ~~(<name>:n) <rulePart n>:r
(<rulePart n>+:rs => self.builder.rule(n, self.builder._or([r] + rs))
| => self.builder.rule(n, r)))
grammar ::= <rule>*:rs <spaces> => self.builder.makeGrammar(rs)
"""
#don't be confused, emacs
v2Grammar = r"""
hspace ::= (' ' | '\t')
vspace ::= (<token "\r\n"> | '\r' | '\n')
emptyline ::= <hspace>* <vspace>
indentation ::= <emptyline>* <hspace>+
noindentation ::= <emptyline>* ~~~<hspace>
number ::= <spaces> ('-' <barenumber>:x => self.builder.exactly(-x)
|<barenumber>:x => self.builder.exactly(x))
barenumber ::= '0' (('x'|'X') <hexdigit>*:hs => int(''.join(hs), 16)
|<octaldigit>*:ds => int('0'+''.join(ds), 8))
|<digit>+:ds => int(''.join(ds))
octaldigit ::= :x ?(x in string.octdigits) => x
hexdigit ::= :x ?(x in string.hexdigits) => x
escapedChar ::= '\\' ('n' => "\n"
|'r' => "\r"
|'t' => "\t"
|'b' => "\b"
|'f' => "\f"
|'"' => '"'
|'\'' => "'"
|'\\' => "\\")
character ::= <token "'"> (<escapedChar> | <anything>):c <token "'"> => self.builder.exactly(c)
string ::= <token '"'> (<escapedChar> | ~('"') <anything>)*:c <token '"'> => self.builder.exactly(''.join(c))
name ::= <letter>:x <letterOrDigit>*:xs !(xs.insert(0, x)) => ''.join(xs)
application ::= <indentation>? <name>:name
('(' !(self.applicationArgs(finalChar=')')):args
=> self.builder.apply(name, self.name, *args)
| => self.builder.apply(name, self.name))
expr1 ::= <application>
|<ruleValue>
|<semanticPredicate>
|<semanticAction>
|<number>
|<character>
|<string>
|<token '('> <expr>:e <token ')'> => e
|<token '['> <expr>:e <token ']'> => self.builder.listpattern(e)
expr2 ::= <token '~'> (<token '~'> <expr2>:e => self.builder.lookahead(e)
|<expr2>:e => self.builder._not(e))
|<expr1>
expr3 ::= (<expr2>:e ('*' => self.builder.many(e)
|'+' => self.builder.many1(e)
|'?' => self.builder.optional(e)
| => e)):r
(':' <name>:n => self.builder.bind(r, n)
| => r)
|<token ':'> <name>:n
=> self.builder.bind(self.builder.apply("anything", self.name), n)
expr4 ::= <expr3>*:es => self.builder.sequence(es)
expr ::= <expr4>:e (<token '|'> <expr4>)*:es !(es.insert(0, e))
=> self.builder._or(es)
ruleValue ::= <token "->"> => self.ruleValueExpr(True)
semanticPredicate ::= <token "?("> => self.semanticPredicateExpr()
semanticAction ::= <token "!("> => self.semanticActionExpr()
rulePart :requiredName ::= <noindentation> <name>:n ?(n == requiredName)
!(setattr(self, "name", n))
<expr4>:args
(<token "="> <expr>:e
=> self.builder.sequence([args, e])
| => args)
rule ::= <noindentation> ~~(<name>:n) <rulePart n>:r
(<rulePart n>+:rs => self.builder.rule(n, self.builder._or([r] + rs))
| => self.builder.rule(n, r))
grammar ::= <rule>*:rs <spaces> => self.builder.makeGrammar(rs)
"""
class OMetaGrammarMixin(object):
"""
Helpers for the base grammar for parsing grammar definitions.
"""
def parseGrammar(self, name, builder, *args):
"""
Entry point for converting a grammar to code (of some variety).
@param name: The name for this grammar.
@param builder: A class that implements the grammar-building interface
(interface to be explicitly defined later)
"""
self.builder = builder(name, self, *args)
res, err = self.apply("grammar")
try:
x = self.input.head()
except EOFError:
pass
else:
raise err
return res
def applicationArgs(self, finalChar):
"""
Collect rule arguments, a list of Python expressions separated by
spaces.
"""
args = []
while True:
try:
(arg, endchar), err = self.pythonExpr(" " + finalChar)
if not arg:
break
args.append(self.builder.expr(arg))
if endchar == finalChar:
break
except ParseError:
break
if args:
return args
else:
raise ParseError()
def ruleValueExpr(self, singleLine):
"""
Find and generate code for a Python expression terminated by a close
paren/brace or end of line.
"""
(expr, endchar), err = self.pythonExpr(endChars="\r\n)]")
if str(endchar) in ")]" or (singleLine and endchar):
self.input = self.input.prev()
return self.builder.expr(expr)
def semanticActionExpr(self):
"""
Find and generate code for a Python expression terminated by a
close-paren, whose return value is ignored.
"""
return self.builder.action(self.pythonExpr(')')[0][0])
def semanticPredicateExpr(self):
"""
Find and generate code for a Python expression terminated by a
close-paren, whose return value determines the success of the pattern
it's in.
"""
expr = self.builder.expr(self.pythonExpr(')')[0][0])
return self.builder.pred(expr)
def eatWhitespace(self):
"""
Consume input until a non-whitespace character is reached.
"""
consumingComment = False
e = None
while True:
try:
c, e = self.input.head()
except EOFError:
break
t = self.input.tail()
if c.isspace() or consumingComment:
self.input = t
if c == '\n':
consumingComment = False
elif c == '#':
consumingComment = True
else:
break
return True, e
rule_spaces = eatWhitespace
class OMetaGrammar(OMetaGrammarMixin, OMeta.makeGrammar(ometaGrammar, globals())):
pass
OMeta.metagrammarClass = OMetaGrammar
class OMeta2Grammar(OMetaGrammarMixin, OMeta.makeGrammar(v2Grammar, globals())):
pass
nullOptimizationGrammar = """
opt ::= ( ["Apply" :ruleName :codeName [<anything>*:exprs]] => self.builder.apply(ruleName, codeName, *exprs)
| ["Exactly" :expr] => self.builder.exactly(expr)
| ["Many" <opt>:expr] => self.builder.many(expr)
| ["Many1" <opt>:expr] => self.builder.many1(expr)
| ["Optional" <opt>:expr] => self.builder.optional(expr)
| ["Or" [<opt>*:exprs]] => self.builder._or(exprs)
| ["And" [<opt>*:exprs]] => self.builder.sequence(exprs)
| ["Not" <opt>:expr] => self.builder._not(expr)
| ["Lookahead" <opt>:expr] => self.builder.lookahead(expr)
| ["Bind" :name <opt>:expr] => self.builder.bind(expr, name)
| ["Predicate" <opt>:expr] => self.builder.pred(expr)
| ["Action" :code] => self.builder.action(code)
| ["Python" :code] => self.builder.expr(code)
| ["List" <opt>:exprs] => self.builder.listpattern(exprs)
)
grammar ::= ["Grammar" :name [<rulePair>*:rs]] => self.builder.makeGrammar(rs)
rulePair ::= ["Rule" :name <opt>:rule] => self.builder.rule(name, rule)
"""
NullOptimizer = OMeta.makeGrammar(nullOptimizationGrammar, {}, name="NullOptimizer")

View File

@ -0,0 +1,612 @@
# -*- test-case-name: pymeta.test.test_runtime -*-
"""
Code needed to run a grammar after it has been compiled.
"""
import operator
class ParseError(Exception):
"""
?Redo from start
"""
@property
def position(self):
return self.args[0]
@property
def error(self):
return self.args[1]
def __init__(self, *a):
Exception.__init__(self, *a)
if len(a) > 2:
self.message = a[2]
def __getitem__(self, item):
return self.args[item]
def __eq__(self, other):
if other.__class__ == self.__class__:
return (self.position, self.error) == (other.position, other.error)
def formatReason(self):
if len(self.error) == 1:
if self.error[0][2] == None:
return 'expected a ' + self.error[0][1]
else:
return 'expected the %s %s' % (self.error[0][1], self.error[0][2])
else:
bits = []
for s in self.error:
if s[2] is None:
desc = "a " + s[1]
else:
desc = repr(s[2])
if s[1] is not None:
desc = "%s %s" % (s[1], desc)
bits.append(desc)
return "expected one of %s, or %s" % (', '.join(bits[:-1]), bits[-1])
def formatError(self, input):
"""
Return a pretty string containing error info about string parsing failure.
"""
lines = input.split('\n')
counter = 0
lineNo = 1
columnNo = 0
for line in lines:
newCounter = counter + len(line)
if newCounter > self.position:
columnNo = self.position - counter
break
else:
counter += len(line) + 1
lineNo += 1
reason = self.formatReason()
return ('\n' + line + '\n' + (' ' * columnNo + '^') +
"\nParse error at line %s, column %s: %s\n" % (lineNo,
columnNo,
reason))
class EOFError(ParseError):
def __init__(self, position):
ParseError.__init__(self, position, eof())
def expected(typ, val=None):
"""
Return an indication of expected input and the position where it was
expected and not encountered.
"""
return [("expected", typ, val)]
def eof():
"""
Return an indication that the end of the input was reached.
"""
return [("message", "end of input")]
def joinErrors(errors):
"""
Return the error from the branch that matched the most of the input.
"""
def get_key(item):
val = item[0]
if val == None:
val = -1000000000
return val
errors.sort(reverse=True, key=get_key)
results = set()
pos = errors[0][0]
for err in errors:
if pos == err[0]:
e = err[1]
if e is not None:
for item in e:
results.add(item)
else:
break
return [pos, list(results)]
class character(str):
"""
Type to allow distinguishing characters from strings.
"""
def __iter__(self):
"""
Prevent string patterns and list patterns from matching single
characters.
"""
raise TypeError("Characters are not iterable")
try:
_has_unicode = True
class unicodeCharacter(unicode):
"""
Type to distinguish characters from Unicode strings.
"""
def __iter__(self):
"""
Prevent string patterns and list patterns from matching single
characters.
"""
raise TypeError("Characters are not iterable")
except NameError:
_has_unicode = False
class InputStream(object):
"""
The basic input mechanism used by OMeta grammars.
"""
def fromIterable(cls, iterable):
"""
@param iterable: Any iterable Python object.
"""
if isinstance(iterable, str):
data = [character(c) for c in iterable]
elif _has_unicode and isinstance(iterable, unicode):
data = [unicodeCharacter(c) for c in iterable]
else:
data = list(iterable)
return cls(data, 0)
fromIterable = classmethod(fromIterable)
def __init__(self, data, position):
self.data = data
self.position = position
self.memo = {}
self.tl = None
def head(self):
if self.position >= len(self.data):
raise EOFError(self.position)
return self.data[self.position], [self.position, None]
def nullError(self):
return [self.position, None]
def tail(self):
if self.tl is None:
self.tl = InputStream(self.data, self.position+1)
return self.tl
def prev(self):
return InputStream(self.data, self.position-1)
def getMemo(self, name):
"""
Returns the memo record for the named rule.
@param name: A rule name.
"""
return self.memo.get(name, None)
def setMemo(self, name, rec):
"""
Store a memo record for the given value and position for the given
rule.
@param name: A rule name.
@param rec: A memo record.
"""
self.memo[name] = rec
return rec
class ArgInput(object):
def __init__(self, arg, parent):
self.arg = arg
self.parent = parent
self.memo = {}
self.err = parent.nullError()
def head(self):
try:
x = self.arg
except:
import pdb; pdb. set_trace()
return self.arg, self.err
def tail(self):
return self.parent
def nullError(self):
return self.parent.nullError()
def getMemo(self, name):
"""
Returns the memo record for the named rule.
@param name: A rule name.
"""
return self.memo.get(name, None)
def setMemo(self, name, rec):
"""
Store a memo record for the given value and position for the given
rule.
@param name: A rule name.
@param rec: A memo record.
"""
self.memo[name] = rec
return rec
class LeftRecursion(object):
"""
Marker for left recursion in a grammar rule.
"""
detected = False
class OMetaBase(object):
"""
Base class providing implementations of the fundamental OMeta
operations. Built-in rules are defined here.
"""
globals = None
def __init__(self, string, globals=None):
"""
@param string: The string to be parsed.
@param globals: A dictionary of names to objects, for use in evaluating
embedded Python expressions.
"""
self.input = InputStream.fromIterable(string)
self.locals = {}
if self.globals is None:
if globals is None:
self.globals = {}
else:
self.globals = globals
self.currentError = self.input.nullError()
def considerError(self, error):
if error and error[0] > self.currentError[0]:
self.currentError = error
def superApply(self, ruleName, *args):
"""
Apply the named rule as defined on this object's superclass.
@param ruleName: A rule name.
"""
r = getattr(super(self.__class__, self), "rule_"+ruleName, None)
if r is not None:
self.input.setMemo(ruleName, None)
return self._apply(r, ruleName, args)
else:
raise NameError("No rule named '%s'" %(ruleName,))
def apply(self, ruleName, *args):
"""
Apply the named rule, optionally with some arguments.
@param ruleName: A rule name.
"""
r = getattr(self, "rule_"+ruleName, None)
if r is not None:
val, err = self._apply(r, ruleName, args)
return val, ParseError(*err)
else:
raise NameError("No rule named '%s'" %(ruleName,))
def _apply(self, rule, ruleName, args):
"""
Apply a rule method to some args.
@param rule: A method of this object.
@param ruleName: The name of the rule invoked.
@param args: A sequence of arguments to it.
"""
if args:
if rule.__code__.co_argcount - 1 != len(args):
for arg in args[::-1]:
self.input = ArgInput(arg, self.input)
return rule()
else:
return rule(*args)
memoRec = self.input.getMemo(ruleName)
if memoRec is None:
oldPosition = self.input
lr = LeftRecursion()
memoRec = self.input.setMemo(ruleName, lr)
#print "Calling", rule
try:
memoRec = self.input.setMemo(ruleName,
[rule(), self.input])
except ParseError:
#print "Failed", rule
raise
#print "Success", rule
if lr.detected:
sentinel = self.input
while True:
try:
self.input = oldPosition
ans = rule()
if (self.input == sentinel):
break
memoRec = oldPosition.setMemo(ruleName,
[ans, self.input])
except ParseError:
break
self.input = oldPosition
elif isinstance(memoRec, LeftRecursion):
memoRec.detected = True
raise ParseError(None, None)
self.input = memoRec[1]
return memoRec[0]
def rule_anything(self):
"""
Match a single item from the input of any kind.
"""
h, p = self.input.head()
self.input = self.input.tail()
return h, p
def exactly(self, wanted):
"""
Match a single item from the input equal to the given specimen.
@param wanted: What to match.
"""
i = self.input
val, p = self.input.head()
self.input = self.input.tail()
if wanted == val:
return val, p
else:
self.input = i
raise ParseError(p[0], expected(None, wanted))
rule_exactly = exactly
def many(self, fn, *initial):
"""
Call C{fn} until it fails to match the input. Collect the resulting
values into a list.
@param fn: A callable of no arguments.
@param initial: Initial values to populate the returned list with.
"""
ans = []
e = None
for x, e in initial:
ans.append(x)
while True:
try:
m = self.input
v, _ = fn()
ans.append(v)
except ParseError:
self.input = m
break
return ans, e
def _or(self, fns):
"""
Call each of a list of functions in sequence until one succeeds,
rewinding the input between each.
@param fns: A list of no-argument callables.
"""
errors = []
for f in fns:
try:
m = self.input
ret, err = f()
errors.append(err)
return ret, joinErrors(errors)
except ParseError as e:
errors.append(e)
self.input = m
raise ParseError(*joinErrors(errors))
def _not(self, fn):
"""
Call the given function. Raise ParseError iff it does not.
@param fn: A callable of no arguments.
"""
m = self.input
try:
fn()
except ParseError as e:
self.input = m
return True, self.input.nullError()
else:
raise ParseError(*self.input.nullError())
def eatWhitespace(self):
"""
Consume input until a non-whitespace character is reached.
"""
e = None
while True:
try:
c, e = self.input.head()
except EOFError:
break
t = self.input.tail()
if c.isspace():
self.input = t
else:
break
return True, e
rule_spaces = eatWhitespace
def pred(self, expr):
"""
Call the given function, raising ParseError if it returns false.
@param expr: A callable of no arguments.
"""
val, e = expr()
if not val:
raise ParseError(*e)
else:
return True, e
def listpattern(self, expr):
"""
Call the given function, treating the next object on the stack as an
iterable to be used for input.
@param expr: A callable of no arguments.
"""
v, e = self.rule_anything()
oldInput = self.input
try:
self.input = InputStream.fromIterable(v)
except TypeError:
e = self.input.nullError()
e[1] = expected("an iterable")
raise ParseError(*e)
expr()
self.end()
self.input = oldInput
return v, e
def end(self):
"""
Match the end of the stream.
"""
return self._not(self.rule_anything)
rule_end = end
def lookahead(self, f):
"""
Execute the given callable, rewinding the stream no matter whether it
returns successfully or not.
@param f: A callable of no arguments.
"""
try:
m = self.input
x = f()
return x
finally:
self.input = m
def token(self, tok):
"""
Match and return the given string, consuming any preceding whitespace.
"""
m = self.input
try:
self.eatWhitespace()
for c in tok:
v, e = self.exactly(c)
return tok, e
except ParseError as e:
self.input = m
raise ParseError(e[0], expected("token", tok))
rule_token = token
def letter(self):
"""
Match a single letter.
"""
x, e = self.rule_anything()
if x.isalpha():
return x, e
else:
e[1] = expected("letter")
raise ParseError(*e)
rule_letter = letter
def letterOrDigit(self):
"""
Match a single alphanumeric character.
"""
x, e = self.rule_anything()
if x.isalnum() or x == '_':
return x, e
else:
e[1] = expected("letter or digit")
raise ParseError(*e)
rule_letterOrDigit = letterOrDigit
def digit(self):
"""
Match a single digit.
"""
x, e = self.rule_anything()
if x.isdigit():
return x, e
else:
e[1] = expected("digit")
raise ParseError(*e)
rule_digit = digit
def pythonExpr(self, endChars="\r\n"):
"""
Extract a Python expression from the input and return it.
@arg endChars: A set of characters delimiting the end of the expression.
"""
delimiters = { "(": ")", "[": "]", "{": "}"}
stack = []
expr = []
lastc = None
endchar = None
while True:
try:
c, e = self.rule_anything()
except ParseError as e:
endchar = None
break
if c in endChars and len(stack) == 0:
endchar = c
break
else:
expr.append(c)
if c in delimiters:
stack.append(delimiters[c])
elif len(stack) > 0 and c == stack[-1]:
stack.pop()
elif c in list(delimiters.values()):
raise ParseError(self.input.position, expected("Python expression"))
elif c in "\"'":
while True:
strc, stre = self.rule_anything()
expr.append(strc)
slashcount = 0
while strc == '\\':
strc, stre = self.rule_anything()
expr.append(strc)
slashcount += 1
if strc == c and slashcount % 2 == 0:
break
if len(stack) > 0:
raise ParseError(self.input.position, expected("Python expression"))
return (''.join(expr).strip(), endchar), e