[jsinterp] Adding interpreter support for pre- and postfix expressions

This commit is contained in:
sulyi 2016-12-15 20:02:04 +01:00
parent 0e4dd1ac77
commit d7443e1233
12 changed files with 53 additions and 33 deletions

View File

@ -5,6 +5,7 @@ from . import (
morespace,
strange_chars,
operators,
unary,
array_access,
parens,
assignments,
@ -29,15 +30,15 @@ from . import (
)
modules = [basic, calc, empty_return, morespace, strange_chars, operators, array_access, parens, assignments, comments,
precedence, call, getfield, branch, switch, for_loop, for_empty, for_in, do_loop, while_loop, label,
func_expr, object_literal, try_statement, with_statement, debug, unshift]
modules = [basic, calc, empty_return, morespace, strange_chars, operators, unary, array_access, parens, assignments,
comments, precedence, call, getfield, branch, switch, for_loop, for_empty, for_in, do_loop, while_loop,
label, func_expr, object_literal, try_statement, with_statement, debug, unshift]
def gettestcases():
for module in modules:
if hasattr(module, 'tests'):
case = {'name': module.__name__[len(__name__) + 1:], 'subtests': []}
case = {'name': module.__name__[len(__name__) + 1:], 'subtests': [], 'skip': {}}
for test in getattr(module, 'tests'):
if 'code' in test:
case['subtests'].append(test)

View File

@ -35,7 +35,7 @@ tests = [
(Token.EXPR, [
(Token.ASSIGN, None, (Token.OPEXPR, [
(Token.MEMBER, (Token.ID, 'i'), None, None),
(Token.UOP, _UNARY_OPERATORS['++'][1])
(Token.POSTFIX, _UNARY_OPERATORS['++'][1])
]), None)
])
])),

View File

@ -30,7 +30,7 @@ tests = [
]), None)]),
(Token.EXPR, [(Token.ASSIGN, None, (Token.OPEXPR, [
(Token.MEMBER, (Token.ID, 'h'), None, None),
(Token.UOP, _UNARY_OPERATORS['++'][1])
(Token.PREFIX, _UNARY_OPERATORS['++'][1])
]), None)]),
(Token.BLOCK, [
(Token.EXPR, [

View File

@ -28,7 +28,7 @@ tests = [
]), None)]),
(Token.EXPR, [(Token.ASSIGN, None, (Token.OPEXPR, [
(Token.MEMBER, (Token.ID, 'h'), None, None),
(Token.UOP, _UNARY_OPERATORS['++'][1])
(Token.PREFIX, _UNARY_OPERATORS['++'][1])
]), None)]),
(Token.BLOCK, [
(Token.EXPR, [

View File

@ -39,16 +39,5 @@ tests = [
(Token.OP, _OPERATORS['>>'][1])
]), None)
]))]
}, {
'code': 'return -5 + +3;',
'asserts': [{'value': -2}]
}, {
'code': 'return -5 + ++a;',
'globals': {'a': -3},
'asserts': [{'value': -7}]
}, {
'code': 'function f() {return -5 + a++;}',
'globals': {'a': -3},
'asserts': [{'value': -8, 'call': ('f',)}, {'value': -7, 'call': ('f',)}]
}
]

View File

