[jsinterp] Implementing String split

This commit is contained in:
sulyi 2018-06-09 10:24:54 +02:00
parent 327bb2dd86
commit f9f030a005
9 changed files with 44 additions and 18 deletions

View File

@ -39,8 +39,6 @@ tests = [
])
]
}, {
# FIXME built-in functions are not yet implemented
'exclude': ('jsinterp2',),
'code': 'function x(a) { return a.split(""); }',
'asserts': [{'value': ["a", "b", "c"], 'call': ('x', "abc")}],
'ast': [

View File

@ -2,7 +2,6 @@ from __future__ import unicode_literals
skip = {
'jsinterp': 'String literals are not supported',
'interpret': 'Builtins are not yet implemented',
'parse': 'Test is not yet implemented: missing ast'
}

View File

@ -22,7 +22,7 @@ __doc__ = """see: `js2tests`"""
defs = gettestcases()
# set level to logging.DEBUG to see messages about missing assertions
# set level to logging.DEBUG to see messages about code tests are running
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
logging.basicConfig(stream=sys.stderr, level=logging.WARNING)
log = logging.getLogger('TestJSInterpreter')

View File

@ -22,7 +22,7 @@ __doc__ = """see: `js2tests`"""
defs = gettestcases()
# set level to logging.DEBUG to see messages about missing assertions
# set level to logging.DEBUG to see messages about code tests are running
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
logging.basicConfig(stream=sys.stderr, level=logging.WARNING)
log = logging.getLogger('TestJSInterpreter2')

View File

@ -53,5 +53,9 @@ global_obj = jsobject.JSObject.construct(
'Array': jsarray.JSArray(),
'Function': jsfunction.JSFunction(),
'String': jsstring.JSString(),
'Number': jsnumber.JSNumber()
'Number': jsnumber.JSNumber(),
'false': jsboolean.false,
'true': jsboolean.true,
'null': base.null,
'undefined': base.undefined
})

View File

@ -24,10 +24,10 @@ class JSProtoBase(JSBase):
super(JSProtoBase, self).__init__('')
cls = self.__class__
while cls.__base__ is not JSProtoBase:
cls = cls.__base__
props = cls.own.copy()
props.update(self.props)
self.props = props
cls = cls.__base__
self.value = {}
def get_prop(self, prop):
@ -63,3 +63,8 @@ native_number = (int, float)
native_object = dict
native_array = (list, tuple)
native_function = FunctionType
def isprimitive(value):
return (isinstance(value, (native_bool, native_string, native_number, native_object, native_array, native_function))
or value is None)

View File

@ -60,7 +60,9 @@ class JSStringPrototype(JSObjectPrototype):
return 'string slice'
def _split(self, sep):
return 'string split'
if sep == '':
return list(self.value)
return self.value.split(sep)
def _substring(self, start, end):
return 'string substring'

View File

@ -6,6 +6,10 @@ from ..compat import compat_str
from ..utils import ExtractorError
from .jsparser import Parser
from .jsgrammar import Token, token_keys
from .jsbuilt_ins import global_obj
from .jsbuilt_ins.base import isprimitive
from .jsbuilt_ins.internals import to_string
from .jsbuilt_ins.utils import to_js
class Context(object):
@ -31,7 +35,7 @@ class Reference(object):
if deep:
if isinstance(self._value, (list, tuple)):
# TODO test nested arrays
value = [elem.getvalue() for elem in self._value]
value = [elem if isprimitive(elem) else elem.getvalue() for elem in self._value]
elif isinstance(self._value, dict):
value = {}
for key, prop in self._value.items():
@ -60,8 +64,6 @@ class Reference(object):
class JSInterpreter(object):
# TODO support json
undefined = object()
def __init__(self, code, variables=None):
super(JSInterpreter, self).__init__()
self.code = code
@ -116,7 +118,8 @@ class JSInterpreter(object):
ref = s.getvalue()
elif name is Token.VAR:
for name, value in stmt[1]:
value = self.interpret_expression(value).getvalue() if value is not None else self.undefined
value = (self.interpret_expression(value).getvalue() if value is not None else
global_obj.get_prop('undefined'))
self.this[name] = Reference(value, (self.this, name))
elif name is Token.EXPR:
for expr in stmt[1]:
@ -158,7 +161,7 @@ class JSInterpreter(object):
if lid[0] is Token.ID and args is None and tail is None:
key = lid[1]
if key is not None:
u = Reference(self.undefined, (self.this, key))
u = Reference(global_obj.get_prop('undefined'), (self.this, key))
leftref = self.this[key] = u
else:
raise ExtractorError('Invalid left-hand side in assignment')
@ -209,16 +212,31 @@ class JSInterpreter(object):
if args is not None:
# TODO interpret NewExpression
pass
source = None
while tail is not None:
tail_name, tail_value, tail = tail
if tail_name is Token.FIELD:
target = target.getvalue()[tail_value]
source = to_js(target.getvalue())
target = source.get_prop(tail_value)
elif tail_name is Token.ELEM:
index = self.interpret_expression(tail_value).getvalue()
target = target.getvalue()[index]
prop = self.interpret_expression(tail_value).getvalue()
target = to_js(target.getvalue()).get_prop(to_string(to_js(prop)))
elif tail_name is Token.CALL:
args = (self.interpret_expression(arg).getvalue() for arg in tail_value)
target = Reference(target.getvalue()(*args))
if isprimitive(target):
if source is None:
target = target(*args)
else:
target = target(source, *args)
else:
if source is None:
target = target.getvalue()(*args)
else:
target = target.getvalue()(source, *args)
if isprimitive(target):
target = Reference(target)
else:
target = Reference(target.getvalue())
ref = target
elif name is Token.ID:

View File

@ -440,7 +440,7 @@ class Parser(object):
# Rhino has check for args length
# Rhino has experimental syntax allowing an object literal to follow a new expression
else:
target = self._primary_expression(stack_top)
target = self._primary_expression(stack_top - 1)
args = None
return (Token.MEMBER, target, args, self._member_tail(stack_top - 1))