View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -0,0 +1,213 @@
"""A more or less complete user-defined wrapper around dictionary objects."""
class UserDict:
def __init__(*args, **kwargs):
if not args:
raise TypeError("descriptor '__init__' of 'UserDict' object "
"needs an argument")
self = args[0]
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
if args:
dict = args[0]
elif 'dict' in kwargs:
dict = kwargs.pop('dict')
import warnings
warnings.warn("Passing 'dict' as keyword argument is "
"deprecated", PendingDeprecationWarning,
stacklevel=2)
else:
dict = None
self.data = {}
if dict is not None:
self.update(dict)
if len(kwargs):
self.update(kwargs)
def __repr__(self): return repr(self.data)
def __cmp__(self, dict):
if isinstance(dict, UserDict):
return cmp(self.data, dict.data)
else:
return cmp(self.data, dict)
__hash__ = None # Avoid Py3k warning
def __len__(self): return len(self.data)
def __getitem__(self, key):
if key in self.data:
return self.data[key]
if hasattr(self.__class__, "__missing__"):
return self.__class__.__missing__(self, key)
raise KeyError(key)
def __setitem__(self, key, item): self.data[key] = item
def __delitem__(self, key): del self.data[key]
def clear(self): self.data.clear()
def copy(self):
if self.__class__ is UserDict:
return UserDict(self.data.copy())
import copy
data = self.data
try:
self.data = {}
c = copy.copy(self)
finally:
self.data = data
c.update(self)
return c
def keys(self): return self.data.keys()
def items(self): return self.data.items()
def iteritems(self): return self.data.iteritems()
def iterkeys(self): return self.data.iterkeys()
def itervalues(self): return self.data.itervalues()
def values(self): return self.data.values()
def has_key(self, key): return key in self.data
def update(*args, **kwargs):
if not args:
raise TypeError("descriptor 'update' of 'UserDict' object "
"needs an argument")
self = args[0]
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
if args:
dict = args[0]
elif 'dict' in kwargs:
dict = kwargs.pop('dict')
import warnings
warnings.warn("Passing 'dict' as keyword argument is deprecated",
PendingDeprecationWarning, stacklevel=2)
else:
dict = None
if dict is None:
pass
elif isinstance(dict, UserDict):
self.data.update(dict.data)
elif isinstance(dict, type({})) or not hasattr(dict, 'items'):
self.data.update(dict)
else:
for k, v in dict.items():
self[k] = v
if len(kwargs):
self.data.update(kwargs)
def get(self, key, failobj=None):
if key not in self:
return failobj
return self[key]
def setdefault(self, key, failobj=None):
if key not in self:
self[key] = failobj
return self[key]
def pop(self, key, *args):
return self.data.pop(key, *args)
def popitem(self):
return self.data.popitem()
def __contains__(self, key):
return key in self.data
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
class IterableUserDict(UserDict):
def __iter__(self):
return iter(self.data)
import _abcoll
_abcoll.MutableMapping.register(IterableUserDict)
class DictMixin:
# Mixin defining all dictionary methods for classes that already have
# a minimum dictionary interface including getitem, setitem, delitem,
# and keys. Without knowledge of the subclass constructor, the mixin
# does not define __init__() or copy(). In addition to the four base
# methods, progressively more efficiency comes with defining
# __contains__(), __iter__(), and iteritems().
# second level definitions support higher levels
def __iter__(self):
for k in self.keys():
yield k
def has_key(self, key):
try:
self[key]
except KeyError:
return False
return True
def __contains__(self, key):
return self.has_key(key)
# third level takes advantage of second level definitions
def iteritems(self):
for k in self:
yield (k, self[k])
def iterkeys(self):
return self.__iter__()
# fourth level uses definitions from lower levels
def itervalues(self):
for _, v in self.iteritems():
yield v
def values(self):
return [v for _, v in self.iteritems()]
def items(self):
return list(self.iteritems())
def clear(self):
for key in self.keys():
del self[key]
def setdefault(self, key, default=None):
try:
return self[key]
except KeyError:
self[key] = default
return default
def pop(self, key, *args):
if len(args) > 1:
raise TypeError, "pop expected at most 2 arguments, got "\
+ repr(1 + len(args))
try:
value = self[key]
except KeyError:
if args:
return args[0]
raise
del self[key]
return value
def popitem(self):
try:
k, v = self.iteritems().next()
except StopIteration:
raise KeyError, 'container is empty'
del self[k]
return (k, v)
def update(self, other=None, **kwargs):
# Make progressively weaker assumptions about "other"
if other is None:
pass
elif hasattr(other, 'iteritems'): # iteritems saves memory and lookups
for k, v in other.iteritems():
self[k] = v
elif hasattr(other, 'keys'):
for k in other.keys():
self[k] = other[k]
else:
for k, v in other:
self[k] = v
if kwargs:
self.update(kwargs)
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __repr__(self):
return repr(dict(self.iteritems()))
def __cmp__(self, other):
if other is None:
return 1
if isinstance(other, DictMixin):
other = dict(other.iteritems())
return cmp(dict(self.iteritems()), other)
def __len__(self):
return len(self.keys())
View
@@ -0,0 +1,128 @@
"""Record of phased-in incompatible language changes.
Each line is of the form:
FeatureName = "_Feature(" OptionalRelease "," MandatoryRelease ","
CompilerFlag ")"
where, normally, OptionalRelease < MandatoryRelease, and both are 5-tuples
of the same form as sys.version_info:
(PY_MAJOR_VERSION, # the 2 in 2.1.0a3; an int
PY_MINOR_VERSION, # the 1; an int
PY_MICRO_VERSION, # the 0; an int
PY_RELEASE_LEVEL, # "alpha", "beta", "candidate" or "final"; string
PY_RELEASE_SERIAL # the 3; an int
)
OptionalRelease records the first release in which
from __future__ import FeatureName
was accepted.
In the case of MandatoryReleases that have not yet occurred,
MandatoryRelease predicts the release in which the feature will become part
of the language.
Else MandatoryRelease records when the feature became part of the language;
in releases at or after that, modules no longer need
from __future__ import FeatureName
to use the feature in question, but may continue to use such imports.
MandatoryRelease may also be None, meaning that a planned feature got
dropped.
Instances of class _Feature have two corresponding methods,
.getOptionalRelease() and .getMandatoryRelease().
CompilerFlag is the (bitfield) flag that should be passed in the fourth
argument to the builtin function compile() to enable the feature in
dynamically compiled code. This flag is stored in the .compiler_flag
attribute on _Future instances. These values must match the appropriate
#defines of CO_xxx flags in Include/compile.h.
No feature line is ever to be deleted from this file.
"""
all_feature_names = [
"nested_scopes",
"generators",
"division",
"absolute_import",
"with_statement",
"print_function",
"unicode_literals",
]
__all__ = ["all_feature_names"] + all_feature_names
# The CO_xxx symbols are defined here under the same names used by
# compile.h, so that an editor search will find them here. However,
# they're not exported in __all__, because they don't really belong to
# this module.
CO_NESTED = 0x0010 # nested_scopes
CO_GENERATOR_ALLOWED = 0 # generators (obsolete, was 0x1000)
CO_FUTURE_DIVISION = 0x2000 # division
CO_FUTURE_ABSOLUTE_IMPORT = 0x4000 # perform absolute imports by default
CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement
CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function
CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals
class _Feature:
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
self.optional = optionalRelease
self.mandatory = mandatoryRelease
self.compiler_flag = compiler_flag
def getOptionalRelease(self):
"""Return first release in which this feature was recognized.
This is a 5-tuple, of the same form as sys.version_info.
"""
return self.optional
def getMandatoryRelease(self):
"""Return release in which this feature will become mandatory.
This is a 5-tuple, of the same form as sys.version_info, or, if
the feature was dropped, is None.
"""
return self.mandatory
def __repr__(self):
return "_Feature" + repr((self.optional,
self.mandatory,
self.compiler_flag))
nested_scopes = _Feature((2, 1, 0, "beta", 1),
(2, 2, 0, "alpha", 0),
CO_NESTED)
generators = _Feature((2, 2, 0, "alpha", 1),
(2, 3, 0, "final", 0),
CO_GENERATOR_ALLOWED)
division = _Feature((2, 2, 0, "alpha", 2),
(3, 0, 0, "alpha", 0),
CO_FUTURE_DIVISION)
absolute_import = _Feature((2, 5, 0, "alpha", 1),
(3, 0, 0, "alpha", 0),
CO_FUTURE_ABSOLUTE_IMPORT)
with_statement = _Feature((2, 5, 0, "alpha", 1),
(2, 6, 0, "alpha", 0),
CO_FUTURE_WITH_STATEMENT)
print_function = _Feature((2, 6, 0, "alpha", 2),
(3, 0, 0, "alpha", 0),
CO_FUTURE_PRINT_FUNCTION)
unicode_literals = _Feature((2, 6, 0, "alpha", 2),
(3, 0, 0, "alpha", 0),
CO_FUTURE_UNICODE_LITERALS)
View
No changes.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
No changes.
View
No changes.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -0,0 +1,96 @@
TOPIC_LOOKUP = {'!': '2-2-4',
'&': '2-5-2',
'&&': '2-2-5',
'((': '2-7-1',
':': '2-2-3',
'ASSIGNING-VARIABLES': '3-0-0',
'Arithmetic': '5-1-0',
'BUILTIN-COMMANDS': '6-0-0',
'Boolean': '5-2-0',
'Brace-Expand': '5-4-0',
'Builtin-Procs': '12-2-0',
'COMMAND-LANGUAGE': '2-0-0',
'Child-Process': '6-7-0',
'Commands': '2-1-0',
'Completion': '6-5-0',
'Compound-Data': '3-3-0',
'Concurrency': '2-5-0',
'Conditional': '2-3-0',
'ENVIRONMENT-VARIABLES': '8-0-0',
'Execution': '7-2-0',
'External': '6-9-0',
'Grouping': '2-4-0',
'I/O': '6-1-0',
'INTRO': '1-0-0',
'Introspection': '6-8-0',
'Keywords': '3-1-0',
'Lexing': '1-4-0',
'OIL-EXTENSINOS': '11-0-0',
'OIL-LIBRARIES': '12-0-0',
'OSH-Options': '7-3-0',
'OTHER-SHELL-SUBLANGUAGES': '5-0-0',
'Oil': '1-4-0',
'Operators': '3-2-0',
'Other': '2-7-0',
'Overview': '1-1-0',
'PLUGINS-AND-HOOKS': '10-0-0',
'Parsing': '7-1-0',
'Patterns': '5-3-0',
'Quotes': '4-1-0',
'Redirects': '2-6-0',
'Run-Code': '6-2-0',
'SHELL-OPTIONS': '7-0-0',
'SPECIAL-VARIABLES': '9-0-0',
'Set-Options': '6-3-0',
'Shell-Process': '6-6-0',
'Special-Vars': '4-3-0',
'Substitutions': '4-2-0',
'Usage': '1-2-0',
'Var-Ops': '4-4-0',
'WORD-LANGUAGE': '4-0-0',
'Working-Dir': '6-4-0',
'[[': '2-2-6',
'ampersand': '2-5-2',
'and': '2-2-5',
'bang': '2-2-4',
'block': '2-4-2',
'bundle-usage': '1-2-1',
'caller': '6-8-3',
'case': '2-2-1',
'colon': '2-2-3',
'config': '1-2-4',
'coproc': '2-7-3',
'dbracket': '2-2-6',
'dparen': '2-7-1',
'enable': '6-9-3',
'false': '2-2-3',
'for': '2-3-2',
'for-expr': '2-3-2',
'function': '2-4-1',
'getopt': '6-9-1',
'hash': '6-8-2',
'help': '6-8-1',
'here-doc': '2-6-3',
'if': '2-2-2',
'kill': '6-9-2',
'line-editing': '1-2-6',
'oil-usage': '1-2-3',
'or': '2-2-5',
'osh-usage': '1-2-2',
'overview': '1-1-1',
'pipe': '2-5-1',
'prompt': '1-2-7',
'read': '6-1-1',
'redir-desc': '2-6-2',
'redir-file': '2-6-1',
'semicolon': '2-1-2',
'simple-command': '2-1-1',
'single-command': '1-4-1',
'startup': '1-2-5',
'subshell': '2-4-3',
'time': '2-7-2',
'true': '2-2-3',
'type': '6-8-4',
'until': '2-3-1',
'while': '2-3-1',
'||': '2-2-5'}
View
@@ -0,0 +1,252 @@
from asdl import const # For const.NO_INTEGER
from asdl import py_meta
from osh.meta import RUNTIME_TYPE_LOOKUP as TYPE_LOOKUP
class part_value_e(object):
StringPartValue = 1
ArrayPartValue = 2
class part_value(py_meta.CompoundObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('part_value')
class StringPartValue(part_value):
tag = 1
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('StringPartValue')
__slots__ = ('s', 'do_split_glob', 'spids')
def __init__(self, s=None, do_split_glob=None, spids=None):
self.s = s
self.do_split_glob = do_split_glob
self.spids = spids or []
class ArrayPartValue(part_value):
tag = 2
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('ArrayPartValue')
__slots__ = ('strs', 'spids')
def __init__(self, strs=None, spids=None):
self.strs = strs or []
self.spids = spids or []
class value_e(object):
Undef = 1
Str = 2
StrArray = 3
class value(py_meta.CompoundObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('value')
class Undef(value):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('Undef')
tag = 1
class Str(value):
tag = 2
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('Str')
__slots__ = ('s', 'spids')
def __init__(self, s=None, spids=None):
self.s = s
self.spids = spids or []
class StrArray(value):
tag = 3
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('StrArray')
__slots__ = ('strs', 'spids')
def __init__(self, strs=None, spids=None):
self.strs = strs or []
self.spids = spids or []
class cell(py_meta.CompoundObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('cell')
__slots__ = ('val', 'exported', 'readonly', 'spids')
def __init__(self, val=None, exported=None, readonly=None, spids=None):
self.val = val
self.exported = exported
self.readonly = readonly
self.spids = spids or []
class var_flags_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('var_flags')
var_flags_e.Exported = var_flags_e(1, 'Exported')
var_flags_e.ReadOnly = var_flags_e(2, 'ReadOnly')
class scope_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('scope')
scope_e.TempEnv = scope_e(1, 'TempEnv')
scope_e.LocalOnly = scope_e(2, 'LocalOnly')
scope_e.GlobalOnly = scope_e(3, 'GlobalOnly')
scope_e.Dynamic = scope_e(4, 'Dynamic')
class lvalue_e(object):
LhsName = 1
LhsIndexedName = 2
class lvalue(py_meta.CompoundObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('lvalue')
class LhsName(lvalue):
tag = 1
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('LhsName')
__slots__ = ('name', 'spids')
def __init__(self, name=None, spids=None):
self.name = name
self.spids = spids or []
class LhsIndexedName(lvalue):
tag = 2
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('LhsIndexedName')
__slots__ = ('name', 'index', 'spids')
def __init__(self, name=None, index=None, spids=None):
self.name = name
self.index = index
self.spids = spids or []
class redirect_e(object):
PathRedirect = 1
DescRedirect = 2
HereRedirect = 3
class redirect(py_meta.CompoundObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('redirect')
class PathRedirect(redirect):
tag = 1
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('PathRedirect')
__slots__ = ('op_id', 'fd', 'filename', 'spids')
def __init__(self, op_id=None, fd=None, filename=None, spids=None):
self.op_id = op_id
self.fd = fd
self.filename = filename
self.spids = spids or []
class DescRedirect(redirect):
tag = 2
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('DescRedirect')
__slots__ = ('op_id', 'fd', 'target_fd', 'spids')
def __init__(self, op_id=None, fd=None, target_fd=None, spids=None):
self.op_id = op_id
self.fd = fd
self.target_fd = target_fd
self.spids = spids or []
class HereRedirect(redirect):
tag = 3
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('HereRedirect')
__slots__ = ('fd', 'body', 'spids')
def __init__(self, fd=None, body=None, spids=None):
self.fd = fd
self.body = body
self.spids = spids or []
class job_status_e(object):
ProcessStatus = 1
PipelineStatus = 2
class job_status(py_meta.CompoundObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('job_status')
class ProcessStatus(job_status):
tag = 1
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('ProcessStatus')
__slots__ = ('status', 'spids')
def __init__(self, status=None, spids=None):
self.status = status
self.spids = spids or []
class PipelineStatus(job_status):
tag = 2
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('PipelineStatus')
__slots__ = ('statuses', 'spids')
def __init__(self, statuses=None, spids=None):
self.statuses = statuses or []
self.spids = spids or []
class span_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('span')
span_e.Black = span_e(1, 'Black')
span_e.Delim = span_e(2, 'Delim')
span_e.Backslash = span_e(3, 'Backslash')
class builtin_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('builtin')
builtin_e.NONE = builtin_e(1, 'NONE')
builtin_e.READ = builtin_e(2, 'READ')
builtin_e.ECHO = builtin_e(3, 'ECHO')
builtin_e.SHIFT = builtin_e(4, 'SHIFT')
builtin_e.CD = builtin_e(5, 'CD')
builtin_e.PUSHD = builtin_e(6, 'PUSHD')
builtin_e.POPD = builtin_e(7, 'POPD')
builtin_e.DIRS = builtin_e(8, 'DIRS')
builtin_e.EXPORT = builtin_e(9, 'EXPORT')
builtin_e.UNSET = builtin_e(10, 'UNSET')
builtin_e.SET = builtin_e(11, 'SET')
builtin_e.SHOPT = builtin_e(12, 'SHOPT')
builtin_e.TRAP = builtin_e(13, 'TRAP')
builtin_e.UMASK = builtin_e(14, 'UMASK')
builtin_e.SOURCE = builtin_e(15, 'SOURCE')
builtin_e.DOT = builtin_e(16, 'DOT')
builtin_e.EVAL = builtin_e(17, 'EVAL')
builtin_e.EXEC = builtin_e(18, 'EXEC')
builtin_e.WAIT = builtin_e(19, 'WAIT')
builtin_e.JOBS = builtin_e(20, 'JOBS')
builtin_e.COMPLETE = builtin_e(21, 'COMPLETE')
builtin_e.COMPGEN = builtin_e(22, 'COMPGEN')
builtin_e.DEBUG_LINE = builtin_e(23, 'DEBUG_LINE')
builtin_e.TRUE = builtin_e(24, 'TRUE')
builtin_e.FALSE = builtin_e(25, 'FALSE')
builtin_e.COLON = builtin_e(26, 'COLON')
builtin_e.TEST = builtin_e(27, 'TEST')
builtin_e.BRACKET = builtin_e(28, 'BRACKET')
builtin_e.GETOPTS = builtin_e(29, 'GETOPTS')
builtin_e.COMMAND = builtin_e(30, 'COMMAND')
builtin_e.TYPE = builtin_e(31, 'TYPE')
builtin_e.HELP = builtin_e(32, 'HELP')
builtin_e.DECLARE = builtin_e(33, 'DECLARE')
builtin_e.TYPESET = builtin_e(34, 'TYPESET')
class effect_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('effect')
effect_e.SpliceParts = effect_e(1, 'SpliceParts')
effect_e.Error = effect_e(2, 'Error')
effect_e.SpliceAndAssign = effect_e(3, 'SpliceAndAssign')
effect_e.NoOp = effect_e(4, 'NoOp')
class process_state_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('process_state')
process_state_e.Init = process_state_e(1, 'Init')
process_state_e.Done = process_state_e(2, 'Done')
class completion_state_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('completion_state')
completion_state_e.NONE = completion_state_e(1, 'NONE')
completion_state_e.FIRST = completion_state_e(2, 'FIRST')
completion_state_e.REST = completion_state_e(3, 'REST')
completion_state_e.VAR_NAME = completion_state_e(4, 'VAR_NAME')
completion_state_e.HASH_KEY = completion_state_e(5, 'HASH_KEY')
completion_state_e.REDIR_FILENAME = completion_state_e(6, 'REDIR_FILENAME')
class word_style_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('word_style')
word_style_e.Expr = word_style_e(1, 'Expr')
word_style_e.Unquoted = word_style_e(2, 'Unquoted')
word_style_e.DQ = word_style_e(3, 'DQ')
word_style_e.SQ = word_style_e(4, 'SQ')
View
@@ -0,0 +1,39 @@
from asdl import const # For const.NO_INTEGER
from asdl import py_meta
from osh.meta import TYPES_TYPE_LOOKUP as TYPE_LOOKUP
class bool_arg_type_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('bool_arg_type')
bool_arg_type_e.Undefined = bool_arg_type_e(1, 'Undefined')
bool_arg_type_e.Path = bool_arg_type_e(2, 'Path')
bool_arg_type_e.Int = bool_arg_type_e(3, 'Int')
bool_arg_type_e.Str = bool_arg_type_e(4, 'Str')
bool_arg_type_e.Other = bool_arg_type_e(5, 'Other')
class redir_arg_type_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('redir_arg_type')
redir_arg_type_e.Path = redir_arg_type_e(1, 'Path')
redir_arg_type_e.Desc = redir_arg_type_e(2, 'Desc')
redir_arg_type_e.Here = redir_arg_type_e(3, 'Here')
class lex_mode_e(py_meta.SimpleObj):
ASDL_TYPE = TYPE_LOOKUP.ByTypeName('lex_mode')
lex_mode_e.NONE = lex_mode_e(1, 'NONE')
lex_mode_e.COMMENT = lex_mode_e(2, 'COMMENT')
lex_mode_e.OUTER = lex_mode_e(3, 'OUTER')
lex_mode_e.DBRACKET = lex_mode_e(4, 'DBRACKET')
lex_mode_e.SQ = lex_mode_e(5, 'SQ')
lex_mode_e.DQ = lex_mode_e(6, 'DQ')
lex_mode_e.DOLLAR_SQ = lex_mode_e(7, 'DOLLAR_SQ')
lex_mode_e.ARITH = lex_mode_e(8, 'ARITH')
lex_mode_e.EXTGLOB = lex_mode_e(9, 'EXTGLOB')
lex_mode_e.VS_1 = lex_mode_e(10, 'VS_1')
lex_mode_e.VS_2 = lex_mode_e(11, 'VS_2')
lex_mode_e.VS_ARG_UNQ = lex_mode_e(12, 'VS_ARG_UNQ')
lex_mode_e.VS_ARG_DQ = lex_mode_e(13, 'VS_ARG_DQ')
lex_mode_e.BASH_REGEX = lex_mode_e(14, 'BASH_REGEX')
lex_mode_e.BASH_REGEX_CHARS = lex_mode_e(15, 'BASH_REGEX_CHARS')
View
@@ -0,0 +1,204 @@
# Access WeakSet through the weakref module.
# This code is separated-out because it is needed
# by abc.py to load everything else at startup.
from _weakref import ref
__all__ = ['WeakSet']
class _IterationGuard(object):
# This context manager registers itself in the current iterators of the
# weak container, such as to delay all removals until the context manager
# exits.
# This technique should be relatively thread-safe (since sets are).
def __init__(self, weakcontainer):
# Don't create cycles
self.weakcontainer = ref(weakcontainer)
def __enter__(self):
w = self.weakcontainer()
if w is not None:
w._iterating.add(self)
return self
def __exit__(self, e, t, b):
w = self.weakcontainer()
if w is not None:
s = w._iterating
s.remove(self)
if not s:
w._commit_removals()
class WeakSet(object):
def __init__(self, data=None):
self.data = set()
def _remove(item, selfref=ref(self)):
self = selfref()
if self is not None:
if self._iterating:
self._pending_removals.append(item)
else:
self.data.discard(item)
self._remove = _remove
# A list of keys to be removed
self._pending_removals = []
self._iterating = set()
if data is not None:
self.update(data)
def _commit_removals(self):
l = self._pending_removals
discard = self.data.discard
while l:
discard(l.pop())
def __iter__(self):
with _IterationGuard(self):
for itemref in self.data:
item = itemref()
if item is not None:
# Caveat: the iterator will keep a strong reference to
# `item` until it is resumed or closed.
yield item
def __len__(self):
return len(self.data) - len(self._pending_removals)
def __contains__(self, item):
try:
wr = ref(item)
except TypeError:
return False
return wr in self.data
def __reduce__(self):
return (self.__class__, (list(self),),
getattr(self, '__dict__', None))
__hash__ = None
def add(self, item):
if self._pending_removals:
self._commit_removals()
self.data.add(ref(item, self._remove))
def clear(self):
if self._pending_removals:
self._commit_removals()
self.data.clear()
def copy(self):
return self.__class__(self)
def pop(self):
if self._pending_removals:
self._commit_removals()
while True:
try:
itemref = self.data.pop()
except KeyError:
raise KeyError('pop from empty WeakSet')
item = itemref()
if item is not None:
return item
def remove(self, item):
if self._pending_removals:
self._commit_removals()
self.data.remove(ref(item))
def discard(self, item):
if self._pending_removals:
self._commit_removals()
self.data.discard(ref(item))
def update(self, other):
if self._pending_removals:
self._commit_removals()
for element in other:
self.add(element)
def __ior__(self, other):
self.update(other)
return self
def difference(self, other):
newset = self.copy()
newset.difference_update(other)
return newset
__sub__ = difference
def difference_update(self, other):
self.__isub__(other)
def __isub__(self, other):
if self._pending_removals:
self._commit_removals()
if self is other:
self.data.clear()
else:
self.data.difference_update(ref(item) for item in other)
return self
def intersection(self, other):
return self.__class__(item for item in other if item in self)
__and__ = intersection
def intersection_update(self, other):
self.__iand__(other)
def __iand__(self, other):
if self._pending_removals:
self._commit_removals()
self.data.intersection_update(ref(item) for item in other)
return self
def issubset(self, other):
return self.data.issubset(ref(item) for item in other)
__le__ = issubset
def __lt__(self, other):
return self.data < set(ref(item) for item in other)
def issuperset(self, other):
return self.data.issuperset(ref(item) for item in other)
__ge__ = issuperset
def __gt__(self, other):
return self.data > set(ref(item) for item in other)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self.data == set(ref(item) for item in other)
def __ne__(self, other):
opposite = self.__eq__(other)
if opposite is NotImplemented:
return NotImplemented
return not opposite
def symmetric_difference(self, other):
newset = self.copy()
newset.symmetric_difference_update(other)
return newset
__xor__ = symmetric_difference
def symmetric_difference_update(self, other):
self.__ixor__(other)
def __ixor__(self, other):
if self._pending_removals:
self._commit_removals()
if self is other:
self.data.clear()
else:
self.data.symmetric_difference_update(ref(item, self._remove) for item in other)
return self
def union(self, other):
return self.__class__(e for s in (self, other) for e in s)
__or__ = union
def isdisjoint(self, other):
return len(self.intersection(other)) == 0
View
@@ -0,0 +1,185 @@
# Copyright 2007 Google, Inc. All Rights Reserved.
# Licensed to PSF under a Contributor Agreement.
"""Abstract Base Classes (ABCs) according to PEP 3119."""
import types
from _weakrefset import WeakSet
# Instance of old-style class
class _C: pass
_InstanceType = type(_C())
def abstractmethod(funcobj):
"""A decorator indicating abstract methods.
Requires that the metaclass is ABCMeta or derived from it. A
class that has a metaclass derived from ABCMeta cannot be
instantiated unless all of its abstract methods are overridden.
The abstract methods can be called using any of the normal
'super' call mechanisms.
Usage:
class C:
__metaclass__ = ABCMeta
@abstractmethod
def my_abstract_method(self, ...):
...
"""
funcobj.__isabstractmethod__ = True
return funcobj
class abstractproperty(property):
"""A decorator indicating abstract properties.
Requires that the metaclass is ABCMeta or derived from it. A
class that has a metaclass derived from ABCMeta cannot be
instantiated unless all of its abstract properties are overridden.
The abstract properties can be called using any of the normal
'super' call mechanisms.
Usage:
class C:
__metaclass__ = ABCMeta
@abstractproperty
def my_abstract_property(self):
...
This defines a read-only property; you can also define a read-write
abstract property using the 'long' form of property declaration:
class C:
__metaclass__ = ABCMeta
def getx(self): ...
def setx(self, value): ...
x = abstractproperty(getx, setx)
"""
__isabstractmethod__ = True
class ABCMeta(type):
"""Metaclass for defining Abstract Base Classes (ABCs).
Use this metaclass to create an ABC. An ABC can be subclassed
directly, and then acts as a mix-in class. You can also register
unrelated concrete classes (even built-in classes) and unrelated
ABCs as 'virtual subclasses' -- these and their descendants will
be considered subclasses of the registering ABC by the built-in
issubclass() function, but the registering ABC won't show up in
their MRO (Method Resolution Order) nor will method
implementations defined by the registering ABC be callable (not
even via super()).
"""
# A global counter that is incremented each time a class is
# registered as a virtual subclass of anything. It forces the
# negative cache to be cleared before its next use.
_abc_invalidation_counter = 0
def __new__(mcls, name, bases, namespace):
cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace)
# Compute set of abstract method names
abstracts = set(name
for name, value in namespace.items()
if getattr(value, "__isabstractmethod__", False))
for base in bases:
for name in getattr(base, "__abstractmethods__", set()):
value = getattr(cls, name, None)
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)
cls.__abstractmethods__ = frozenset(abstracts)
# Set up inheritance registry
cls._abc_registry = WeakSet()
cls._abc_cache = WeakSet()
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
return cls
def register(cls, subclass):
"""Register a virtual subclass of an ABC."""
if not isinstance(subclass, (type, types.ClassType)):
raise TypeError("Can only register classes")
if issubclass(subclass, cls):
return # Already a subclass
# Subtle: test for cycles *after* testing for "already a subclass";
# this means we allow X.register(X) and interpret it as a no-op.
if issubclass(cls, subclass):
# This would create a cycle, which is bad for the algorithm below
raise RuntimeError("Refusing to create an inheritance cycle")
cls._abc_registry.add(subclass)
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print >> file, "Class: %s.%s" % (cls.__module__, cls.__name__)
print >> file, "Inv.counter: %s" % ABCMeta._abc_invalidation_counter
for name in sorted(cls.__dict__.keys()):
if name.startswith("_abc_"):
value = getattr(cls, name)
print >> file, "%s: %r" % (name, value)
def __instancecheck__(cls, instance):
"""Override for isinstance(instance, cls)."""
# Inline the cache checking when it's simple.
subclass = getattr(instance, '__class__', None)
if subclass is not None and subclass in cls._abc_cache:
return True
subtype = type(instance)
# Old-style instances
if subtype is _InstanceType:
subtype = subclass
if subtype is subclass or subclass is None:
if (cls._abc_negative_cache_version ==
ABCMeta._abc_invalidation_counter and
subtype in cls._abc_negative_cache):
return False
# Fall back to the subclass check.
return cls.__subclasscheck__(subtype)
return (cls.__subclasscheck__(subclass) or
cls.__subclasscheck__(subtype))
def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
# Check cache
if subclass in cls._abc_cache:
return True
# Check negative cache; may have to invalidate
if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter:
# Invalidate the negative cache
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
elif subclass in cls._abc_negative_cache:
return False
# Check the subclass hook
ok = cls.__subclasshook__(subclass)
if ok is not NotImplemented:
assert isinstance(ok, bool)
if ok:
cls._abc_cache.add(subclass)
else:
cls._abc_negative_cache.add(subclass)
return ok
# Check if it's a direct subclass
if cls in getattr(subclass, '__mro__', ()):
cls._abc_cache.add(subclass)
return True
# Check if it's a subclass of a registered class (recursive)
for rcls in cls._abc_registry:
if issubclass(subclass, rcls):
cls._abc_cache.add(subclass)
return True
# Check if it's a subclass of a subclass (recursive)
for scls in cls.__subclasses__():
if issubclass(subclass, scls):
cls._abc_cache.add(subclass)
return True
# No dice; update negative cache
cls._abc_negative_cache.add(subclass)
return False
View
No changes.
View
@@ -0,0 +1,19 @@
#!/usr/bin/env python
"""
arith_ast.py
"""
import os
import sys
from asdl import asdl_ as asdl
from asdl import py_meta
this_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
schema_path = os.path.join(this_dir, 'arith.asdl')
with open(schema_path) as f:
module = asdl.parse(f)
type_lookup = asdl.ResolveTypes(module)
root = sys.modules[__name__]
py_meta.MakeTypes(module, root, type_lookup)
View
@@ -0,0 +1,197 @@
#!/usr/bin/env python
from __future__ import print_function
"""
arith_ast_test.py: Tests for arith_ast.py
"""
import cStringIO
import unittest
from asdl import py_meta
from asdl import asdl_
from asdl import const
from asdl import encode
from asdl import arith_ast # module under test
# Sanity check. Doesn't pass because this unit test exposes implementatio
# details, like the concrete classes.
#from _tmp import arith_ast_asdl as arith_ast
ArithVar = arith_ast.ArithVar
ArithUnary = arith_ast.ArithUnary
ArithBinary = arith_ast.ArithBinary
Const = arith_ast.Const
Slice = arith_ast.Slice
arith_expr = arith_ast.arith_expr
source_location = arith_ast.source_location
op_id_e = arith_ast.op_id_e
cflow_e = arith_ast.cflow_e
#cflow_t = arith_ast.cflow_t
class ArithAstTest(unittest.TestCase):
def testFieldDefaults(self):
s = arith_ast.Slice()
s.a = ArithVar('foo')
self.assertEqual(None, s.begin)
self.assertEqual(None, s.end)
self.assertEqual(None, s.stride)
print(s)
func = arith_ast.FuncCall()
func.name = 'f'
self.assertEqual([], func.args)
print(func)
t = arith_ast.token(5, 'x')
self.assertEqual(5, t.id)
self.assertEqual('x', t.value)
self.assertEqual(const.NO_INTEGER, t.span_id)
def testTypeCheck(self):
v = ArithVar('name')
# Integer is not allowed
self.assertRaises(AssertionError, ArithVar, 1)
v = ArithUnary(op_id_e.Minus, Const(99))
# Raw integer is not allowed
self.assertRaises(AssertionError, ArithUnary, op_id_e.Minus, 99)
v = ArithUnary(op_id_e.Minus, Const(99))
# Raw integer is not allowed
#self.assertRaises(AssertionError, ArithUnary, op_id_e.Minus, op_id_e.Plus)
def testExtraFields(self):
v = ArithVar('z')
# TODO: Attach this to EVERY non-simple constructor? Those are subclasses
# of Sum types.
# What about product types?
#print(v.xspans)
def testEncode(self):
obj = arith_ast.Const(99)
print('Encoding into binary:')
print(obj)
enc = encode.Params()
f = cStringIO.StringIO()
out = encode.BinOutput(f)
encode.EncodeRoot(obj, enc, out)
e = f.getvalue()
#print(repr(e))
#print(e[0:4], e[4:8], e[8:])
# Header is OHP version 1
self.assertEqual(b'OHP\x01', e[0:4])
self.assertEqual(b'\x04', e[4:5]) # alignment 4
# TODO: Fix after spids
return
self.assertEqual(b'\x02\x00\x00', e[5:8]) # root ref 2
self.assertEqual(b'\x01', e[8:9]) # tag 1 is const
self.assertEqual(b'\x63\x00\x00', e[9:12]) # 0x63 = 99
def testConstructorType(self):
n1 = ArithVar('x')
n2 = ArithVar(name='y')
print(n1)
print(n2)
# Not good because not assigned?
n3 = ArithVar()
# NOTE: You cannot instantiate a product type directly? It's just used for
# type checking. What about OCaml?
# That means you just need to create classes for the records (Constructor).
# They all descend from Obj. They don't need
n3 = ArithVar()
try:
n4 = ArithVar('x', name='X')
except TypeError as e:
pass
else:
raise AssertionError("Should have failed")
def testProductType(self):
print()
print('-- PRODUCT --')
print()
s = source_location()
s.path = 'hi'
s.line = 1
s.col = 2
s.length = 3
print(s)
assert isinstance(s.ASDL_TYPE, asdl_.Product)
# Implementation detail for dynamic type checking
assert isinstance(s, py_meta.CompoundObj)
def testSimpleSumType(self):
# TODO: Should be op_id_i.Plus -- instance
# Should be op_id_s.Plus
print()
print('-- SIMPLE SUM --')
print()
o = op_id_e.Plus
assert isinstance(o, py_meta.SimpleObj)
# Implementation detail for dynamic type checking
assert isinstance(o.ASDL_TYPE, asdl_.Sum)
def testCompoundSumType(self):
print()
print('-- COMPOUND SUM --')
print()
# TODO: Should be cflow_t.Break() and cflow_i.Break
c = arith_ast.Break()
assert isinstance(c, arith_ast.Break)
assert isinstance(c, arith_ast.cflow)
assert isinstance(c, py_meta.CompoundObj)
# Implementation detail for dynamic type checking
assert isinstance(c.ASDL_TYPE, asdl_.Constructor), c.ASDL_TYPE
def testOtherTypes(self):
c = Const(66)
print(c)
print((Slice(Const(1), Const(5), Const(2))))
print((op_id_e.Plus))
# Class for sum type
print(arith_expr)
# Invalid because only half were assigned
#print(ArithBinary(op_id_e.Plus, Const(5)))
n = ArithBinary()
#n.CheckUnassigned()
n.op_id = op_id_e.Plus
n.left = Const(5)
#n.CheckUnassigned()
n.right = Const(6)
n.CheckUnassigned()
arith_expr_e = arith_ast.arith_expr_e
self.assertEqual(arith_expr_e.Const, c.tag)
self.assertEqual(arith_expr_e.ArithBinary, n.tag)
if __name__ == '__main__':
unittest.main()
View
@@ -0,0 +1,246 @@
#!/usr/bin/env python
from __future__ import print_function
"""
arith_parse.py: Parse shell-like and C-like arithmetic.
"""
import sys
from asdl import tdop
from asdl.tdop import CompositeNode
from asdl import arith_ast
op_id = arith_ast.op_id_e # TODO: Rename this back.
#
# Null Denotation -- token that takes nothing on the left
#
def NullConstant(p, token, bp):
if token.type == 'number':
return arith_ast.Const(token.val)
# We have to wrap a string in some kind of variant.
if token.type == 'name':
return arith_ast.ArithVar(token.val)
raise AssertionError(token.type)
def NullParen(p, token, bp):
""" Arithmetic grouping """
r = p.ParseUntil(bp)
p.Eat(')')
return r
def NullPrefixOp(p, token, bp):
"""Prefix operator.
Low precedence: return, raise, etc.
return x+y is return (x+y), not (return x) + y
High precedence: logical negation, bitwise complement, etc.
!x && y is (!x) && y, not !(x && y)
"""
r = p.ParseUntil(bp)
return CompositeNode(token, [r])
def NullIncDec(p, token, bp):
""" ++x or ++x[1] """
right = p.ParseUntil(bp)
if right.token.type not in ('name', 'get'):
raise tdop.ParseError("Can't assign to %r (%s)" % (right, right.token))
return CompositeNode(token, [right])
#
# Left Denotation -- token that takes an expression on the left
#
def LeftIncDec(p, token, left, rbp):
""" For i++ and i--
"""
if left.token.type not in ('name', 'get'):
raise tdop.ParseError("Can't assign to %r (%s)" % (left, left.token))
token.type = 'post' + token.type
return CompositeNode(token, [left])
def LeftIndex(p, token, left, unused_bp):
""" index f[x+1] """
# f[x] or f[x][y]
if not isinstance(left, arith_ast.ArithVar):
raise tdop.ParseError("%s can't be indexed" % left)
index = p.ParseUntil(0)
if p.AtToken(':'):
p.Next()
end = p.ParseUntil(0)
else:
end = None
p.Eat(']')
# TODO: If you see ], then
# 1:4
# 1:4:2
# Both end and step are optional
if end:
return arith_ast.Slice(left, index, end, None)
else:
return arith_ast.Index(left, index)
def LeftTernary(p, token, left, bp):
""" e.g. a > 1 ? x : y """
true_expr = p.ParseUntil(bp)
p.Eat(':')
false_expr = p.ParseUntil(bp)
children = [left, true_expr, false_expr]
return CompositeNode(token, children)
def LeftBinaryOp(p, token, left, rbp):
""" Normal binary operator like 1+2 or 2*3, etc. """
if token.val == '+':
op_id_ = op_id.Plus
elif token.val == '-':
op_id_ = op_id.Minus
elif token.val == '*':
op_id_ = op_id.Star
else:
raise AssertionError(token.val)
return arith_ast.ArithBinary(op_id_, left, p.ParseUntil(rbp))
def LeftAssign(p, token, left, rbp):
""" Normal binary operator like 1+2 or 2*3, etc. """
# x += 1, or a[i] += 1
if left.token.type not in ('name', 'get'):
raise tdop.ParseError("Can't assign to %r (%s)" % (left, left.token))
return CompositeNode(token, [left, p.ParseUntil(rbp)])
def LeftComma(p, token, left, rbp):
""" foo, bar, baz
Could be sequencing operator, or tuple without parens
"""
r = p.ParseUntil(rbp)
if left.token.type == ',': # Keep adding more children
left.children.append(r)
return left
children = [left, r]
return CompositeNode(token, children)
# For overloading of , inside function calls
COMMA_PREC = 1
def LeftFuncCall(p, token, left, unused_bp):
""" Function call f(a, b). """
args = []
# f(x) or f[i](x)
if not isinstance(left, arith_ast.ArithVar):
raise tdop.ParseError("%s can't be called" % left)
func_name = left.name # get a string
while not p.AtToken(')'):
# We don't want to grab the comma, e.g. it is NOT a sequence operator. So
# set the precedence to 5.
args.append(p.ParseUntil(COMMA_PREC))
if p.AtToken(','):
p.Next()
p.Eat(")")
return arith_ast.FuncCall(func_name, args)
def MakeShellParserSpec():
"""
Create a parser.
Compare the code below with this table of C operator precedence:
http://en.cppreference.com/w/c/language/operator_precedence
"""
spec = tdop.ParserSpec()
spec.Left(31, LeftIncDec, ['++', '--'])
spec.Left(31, LeftFuncCall, ['('])
spec.Left(31, LeftIndex, ['['])
# 29 -- binds to everything except function call, indexing, postfix ops
spec.Null(29, NullIncDec, ['++', '--'])
spec.Null(29, NullPrefixOp, ['+', '!', '~', '-'])
# Right associative: 2 ** 3 ** 2 == 2 ** (3 ** 2)
spec.LeftRightAssoc(27, LeftBinaryOp, ['**'])
spec.Left(25, LeftBinaryOp, ['*', '/', '%'])
spec.Left(23, LeftBinaryOp, ['+', '-'])
spec.Left(21, LeftBinaryOp, ['<<', '>>'])
spec.Left(19, LeftBinaryOp, ['<', '>', '<=', '>='])
spec.Left(17, LeftBinaryOp, ['!=', '=='])
spec.Left(15, LeftBinaryOp, ['&'])
spec.Left(13, LeftBinaryOp, ['^'])
spec.Left(11, LeftBinaryOp, ['|'])
spec.Left(9, LeftBinaryOp, ['&&'])
spec.Left(7, LeftBinaryOp, ['||'])
spec.LeftRightAssoc(5, LeftTernary, ['?'])
# Right associative: a = b = 2 is a = (b = 2)
spec.LeftRightAssoc(3, LeftAssign, [
'=',
'+=', '-=', '*=', '/=', '%=',
'<<=', '>>=', '&=', '^=', '|='])
spec.Left(COMMA_PREC, LeftComma, [','])
# 0 precedence -- doesn't bind until )
spec.Null(0, NullParen, ['(']) # for grouping
# -1 precedence -- never used
spec.Null(-1, NullConstant, ['name', 'number'])
spec.Null(-1, tdop.NullError, [')', ']', ':', 'eof'])
return spec
def MakeParser(s):
"""Used by tests."""
spec = MakeShellParserSpec()
lexer = tdop.Tokenize(s)
p = tdop.Parser(spec, lexer)
return p
def ParseShell(s, expected=None):
"""Used by tests."""
p = MakeParser(s)
tree = p.Parse()
sexpr = repr(tree)
if expected is not None:
assert sexpr == expected, '%r != %r' % (sexpr, expected)
#print('%-40s %s' % (s, sexpr))
return tree
def main(argv):
try:
s = argv[1]
except IndexError:
print('Usage: ./arith_parse.py EXPRESSION')
else:
try:
tree = ParseShell(s)
except tdop.ParseError as e:
print('Error parsing %r: %s' % (s, e), file=sys.stderr)
if __name__ == '__main__':
main(sys.argv)
View
@@ -0,0 +1,223 @@
#!/usr/bin/env python
from __future__ import print_function
from asdl import tdop
from asdl import arith_ast
from asdl import arith_parse # module under test
def _assertParseError(make_parser, s, error_substring=''):
p = make_parser(s)
try:
node = p.Parse()
except tdop.ParseError as e:
err = str(e)
if error_substring in err:
print('got expected error for %s: %s' % (s, err))
else:
raise AssertionError('Expected %r to be in %r' % (error_substring, err))
else:
raise AssertionError('%r should have failed' % s)
def TestArith(t_parse):
t_parse('1+2+3', '(+ (+ 1 2) 3)')
t_parse('1+2*3', '(+ 1 (* 2 3))')
t_parse('4*(2+3)', '(* 4 (+ 2 3))')
t_parse('(2+3)*4', '(* (+ 2 3) 4)')
return
t_parse('1<2', '(< 1 2)')
t_parse('x=3', '(= x 3)')
t_parse('x = 2*3', '(= x (* 2 3))')
t_parse('x = y', '(= x y)')
t_parse('x*y - y*z', '(- (* x y) (* y z))')
t_parse('x/y - y%z', '(- (/ x y) (% y z))')
t_parse("x = y", "(= x y)")
t_parse('2 ** 3 ** 2', '(** 2 (** 3 2))')
t_parse('a = b = 10', '(= a (= b 10))')
t_parse('x = ((y*4)-2)', '(= x (- (* y 4) 2))')
t_parse('x - -y', '(- x (- y))')
t_parse("-1 * -2", "(* (- 1) (- 2))")
t_parse("-x * -y", "(* (- x) (- y))")
t_parse('x - -234', '(- x (- 234))')
# Python doesn't allow this
t_parse('x += y += 3', '(+= x (+= y 3))')
# This is sort of nonsensical, but bash allows it. The 1 is discarded as
# the first element of the comma operator.
t_parse('x[1,2]', '(get x (, 1 2))')
# Python doesn't have unary +
t_parse('+1 - +2', '(- (+ 1) (+ 2))')
# LHS
t_parse('f[x] += 1', '(+= (get f x) 1)')
def TestBitwise(t_parse):
t_parse("~1 | ~2", "(| (~ 1) (~ 2))")
t_parse("x & y | a & b", "(| (& x y) (& a b))")
t_parse("~x ^ y", "(^ (~ x) y)")
t_parse("x << y | y << z", "(| (<< x y) (<< y z))")
t_parse("a ^= b-1", "(^= a (- b 1))")
def TestLogical(t_parse):
t_parse("a && b || c && d", "(|| (&& a b) (&& c d))")
t_parse("!a && !b", "(&& (! a) (! b))")
t_parse("a != b && c == d", "(&& (!= a b) (== c d))")
t_parse("a > b ? 0 : 1", "(? (> a b) 0 1)")
t_parse("a > b ? x+1 : y+1", "(? (> a b) (+ x 1) (+ y 1))")
t_parse("1 ? true1 : 2 ? true2 : false", "(? 1 true1 (? 2 true2 false))")
t_parse("1 ? true1 : (2 ? true2 : false)", "(? 1 true1 (? 2 true2 false))")
t_parse("1 ? (2 ? true : false1) : false2", "(? 1 (? 2 true false1) false2)")
t_parse("1 ? 2 ? true : false1 : false2", "(? 1 (? 2 true false1) false2)")
# Should have higher precedence than comma
t_parse("x ? 1 : 2, y ? 3 : 4", "(, (? x 1 2) (? y 3 4))")
def TestUnary(t_parse):
t_parse("!x", "(! x)")
t_parse("x--", "(post-- x)")
t_parse("x[1]--", "(post-- (get x 1))")
t_parse("--x", "(-- x)")
t_parse("++x[1]", "(++ (get x 1))")
t_parse("!x--", "(! (post-- x))")
t_parse("~x++", "(~ (post++ x))")
t_parse("x++ - y++", "(- (post++ x) (post++ y))")
t_parse("++x - ++y", "(- (++ x) (++ y))")
#
# 1. x++ f() x[] left associative
# f(x)[1]++ means
# (++ (get (call f x) 1))
# 2. ++x + - ! ~ right associative
# -++x means (- (++ x))
def TestArrays(t_parse):
"""Shared between shell, oil, and Python."""
t_parse('x[1]', '(get x 1)')
t_parse('x[a+b]', '(get x (+ a b))')
def TestComma(t_parse):
t_parse('x=1,y=2,z=3', '(, (= x 1) (= y 2) (= z 3))')
def TestFuncCalls(t_parse):
t_parse('x = y(2)*3 + y(4)*5', '(= x (+ (* (call y 2) 3) (* (call y 4) 5)))')
t_parse('x(1,2)+y(3,4)', '(+ (call x 1 2) (call y 3 4))')
t_parse('x(a,b,c[d])', '(call x a b (get c d))')
t_parse('x(1,2)*j+y(3,4)*k+z(5,6)*l',
'(+ (+ (* (call x 1 2) j) (* (call y 3 4) k)) (* (call z 5 6) l))')
t_parse('print(test(2,3))', '(call print (call test 2 3))')
t_parse('print("x")', '(call print x)')
t_parse('min(255,n*2)', '(call min 255 (* n 2))')
t_parse('c = pal[i*8]', '(= c (get pal (* i 8)))')
def TestErrors(p):
_assertParseError(p, '}')
_assertParseError(p, ']')
_assertParseError(p, '{') # depends on language
_assertParseError(p, "x+1 = y", "Can't assign")
_assertParseError(p, "(x+1)++", "Can't assign")
# Should be an EOF error
_assertParseError(p, 'foo ? 1 :', 'Unexpected end')
_assertParseError(p, 'foo ? 1 ', 'expected :')
_assertParseError(p, '%', "can't be used in prefix position")
error_str = "can't be used in prefix"
_assertParseError(p, '}')
_assertParseError(p, '{')
_assertParseError(p, ']', error_str)
_assertParseError(p, '1 ( 2', "can't be called")
_assertParseError(p, '(x+1) ( 2 )', "can't be called")
#_assertParseError(p, '1 ) 2')
_assertParseError(p, '1 [ 2 ]', "can't be indexed")
arith_expr_e = arith_ast.arith_expr_e
#source_location = arith_ast.source_location
#op_id_e = arith_ast.op_id_e
class Visitor(object):
def __init__(self):
pass
# In Python, they do introspection on method names.
# method = 'visit_' + node.__class__.__name__
# I'm not going to bother, because I have ASDL! I want the generic visitor.
def Visit(self, node):
raise NotImplementedError
# Like ast.NodeVisitor().generic_visit!
def VisitChildren(self, node):
#print dir(node)
# TODO: Use node.ASDL_TYPE.GetFields()
# Only compound children get visited?
print([name for name in dir(node) if not name.startswith('_')])
# Call self.Visit()!
class PrettyPrinter(Visitor):
def Visit(self, node):
if node.tag == arith_expr_e.ArithUnary:
print('ArithUnary %s' % node.child)
else:
self.VisitChildren(node)
def t_parse(s, expected=None):
p = arith_parse.MakeParser(s)
tree = p.Parse()
print(tree)
#v = PrettyPrinter()
#v.Visit(tree)
#print('%-40s %s' % (s, sexpr))
return tree
def main():
p = arith_parse.MakeParser
TestArith(t_parse)
return
TestBitwise(t_parse)
TestLogical(t_parse)
TestUnary(t_parse)
TestArrays(t_parse)
TestFuncCalls(t_parse)
TestComma(t_parse)
TestErrors(p)
if __name__ == '__main__':
main()
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -0,0 +1,86 @@
#!/usr/bin/env python
from __future__ import print_function
"""
asdl_demo.py
"""
import sys
from asdl import asdl_ as asdl
from asdl import arith_parse
from asdl import py_meta
from asdl import encode
from asdl import format as fmt
from osh.meta import Id
from core import util
log = util.log
def main(argv):
try:
action = argv[1]
except IndexError:
raise RuntimeError('Action required')
if action == 'py': # Prints the module
schema_path = argv[2]
with open(schema_path) as f:
module = asdl.parse(f)
app_types = {'id': asdl.UserType(Id)}
type_lookup = asdl.ResolveTypes(module, app_types)
# Note this is a big tree. But we really want a graph of pointers to
# instances.
# Type(name, Product(...))
# Type(name, Sum([Constructor(...), ...]))
#print(module)
root = sys.modules[__name__]
# NOTE: We shouldn't pass in app_types for arith.asdl, but this is just a
# demo.
py_meta.MakeTypes(module, root, type_lookup)
log('Dynamically created a Python module with these types:')
for name in dir(root):
print('\t' + name)
elif action == 'arith-encode': # oheap encoding
expr = argv[2]
out_path = argv[3]
obj = arith_parse.ParseShell(expr)
print('Encoding %r into binary:' % expr)
print(obj)
enc = encode.Params()
with open(out_path, 'wb') as f:
out = encode.BinOutput(f)
encode.EncodeRoot(obj, enc, out)
elif action == 'arith-format': # pretty printing
expr = argv[2]
obj = arith_parse.ParseShell(expr)
#out = fmt.TextOutput(sys.stdout)
tree = fmt.MakeTree(obj)
#treee= ['hi', 'there', ['a', 'b'], 'c']
f = fmt.DetectConsoleOutput(sys.stdout)
fmt.PrintTree(tree, f)
print()
# Might need to print the output?
# out.WriteToFile?
else:
raise RuntimeError('Invalid action %r' % action)
if __name__ == '__main__':
try:
main(sys.argv)
except RuntimeError as e:
print('FATAL: %r' % e, file=sys.stderr)
sys.exit(1)
View
@@ -0,0 +1,36 @@
#!/usr/bin/env python
"""
const.py
"""
DEFAULT_INT_WIDTH = 3 # 24 bits
# 2^24 - 1 is used as an invalid/uninitialized value for ASDL integers.
# Why? We have a few use cases for invalid/sentinel values:
# - span_id, line_id. Sometimes we don't have a span ID.
# - file descriptor: 'read x < f.txt' vs 'read x 0< f.txt'
#
# Other options for representation:
#
# 1. ADSL could use signed integers, then -1 is valid.
# 2. Use a type like fd = None | Some(int fd)
#
# I don't like #1 because ASDL is lazily-decoded, and then we have to do sign
# extension on demand. (24 bits to 32 or 64). As far as I can tell, sign
# extension requires a branch, at least in portable C (on the sign bit).
#
# Thes second option is semantically cleaner. But it needlessly
# inflates the size of both the source code and the data. Instead of having a
# single "inline" integer, we would need a reference to another value.
#
# We could also try to do some fancy thing like fd = None |
# Range<1..max_fd>(fd), with smart encoding. But that is overkill for these
# use cases.
#
# Using InvalidInt instead of -1 seems like a good compromise.
NO_INTEGER = (1 << (DEFAULT_INT_WIDTH * 8)) - 1
# NOTE: In Python: 1 << (n * 8) - 1 is wrong! I thought that bit shift would
# have higher precedence.
View
@@ -0,0 +1,294 @@
"""
encode.py
"""
from asdl import asdl_ as asdl
from asdl import py_meta
from asdl import const
from core import util
class EncodeError(Exception):
def __init__(self, *args, **kwargs):
Exception.__init__(self, *args, **kwargs)
self.details_printed = False
_DEFAULT_ALIGNMENT = 4
class BinOutput(object):
"""Write aligned blocks here. Keeps track of block indexes for refs."""
def __init__(self, f, alignment=_DEFAULT_ALIGNMENT):
self.f = f
# index of last block, to return as a ref.
self.last_block = 0
self.alignment = alignment
def WriteRootRef(self, chunk):
self.f.seek(5) # seek past 'OHP\x01\x04'
assert len(chunk) == 3
self.f.write(chunk)
def Write(self, chunk):
"""
Return a block pointer/index.
"""
# Input should be padded
assert len(chunk) % self.alignment == 0
self.f.write(chunk)
ref = self.last_block
num_blocks = len(chunk) // self.alignment # int division
#print('WROTE %d blocks' % num_blocks)
self.last_block += num_blocks
# Return a reference to the beginning
return ref
class Params(object):
"""Encoding parameters.
Hm most of these settings should be per-field, expressed in the schema. The
only global one is the ref/pointer alignment. 4 and 8 are the most likely
choices, and 4 is probably fine, because you have 64 MB of addressable memory
with 24 bit pointers.
"""
def __init__(self, alignment=_DEFAULT_ALIGNMENT,
int_width=const.DEFAULT_INT_WIDTH):
self.alignment = alignment
self.pointer_type = 'uint32_t'
self.tag_width = 1 # for ArithVar vs ArithWord.
self.int_width = int_width
self.ref_width = int_width # Constant 3, used by gen_cpp
# used for fd, line/col
# also I guess steuff like SimpleCommand
self.index_width = 2 # 16 bits, e.g. max 64K entries in an array
self.max_int = 1 << (self.int_width * 8)
self.max_index = 1 << (self.index_width * 8)
self.max_tag = 1 << (self.tag_width * 8)
def Tag(self, i, chunk):
if i > self.max_tag:
raise AssertionError('Invalid id %r' % i)
chunk.append(i & 0xFF)
def Int(self, n, chunk):
if n < 0:
raise EncodeError(
"ASDL can't currently encode negative numbers. Got %d" % n)
if n > self.max_int:
raise EncodeError(
'%d is too big to fit in %d bytes' % (n, self.int_width))
for i in range(self.int_width):
chunk.append(n & 0xFF)
n >>= 8
def Ref(self, n, chunk):
# NOTE: ref width is currently the same as int width. Could be different.
self.Int(n, chunk)
def _Pad(self, chunk):
n = len(chunk)
a = self.alignment
if n % a != 0:
chunk.extend(b'\x00' * (a - (n % a)))
return chunk
# Right now all strings are references. Later they could be inline.
def Str(self, s, chunk):
# NOTE: For variable, proc, and function names, it could make sense to
# pre-compute and store a hash value. They will be looked up in the stack
# and so forth.
# - You could also return a obj number or object ID.
chunk.extend(s)
chunk.append(0) # NUL terminator
def PaddedStr(self, s):
# NOTE:
# - The encoder could also have an intern table to save space.
# - Str and PaddedStr will both return char* ? Should we allow them to
# VARY with the same schema, is a value/ref type PART of the schema? It's
# basically small size optimization and "flexible array" optimization. I
# think you want that possibility.
chunk = bytearray()
self.Str(s, chunk)
return self._Pad(chunk)
def Bytes(self, buf, chunk):
n = len(buf)
if n >= self.max_index:
raise EncodeError("bytes object is too long (%d)" % n)
for i in range(self.index_width):
chunk.append(n & 0xFF)
n >>= 8
chunk.extend(buf)
def PaddedBytes(self, buf):
chunk = bytearray()
self.Bytes(buf, chunk)
return self._Pad(chunk)
def PaddedBlock(self, chunk):
return self._Pad(chunk)
def EncodeArray(obj_list, item_desc, enc, out):
"""
Args:
obj_list: List of Obj values
Returns:
ref
"""
array_chunk = bytearray()
enc.Int(len(obj_list), array_chunk) # Length prefix
if isinstance(item_desc, asdl.IntType) or \
isinstance(item_desc, asdl.BoolType):
for item in obj_list:
enc.Int(item, array_chunk)
elif isinstance(item_desc, asdl.UserType):
# Assume Id for now
for item in obj_list:
enc.Int(item.enum_value, array_chunk)
elif isinstance(item_desc, asdl.StrType):
for item in obj_list:
ref = out.Write(enc.PaddedStr(item))
enc.Ref(ref, array_chunk)
elif isinstance(item_desc, asdl.Sum) and asdl.is_simple(item_desc):
for item in obj_list:
enc.Int(item.enum_id, array_chunk)
else:
# A simple value is either an int, enum, or pointer. (Later: Iter<Str>
# might be possible for locality.)
assert isinstance(item_desc, asdl.Sum) or \
isinstance(item_desc, asdl.Product), item_desc
# This is like vector<T*>
# Later:
# - Product types can be put in line
# - Sum types can even be put in line, if you have List<T> rather than
# Array<T>. Array implies O(1) random access; List doesn't.
for item in obj_list:
try:
ref = EncodeObj(item, enc, out)
except EncodeError as e:
if not e.details_printed:
util.log("Error encoding array: %s (item %s)", e, item)
e.details_printed = True
raise
enc.Ref(ref, array_chunk)
this_ref = out.Write(enc.PaddedBlock(array_chunk))
return this_ref
def EncodeObj(obj, enc, out):
"""
Args:
obj: Obj to encode
enc: encoding params
out: output file
Returns:
ref: Reference to the last block
"""
# Algorithm: Depth first, post-order traversal. First obj is the first leaf.
# last obj is the root.
#
# Array is a homogeneous type.
this_chunk = bytearray()
assert isinstance(obj, py_meta.CompoundObj), \
'%r is not a compound obj (%r)' % (obj, obj.__class__)
# Constructor objects have a tag.
if isinstance(obj.ASDL_TYPE, asdl.Constructor):
enc.Tag(obj.tag, this_chunk)
for name, desc in obj.ASDL_TYPE.GetFields(): # encode in order
field_val = getattr(obj, name)
# TODO:
# - Float would be inline, etc.
# - Repeated value: write them all adjacent to each other?
is_maybe = False
if isinstance(desc, asdl.MaybeType):
is_maybe = True
desc = desc.desc # descent
#
# Now look at types
#
if isinstance(desc, asdl.IntType) or isinstance(desc, asdl.BoolType):
enc.Int(field_val, this_chunk)
elif isinstance(desc, asdl.Sum) and asdl.is_simple(desc):
# Encode enums as integers. TODO later: Don't use 3 bytes! Can use 1
# byte for most enums.
enc.Int(field_val.enum_id, this_chunk)
# Write variable length field first, assuming that it's a ref/pointer.
# TODO: allow one inline, hanging string or array per record.
elif isinstance(desc, asdl.StrType):
ref = out.Write(enc.PaddedStr(field_val))
enc.Ref(ref, this_chunk)
elif isinstance(desc, asdl.ArrayType):
item_desc = desc.desc
ref = EncodeArray(field_val, item_desc, enc, out)
enc.Ref(ref, this_chunk)
elif isinstance(desc, asdl.UserType):
if is_maybe and field_val is None: # e.g. id? prefix_op
enc.Ref(0, this_chunk)
else:
# Assume Id for now
enc.Int(field_val.enum_value, this_chunk)
else:
if is_maybe and field_val is None:
enc.Ref(0, this_chunk)
else:
try:
ref = EncodeObj(field_val, enc, out)
except EncodeError as e:
if not e.details_printed:
util.log("Error encoding %s : %s (val %s)", name, e, field_val)
e.details_printed = True
raise
enc.Ref(ref, this_chunk)
# Write the parent record
this_ref = out.Write(enc.PaddedBlock(this_chunk))
return this_ref
def EncodeRoot(obj, enc, out):
ref = out.Write(b'OHP\x01') # header, version 1
assert ref == 0
# 4-byte alignment, then 3 byte placeholder for the root ref.
ref = out.Write(b'\4\0\0\0')
assert ref == 1
root_ref = EncodeObj(obj, enc, out)
chunk = bytearray()
enc.Ref(root_ref, chunk)
out.WriteRootRef(chunk) # back up and write it
#print("Root obj ref:", root_ref)
View
@@ -0,0 +1,37 @@
#!/usr/bin/env python
"""
encode_test.py: Tests for encode.py
"""
import unittest
from asdl import encode # module under test
from asdl import const
class EncoderTest(unittest.TestCase):
def testEncoder(self):
p = encode.Params(16)
chunk = bytearray()
p.Int(1, chunk)
self.assertEqual(b'\x01\x00\x00', chunk)
chunk = bytearray()
p.Int(const.NO_INTEGER, chunk)
self.assertEqual(b'\xff\xff\xff', chunk)
chunk = p.PaddedBytes('0123456789')
# 2 byte length -- max 64K entries
self.assertEqual(b'\x0A\x000123456789\x00\x00\x00\x00', bytes(chunk))
chunk = p.PaddedStr('0123456789')
# 2 byte length -- max 64K entries
self.assertEqual(b'0123456789\x00\x00\x00\x00\x00\x00', bytes(chunk))
#p.Block([b'a', b'bc'])
if __name__ == '__main__':
unittest.main()
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -0,0 +1,49 @@
#!/usr/bin/python -S
"""
format_test.py: Tests for format.py
"""
import cStringIO
import unittest
from asdl import format as fmt
from asdl import arith_ast # module under test
class FormatTest(unittest.TestCase):
def testSimpleSum(self):
node = arith_ast.op_id_e.Plus
print(node)
f = cStringIO.StringIO()
ast_f = fmt.TextOutput(f)
tree = fmt.MakeTree(node)
fmt.PrintTree(tree, ast_f)
# Hm this prints 'Plus'. Doesn't print the class or the number.
# But those aren't intrinsic. These are mostly used for ther IDENTITY.
# I think the ASDL_TYPE field contains the relevant info. Yes!
pretty_str = f.getvalue()
print(pretty_str)
def testRepeatedString(self):
node = arith_ast.assign('declare', ['-r', '-x'])
f = cStringIO.StringIO()
ast_f = fmt.TextOutput(f)
tree = fmt.MakeTree(node)
#print(tree)
fmt.PrintTree(tree, ast_f)
pretty_str = f.getvalue()
print(pretty_str)
self.assertEqual('(assign name:declare flags:[-r -x])', pretty_str)
if __name__ == '__main__':
unittest.main()
View
@@ -0,0 +1,399 @@
#!/usr/bin/env python
from __future__ import print_function
"""
asdl_cpp.py
Turn an ASDL schema into C++ code.
TODO:
- Optional fields
- in osh, it's only used in two places:
- arith_expr? for slice length
- word? for var replace
- So you're already using pointers, can encode the NULL pointer.
- Change everything to use references instead of pointers? Non-nullable.
- Unify ClassDefVisitor and MethodBodyVisitor.
- Whether you need a separate method body should be a flag.
- offset calculations are duplicated
- generate a C++ pretty-printer
Technically we don't even need alignment? I guess the reason is to increase
address space. If 1, then we have 16MiB of code. If 4, then we have 64 MiB.
Everything is decoded on the fly, or is a char*, which I don't think has to be
aligned (because the natural alignment woudl be 1 byte anyway.)
"""
import sys
from asdl import asdl_ as asdl
from asdl import encode
from asdl import visitor
from osh.meta import Id
class ChainOfVisitors:
def __init__(self, *visitors):
self.visitors = visitors
def VisitModule(self, module):
for v in self.visitors:
v.VisitModule(module)
_BUILTINS = {
'string': 'char*', # A read-only string is a char*
'int': 'int',
'bool': 'bool',
'id': 'Id', # Application specific hack for now
}
class ForwardDeclareVisitor(visitor.AsdlVisitor):
"""Print forward declarations.
ASDL allows forward references of types, but C++ doesn't.
"""
def VisitCompoundSum(self, sum, name, depth):
self.Emit("class %(name)s_t;" % locals(), depth)
def VisitProduct(self, product, name, depth):
self.Emit("class %(name)s_t;" % locals(), depth)
def EmitFooter(self):
self.Emit("", 0) # blank line
class ClassDefVisitor(visitor.AsdlVisitor):
"""Generate C++ classes and type-safe enums."""
def __init__(self, f, enc_params, type_lookup, enum_types=None):
visitor.AsdlVisitor.__init__(self, f)
self.ref_width = enc_params.ref_width
self.type_lookup = type_lookup
self.enum_types = enum_types or {}
self.pointer_type = enc_params.pointer_type
self.footer = [] # lines
def _GetCppType(self, field):
"""Return a string for the C++ name of the type."""
type_name = field.type
cpp_type = _BUILTINS.get(type_name)
if cpp_type is not None:
return cpp_type
typ = self.type_lookup.ByTypeName(type_name)
if isinstance(typ, asdl.Sum) and asdl.is_simple(typ):
# Use the enum instead of the class.
return "%s_e" % type_name
# - Pointer for optional type.
# - ints and strings should generally not be optional? We don't have them
# in osh yet, so leave it out for now.
if field.opt:
return "%s_t*" % type_name
return "%s_t&" % type_name
def EmitFooter(self):
for line in self.footer:
self.f.write(line)
def _EmitEnum(self, sum, name, depth):
enum = []
for i in range(len(sum.types)):
type = sum.types[i]
enum.append("%s = %d" % (type.name, i + 1)) # zero is reserved
self.Emit("enum class %s_e : uint8_t {" % name, depth)
self.Emit(", ".join(enum), depth + 1)
self.Emit("};", depth)
self.Emit("", depth)
def VisitSimpleSum(self, sum, name, depth):
self._EmitEnum(sum, name, depth)
def VisitCompoundSum(self, sum, name, depth):
# This is a sign that Python needs string interpolation!!!
def Emit(s, depth=depth):
self.Emit(s % sys._getframe(1).f_locals, depth)
self._EmitEnum(sum, name, depth)
Emit("class %(name)s_t : public Obj {")
Emit(" public:")
# All sum types have a tag
Emit("%(name)s_e tag() const {", depth + 1)
Emit("return static_cast<%(name)s_e>(bytes_[0]);", depth + 2)
Emit("}", depth + 1)
Emit("};")
Emit("")
# TODO: This should be replaced with a call to the generic
# self.VisitChildren()
super_name = "%s_t" % name
for t in sum.types:
self.VisitConstructor(t, super_name, depth)
# rudimentary attribute handling
for field in sum.attributes:
type = str(field.type)
assert type in asdl.builtin_types, type
Emit("%s %s;" % (type, field.name), depth + 1)
def VisitConstructor(self, cons, def_name, depth):
#print(dir(cons))
if cons.fields:
self.Emit("class %s : public %s {" % (cons.name, def_name), depth)
self.Emit(" public:", depth)
offset = 1 # for the ID
for f in cons.fields:
self.VisitField(f, cons.name, offset, depth + 1)
offset += self.ref_width
self.Emit("};", depth)
self.Emit("", depth)
def VisitProduct(self, product, name, depth):
self.Emit("class %(name)s_t : public Obj {" % locals(), depth)
self.Emit(" public:", depth)
offset = 0
for f in product.fields:
type_name = '%s_t' % name
self.VisitField(f, type_name, offset, depth + 1)
offset += self.ref_width
for field in product.attributes:
# rudimentary attribute handling
type = str(field.type)
assert type in asdl.builtin_types, type
self.Emit("%s %s;" % (type, field.name), depth + 1)
self.Emit("};", depth)
self.Emit("", depth)
def VisitField(self, field, type_name, offset, depth):
"""
Even though they are inline, some of them can't be in the class {}, because
static_cast<> requires inheritance relationships to be already declared. We
have to print all the classes first, then all the bodies that might use
static_cast<>.
http://stackoverflow.com/questions/5808758/why-is-a-static-cast-from-a-pointer-to-base-to-a-pointer-to-derived-invalid
"""
ctype = self._GetCppType(field)
name = field.name
pointer_type = self.pointer_type
# Either 'left' or 'BoolBinary::left', depending on whether it's inline.
# Mutated later.
maybe_qual_name = name
func_proto = None
func_header = None
body_line1 = None
inline_body = None
if field.seq: # Array/repeated
# For size accessor, follow the ref, and then it's the first integer.
size_header = (
'inline int %(name)s_size(const %(pointer_type)s* base) const {')
size_body = "return Ref(base, %(offset)d).Int(0);"
self.Emit(size_header % locals(), depth)
self.Emit(size_body % locals(), depth + 1)
self.Emit("}", depth)
ARRAY_OFFSET = 'int a = (index+1) * 3;'
A_POINTER = (
'inline const %(ctype)s %(maybe_qual_name)s('
'const %(pointer_type)s* base, int index) const')
if ctype in ('bool', 'int'):
func_header = A_POINTER + ' {'
body_line1 = ARRAY_OFFSET
inline_body = 'return Ref(base, %(offset)d).Int(a);'
elif ctype.endswith('_e') or ctype in self.enum_types:
func_header = A_POINTER + ' {'
body_line1 = ARRAY_OFFSET
inline_body = (
'return static_cast<const %(ctype)s>(Ref(base, %(offset)d).Int(a));')
elif ctype == 'char*':
func_header = A_POINTER + ' {'
body_line1 = ARRAY_OFFSET
inline_body = 'return Ref(base, %(offset)d).Str(base, a);'
else:
# Write function prototype now; write body later.
func_proto = A_POINTER + ';'
maybe_qual_name = '%s::%s' % (type_name, name)
func_def = A_POINTER + ' {'
# This static_cast<> (downcast) causes problems if put within "class
# {}".
func_body = (
'return static_cast<const %(ctype)s>('
'Ref(base, %(offset)d).Ref(base, a));')
self.footer.extend(visitor.FormatLines(func_def % locals(), 0))
self.footer.extend(visitor.FormatLines(ARRAY_OFFSET, 1))
self.footer.extend(visitor.FormatLines(func_body % locals(), 1))
self.footer.append('}\n\n')
maybe_qual_name = name # RESET for later
else: # not repeated
SIMPLE = "inline %(ctype)s %(maybe_qual_name)s() const {"
POINTER = (
'inline const %(ctype)s %(maybe_qual_name)s('
'const %(pointer_type)s* base) const')
if ctype in ('bool', 'int'):
func_header = SIMPLE
inline_body = 'return Int(%(offset)d);'
elif ctype.endswith('_e') or ctype in self.enum_types:
func_header = SIMPLE
inline_body = 'return static_cast<const %(ctype)s>(Int(%(offset)d));'
elif ctype == 'char*':
func_header = POINTER + " {"
inline_body = 'return Str(base, %(offset)d);'
else:
# Write function prototype now; write body later.
func_proto = POINTER + ";"
maybe_qual_name = '%s::%s' % (type_name, name)
func_def = POINTER + ' {'
if field.opt:
func_body = (
'return static_cast<const %(ctype)s>(Optional(base, %(offset)d));')
else:
func_body = (
'return static_cast<const %(ctype)s>(Ref(base, %(offset)d));')
# depth 0 for bodies
self.footer.extend(visitor.FormatLines(func_def % locals(), 0))
self.footer.extend(visitor.FormatLines(func_body % locals(), 1))
self.footer.append('}\n\n')
maybe_qual_name = name # RESET for later
if func_proto:
self.Emit(func_proto % locals(), depth)
else:
self.Emit(func_header % locals(), depth)
if body_line1:
self.Emit(body_line1, depth + 1)
self.Emit(inline_body % locals(), depth + 1)
self.Emit("}", depth)
# Used by osh/ast_gen.py
class CEnumVisitor(visitor.AsdlVisitor):
def VisitSimpleSum(self, sum, name, depth):
# Just use #define, since enums aren't namespaced.
for i, variant in enumerate(sum.types):
self.Emit('#define %s__%s %d' % (name, variant.name, i + 1), depth)
self.Emit("", depth)
def main(argv):
try:
action = argv[1]
except IndexError:
raise RuntimeError('Action required')
# TODO: Also generate a switch/static_cast<> pretty printer in C++! For
# debugging. Might need to detect cycles though.
if action == 'cpp':
schema_path = argv[2]
app_types = {'id': asdl.UserType(Id)}
with open(schema_path) as input_f:
module, type_lookup = asdl.LoadSchema(input_f, app_types)
# TODO: gen_cpp.py should be a library and the application should add Id?
# Or we should enable ASDL metaprogramming, and let Id be a metaprogrammed
# simple sum type.
f = sys.stdout
# How do mutation of strings, arrays, etc. work? Are they like C++
# containers, or their own? I think they mirror the oil language
# semantics.
# Every node should have a mirror. MutableObj. MutableRef (pointer).
# MutableArithVar -- has std::string. The mirrors are heap allocated.
# All the mutable ones should support Dump()/Encode()?
# You can just write more at the end... don't need to disturb existing
# nodes? Rewrite pointers.
alignment = 4
enc = encode.Params(alignment)
d = {'pointer_type': enc.pointer_type}
f.write("""\
#include <cstdint>
class Obj {
public:
// Decode a 3 byte integer from little endian
inline int Int(int n) const;
inline const Obj& Ref(const %(pointer_type)s* base, int n) const;
inline const Obj* Optional(const %(pointer_type)s* base, int n) const;
// NUL-terminated
inline const char* Str(const %(pointer_type)s* base, int n) const;
protected:
uint8_t bytes_[1]; // first is ID; rest are a payload
};
""" % d)
# Id should be treated as an enum.
c = ChainOfVisitors(
ForwardDeclareVisitor(f),
ClassDefVisitor(f, enc, type_lookup, enum_types=['Id']))
c.VisitModule(module)
f.write("""\
inline int Obj::Int(int n) const {
return bytes_[n] + (bytes_[n+1] << 8) + (bytes_[n+2] << 16);
}
inline const Obj& Obj::Ref(const %(pointer_type)s* base, int n) const {
int offset = Int(n);
return reinterpret_cast<const Obj&>(base[offset]);
}
inline const Obj* Obj::Optional(const %(pointer_type)s* base, int n) const {
int offset = Int(n);
if (offset) {
return reinterpret_cast<const Obj*>(base + offset);
} else {
return nullptr;
}
}
inline const char* Obj::Str(const %(pointer_type)s* base, int n) const {
int offset = Int(n);
return reinterpret_cast<const char*>(base + offset);
}
""" % d)
# uint32_t* and char*/Obj* aren't related, so we need to use
# reinterpret_cast<>.
# http://stackoverflow.com/questions/10151834/why-cant-i-static-cast-between-char-and-unsigned-char
else:
raise RuntimeError('Invalid action %r' % action)
if __name__ == '__main__':
try:
main(sys.argv)
except RuntimeError as e:
print('FATAL: %s' % e, file=sys.stderr)
sys.exit(1)
View
@@ -0,0 +1,135 @@
#!/usr/bin/env python
"""
gen_python.py
Generate Python code from and ASDL schema.
TODO:
- What about Id? app_types?
"""
import sys
from asdl import asdl_ as asdl
from asdl import visitor
class GenClassesVisitor(visitor.AsdlVisitor):
def VisitSimpleSum(self, sum, name, depth):
self.Emit('class %s_e(py_meta.SimpleObj):' % name, depth)
self.Emit(' ASDL_TYPE = TYPE_LOOKUP.ByTypeName(%r)' % name, depth)
self.Emit('', depth)
# Just use #define, since enums aren't namespaced.
for i, variant in enumerate(sum.types):
attr = '%s_e.%s = %s_e(%d, %r)' % (
name, variant.name, name, i + 1, variant.name)
self.Emit(attr, depth)
self.Emit('', depth)
def _GenClass(self, desc, name, super_name, depth, tag_num=None):
self.Emit('class %s(%s):' % (name, super_name), depth)
if tag_num is not None:
self.Emit(' tag = %d' % tag_num, depth)
field_names = [f.name for f in desc.fields]
quoted_fields = repr(tuple(field_names))
# NOTE: FIELDS is a duplicate of __slots__, used for pretty printing and
# oheap serialization. TODO: measure the effect of __slots__, and then get
# rid of FIELDS? Or you can just make it an alias.
# FIELDS = self.__slots__.
self.Emit(' ASDL_TYPE = TYPE_LOOKUP.ByTypeName(%r)' % name, depth)
self.Emit(' __slots__ = %s' % quoted_fields, depth)
self.Emit('', depth)
# TODO: leave out spids? Mark it as an attribute?
args = ', '.join('%s=None' % f.name for f in desc.fields)
self.Emit(' def __init__(self, %s):' % args, depth)
for f in desc.fields:
# This logic is like _MakeFieldDescriptors
default = None
if f.opt: # Maybe
if f.type == 'int':
default = 'const.NO_INTEGER'
elif f.type == 'string':
default = "''"
else:
default = 'None'
elif f.seq: # Array
default = '[]'
# PROBLEM: Optional ints can't be zero!
# self.span_id = span_id or const.NO_INTEGER
# I don't want to add if statements checking against None?
# For now don't use optional ints. We don't need it.
default_str = (' or %s' % default) if default else ''
self.Emit(' self.%s = %s%s' % (f.name, f.name, default_str), depth)
self.Emit('', depth)
def VisitConstructor(self, cons, def_name, tag_num, depth):
if cons.fields:
self._GenClass(cons, cons.name, def_name, depth, tag_num=tag_num)
else:
self.Emit("class %s(%s):" % (cons.name, def_name), depth)
self.Emit(' ASDL_TYPE = TYPE_LOOKUP.ByTypeName(%r)' % cons.name, depth)
self.Emit(' tag = %d' % tag_num, depth)
self.Emit('', depth)
def VisitCompoundSum(self, sum, name, depth):
# define command_e
self.Emit('class %s_e(object):' % name, depth)
for i, variant in enumerate(sum.types):
self.Emit(' %s = %d' % (variant.name, i + 1), depth)
self.Emit('', depth)
self.Emit('class %s(py_meta.CompoundObj):' % name, depth)
self.Emit(' ASDL_TYPE = TYPE_LOOKUP.ByTypeName(%r)' % name, depth)
self.Emit('', depth)
# define command_t, and then make subclasses
super_name = '%s' % name
for i, t in enumerate(sum.types):
tag_num = i + 1
self.VisitConstructor(t, super_name, tag_num, depth)
def VisitProduct(self, product, name, depth):
self._GenClass(product, name, 'py_meta.CompoundObj', depth)
def EmitFooter(self):
pass
def main(argv):
schema_path = argv[1]
type_lookup_import = argv[2]
with open(schema_path) as input_f:
module = asdl.parse(input_f)
f = sys.stdout
f.write("""\
from asdl import const # For const.NO_INTEGER
from asdl import py_meta
%s
""" % type_lookup_import)
v = GenClassesVisitor(f)
v.VisitModule(module)
if __name__ == '__main__':
try:
main(sys.argv)
except RuntimeError as e:
print >>sys.stderr, 'FATAL: %s' % e
sys.exit(1)
Oops, something went wrong.