Skip to content

Commit 863afaa

Browse files
committed
Implement strings with concatenation and single-character slicing.
1 parent e5723e2 commit 863afaa

8 files changed

Lines changed: 499 additions & 0 deletions

File tree

nativepython/native_ast.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,11 +380,15 @@ def expr_is_simple(expr):
380380
__rshift__ = expr_concatenate,
381381
__str__ = expr_str,
382382
structElt = lambda self, ix: Expression.StructElementByIndex(left=self,index=ix),
383+
negate = lambda self: Expression.Unaryop(op=UnaryOp.Negate(), operand=self),
383384
sub = lambda self, other: Expression.Binop(op=BinaryOp.Sub(), l=self,r=ensureExpr(other)),
384385
add = lambda self, other: Expression.Binop(op=BinaryOp.Add(), l=self,r=ensureExpr(other)),
385386
mul = lambda self, other: Expression.Binop(op=BinaryOp.Mul(), l=self,r=ensureExpr(other)),
386387
eq = lambda self, other: Expression.Binop(op=BinaryOp.Eq(), l=self,r=ensureExpr(other)),
387388
lt = lambda self, other: Expression.Binop(op=BinaryOp.Lt(), l=self,r=ensureExpr(other)),
389+
gt = lambda self, other: Expression.Binop(op=BinaryOp.Gt(), l=self,r=ensureExpr(other)),
390+
lte = lambda self, other: Expression.Binop(op=BinaryOp.LtE(), l=self,r=ensureExpr(other)),
391+
gte = lambda self, other: Expression.Binop(op=BinaryOp.GtE(), l=self,r=ensureExpr(other)),
388392
lshift = lambda self, other: Expression.Binop(op=BinaryOp.LShift(), l=self,r=ensureExpr(other)),
389393
rshift = lambda self, other: Expression.Binop(op=BinaryOp.RShift(), l=self,r=ensureExpr(other)),
390394
bitand = lambda self, other: Expression.Binop(op=BinaryOp.BitAnd(), l=self,r=ensureExpr(other)),
@@ -453,6 +457,7 @@ def const_utf8_cstr(i):
453457
)
454458

455459
Void = Type.Void()
460+
VoidPtr = Void.pointer()
456461
Bool = Type.Int(bits=1, signed=False)
457462
UInt8 = Type.Int(bits=8, signed=False)
458463
UInt8Ptr = UInt8.pointer()

nativepython/python_object_representation.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from nativepython.type_wrappers.class_wrapper import ClassWrapper, BoundMethodWrapper
1111
from nativepython.type_wrappers.len_wrapper import LenWrapper
1212
from nativepython.type_wrappers.arithmetic_wrapper import Int64Wrapper, Float64Wrapper, BoolWrapper
13+
from nativepython.type_wrappers.string_wrapper import StringWrapper
1314
from nativepython.type_wrappers.python_object_of_type_wrapper import PythonObjectOfTypeWrapper
1415
from typed_python._types import TypeFor
1516
from typed_python import *
@@ -41,6 +42,9 @@ def _typedPythonTypeToTypeWrapper(t):
4142
if t is NoneType():
4243
return NoneWrapper()
4344

45+
if t is String():
46+
return StringWrapper()
47+
4448
if t.__typed_python_category__ == "Class":
4549
return ClassWrapper(t)
4650