@ -42,7 +42,7 @@ tests = [
[
(Token.EXPR, [(Token.ASSIGN, None, (Token.OPEXPR, [
(Token.MEMBER, (Token.ID, 'x'), None, None),
(Token.UOP, _UNARY_OPERATORS['++'][1])
(Token.POSTFIX, _UNARY_OPERATORS['++'][1])
]), None)])
]),
((Token.EXPR, [(Token.ASSIGN, None, (Token.OPEXPR, [
@ -50,7 +50,7 @@ tests = [
[
(Token.EXPR, [(Token.ASSIGN, None, (Token.OPEXPR, [
(Token.MEMBER, (Token.ID, 'x'), None, None),
(Token.UOP, _UNARY_OPERATORS['--'][1])
(Token.POSTFIX, _UNARY_OPERATORS['--'][1])
]), None)]),
(Token.BREAK, None)
]),

17
test/jstests/unary.py Normal file
View File

@ -0,0 +1,17 @@
skip = {'p': True}
tests = [
{
'code': 'return -5 + +3;',
'asserts': [{'value': -2}]
}, {
'code': 'function f() {return -5 + ++a;}',
'globals': {'a': -3},
'asserts': [{'value': -7, 'call': ('f',)}, {'value': -6, 'call': ('f',)}]
}, {
'code': 'function f() {return -5 + a++;}',
'globals': {'a': -3},
'asserts': [{'value': -8, 'call': ('f',)}, {'value': -7, 'call': ('f',)}]
}
]

View File

@ -35,7 +35,7 @@ tests = [
(Token.EXPR, [
(Token.ASSIGN, None, (Token.OPEXPR, [
(Token.MEMBER, (Token.ID, 'i'), None, None),
(Token.UOP, _UNARY_OPERATORS['++'][1])
(Token.POSTFIX, _UNARY_OPERATORS['++'][1])
]), None)
])
])),

View File

@ -33,7 +33,7 @@ def generator(test_case):
else:
self.assertEqual(jsi.run(), a['value'])
if 'skip' not in test_case or 'i' not in test_case['skip']:
if 'i' not in test_case['skip']:
reason = False
else:
reason = test_case['skip']['i']

View File

@ -44,7 +44,7 @@ def generator(test_case):
if 'ast' in a:
self.assertEqual(traverse(parsed), traverse(a['ast']))
if 'skip' not in test_case or 'p' not in test_case['skip']:
if 'p' not in test_case['skip']:
reason = False
else:
reason = test_case['skip']['p']
@ -54,12 +54,13 @@ def generator(test_case):
# And add them to TestJSInterpreter
for n, tc in enumerate(defs):
test_method = generator(tc)
tname = 'test_' + str(tc['name'])
i = 1
while hasattr(TestJSInterpreterParse, tname):
tname = 'test_%s_%d' % (tc['name'], i)
i += 1
test_method.__name__ = str(tname)
setattr(TestJSInterpreterParse, test_method.__name__, test_method)
del test_method
if 'p' not in tc['skip'] or tc['skip']['p'] is not True:
test_method = generator(tc)
tname = 'test_' + str(tc['name'])
i = 1
while hasattr(TestJSInterpreterParse, tname):
tname = 'test_%s_%d' % (tc['name'], i)
i += 1
test_method.__name__ = str(tname)
setattr(TestJSInterpreterParse, test_method.__name__, test_method)
del test_method

View File

@ -9,7 +9,7 @@ _token_keys = ('COPEN', 'CCLOSE', 'POPEN', 'PCLOSE', 'SOPEN', 'SCLOSE',
'AND', 'OR', 'PLUS', 'NEG', 'INC', 'DEC', 'NOT', 'BNOT', 'DEL', 'VOID', 'TYPE',
'LT', 'GT', 'LE', 'GE', 'EQ', 'NE', 'SEQ', 'SNE', 'IN', 'INSTANCEOF',
'BOR', 'BXOR', 'BAND', 'RSHIFT', 'LSHIFT', 'URSHIFT', 'SUB', 'ADD', 'MOD', 'DIV', 'MUL',
'OP', 'AOP', 'UOP', 'LOP', 'REL',
'OP', 'AOP', 'UOP', 'LOP', 'REL', 'PREFIX', 'POSTFIX',
'COMMENT', 'TOKEN', 'PUNCT',
'NULL', 'BOOL', 'ID', 'STR', 'INT', 'FLOAT', 'REGEX', 'OBJECT',
'REFLAGS', 'REBODY',

View File

@ -33,6 +33,7 @@ class Reference(object):
if not hasattr(parent, '__setitem__'):
raise ExtractorError('Unknown reference')
parent.__setitem__(key, Reference(value, (parent, key)))
return value
def __repr__(self):
if self._parent is not None:
@ -748,6 +749,8 @@ class JSInterpreter(object):
if peek_id is Token.UOP:
name, op = peek_value
had_inc = name in (Token.INC, Token.DEC)
if had_inc:
peek_id = Token.PREFIX
while stack and stack[-1][0] > 16:
_, stack_id, stack_op = stack.pop()
out.append((stack_id, stack_op))
@ -770,6 +773,7 @@ class JSInterpreter(object):
raise ExtractorError('''Can't have prefix and postfix operator at the same time at %d''' % peek_pos)
name, op = peek_value
if name in (Token.INC, Token.DEC):
peek_id = Token.POSTFIX
prec = 17
else:
raise ExtractorError('Unexpected operator at %d' % peek_pos)
@ -880,6 +884,7 @@ class JSInterpreter(object):
elif name is Token.OPEXPR:
stack = []
postfix = []
rpn = expr[1][:]
# FIXME support pre- and postfix operators
while rpn:
@ -893,10 +898,17 @@ class JSInterpreter(object):
elif token[0] is Token.UOP:
right = stack.pop()
stack.append(Reference(token[1](right.getvalue())))
elif token[0] is Token.PREFIX:
right = stack.pop()
stack.append(Reference(right.putvalue(token[1](right.getvalue()))))
elif token[0] is Token.POSTFIX:
postfix.append((stack[-1], token[1]))
else:
stack.append(self.interpret_expression(token))
result = stack.pop()
if not stack:
for operand, op in postfix:
operand.putvalue(op(operand.getvalue()))
ref = result
else:
raise ExtractorError('Expression has too many values')