@@ -101,6 +105,9 @@ def pythonObjectRepresentation(context, f):
101105
Float64Wrapper(),
102106
False
103107
)
108+
if isinstance(f, str):
109+
return StringWrapper().constant(context, f)
110+
104111
if isinstance(f, type(pythonObjectRepresentation)):
105112
return TypedExpression(
106113
context,
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright 2018 Braxton Mckee
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typed_python import *
16+
import typed_python._types as _types
17+
from nativepython.runtime import Runtime
18+
import unittest
19+
import time
20+
import psutil
21+
22+
def Compiled(f):
23+
f = TypedFunction(f)
24+
return Runtime.singleton().compile(f)
25+
26+
27+
someStrings = [
28+
"",
29+
"a",
30+
"as\x00df",
31+
"\u00F1",
32+
"\u0F01",
33+
"\u0F01",
34+
"\u1002"
35+
]
36+
37+
for s1 in list(someStrings):
38+
for s2 in list(someStrings):
39+
someStrings.append(s1+s2)
40+
someStrings = sorted(set(someStrings))
41+
42+
class TestStringCompilation(unittest.TestCase):
43+
def test_string_passing_and_refcounting(self):
44+
@Compiled
45+
def takeFirst(x: str, y: str):
46+
return x
47+
48+
@Compiled
49+
def takeSecond(x: str, y: str):
50+
return y
51+
52+
for s in someStrings:
53+
for s2 in someStrings:
54+
self.assertEqual(s, takeFirst(s,s2))
55+
self.assertEqual(s2, takeSecond(s,s2))
56+
57+
def test_string_len(self):
58+
@Compiled
59+
def compiledLen(x: str):
60+
return len(x)
61+
62+
for s in someStrings:
63+
self.assertEqual(len(s), compiledLen(s))
64+
65+
def test_string_concatenation(self):
66+
@Compiled
67+
def concat(x: str, y: str):
68+
return x + y
69+
70+
@Compiled
71+
def concatLen(x: str, y: str):
72+
return len(x + y)
73+
74+
for s in someStrings:
75+
for s2 in someStrings:
76+
self.assertEqual(s+s2, concat(s,s2))
77+
self.assertEqual(len(s+s2), concatLen(s,s2))
78+
79+
def test_string_constants(self):
80+
def makeConstantConcatenator(s):
81+
def returner():
82+
return s
83+
return returner
84+
85+
for s in someStrings:
86+
f = Compiled(makeConstantConcatenator(s))
87+
s_from_code = f()
88+
89+
self.assertEqual(s, s_from_code, (repr(s),repr(s_from_code)))
90+
91+
def test_string_getitem(self):
92+
@Compiled
93+
def getitem(x: str, y: int):
94+
return x[y]
95+
96+
def callOrExcept(f, *args):
97+
try:
98+
return ("Normal", f(*args))
99+
except Exception as e:
100+
return ("Exception", str(e))
101+
102+
for s in someStrings:
103+
for i in range(-20, 20):
104+
self.assertEqual(callOrExcept(getitem, s,i), callOrExcept(lambda s,i: s[i], s, i), (s,i))
105+

nativepython/type_wrappers/runtime_functions.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,24 @@ def externalCallTarget(name, output, *inputs):
9595
Int64,
9696
Void.pointer()
9797
)
98+
99+
string_concat = externalCallTarget(
100+
"nativepython_runtime_string_concat",
101+
Void.pointer(),
102+
Void.pointer(), Void.pointer()
103+
)
104+
string_getitem_int64 = externalCallTarget(
105+
"nativepython_runtime_string_getitem_int64",
106+
Void.pointer(),
107+
Void.pointer(), Int64
108+
)
109+
string_from_utf8_and_len = externalCallTarget(
110+
"nativepython_runtime_string_from_utf8_and_len",
111+
Void.pointer(),
112+
UInt8Ptr, Int64
113+
)
114+
destroy_string = externalCallTarget(
115+
"nativepython_runtime_destroy_string",
116+
Void,
117+
Void.pointer()
118+
)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2018 Braxton Mckee
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from nativepython.type_wrappers.refcounted_wrapper import RefcountedWrapper
16+
from nativepython.typed_expression import TypedExpression
17+
from nativepython.type_wrappers.exceptions import generateThrowException
18+
import nativepython.type_wrappers.runtime_functions as runtime_functions
19+
20+
from typed_python import NoneType, Int64, String
21+
22+
import nativepython.native_ast as native_ast
23+
import nativepython
24+
25+
from nativepython.native_ast import VoidPtr
26+
27+
typeWrapper = lambda t: nativepython.python_object_representation.typedPythonTypeToTypeWrapper(t)
28+
29+
class StringWrapper(RefcountedWrapper):
30+
is_pod = False
31+
is_empty = False
32+
is_pass_by_ref = True
33+
34+
def __init__(self):
35+
super().__init__(String())
36+
37+
self.layoutType = native_ast.Type.Struct(element_types=(
38+
('refcount', native_ast.Int64),
39+
('data', native_ast.UInt8)
40+
), name='StringLayout').pointer()
41+
42+
def getNativeLayoutType(self):
43+
return self.layoutType
44+
45+
def on_refcount_zero(self, context, instance):
46+
assert instance.isReference
47+
return runtime_functions.destroy_string.call(instance.nonref_expr.cast(native_ast.VoidPtr))
48+
49+
def convert_bin_op(self, context, left, op, right):
50+
if right.expr_type == left.expr_type:
51+
if op.matches.Add:
52+
return context.push(str, lambda strRef:
53+
strRef.expr.store(
54+
runtime_functions.string_concat.call(
55+
left.nonref_expr.cast(VoidPtr),
56+
right.nonref_expr.cast(VoidPtr)
57+
).cast(self.layoutType)
58+
)
59+
)
60+
61+
return super().convert_bin_op(context, left, op, right)
62+
63+
def convert_getitem(self, context, expr, item):
64+
item = item.toInt64()
65+
66+
len_expr = self.convert_len(context, expr)
67+
68+
with context.ifelse((item.nonref_expr.lt(len_expr.nonref_expr.negate())).bitor(item.nonref_expr.gte(len_expr.nonref_expr))) as (true,false):
69+
with true:
70+
context.pushException(IndexError, "string index out of range")
71+
72+
return context.push(str, lambda strRef:
73+
strRef.expr.store(
74+
runtime_functions.string_getitem_int64.call(expr.nonref_expr.cast(native_ast.VoidPtr), item.nonref_expr)
75+
.cast(self.layoutType)
76+
)
77+
)
78+
79+
def convert_len_native(self, expr):
80+
return native_ast.Expression.Branch(
81+
cond=expr,
82+
false=native_ast.const_int_expr(0),
83+
true=expr.ElementPtrIntegers(0,1).ElementPtrIntegers(4).cast(native_ast.Int32.pointer()).load().cast(native_ast.Int64)
84+
)
85+
86+
def convert_len(self, context, expr):
87+
return context.pushPod(int, self.convert_len_native(expr.nonref_expr))
88+
89+
def constant(self, context, s):
90+
return context.push(str, lambda strRef:
91+
strRef.expr.store(
92+
runtime_functions.string_from_utf8_and_len.call(
93+
native_ast.const_utf8_cstr(s),
94+
native_ast.const_int_expr(len(s))
95+
).cast(self.layoutType)
96+
)
97+
)
98+
99+
100+

0 commit comments

Comments
 (0)