Skip to content

Commit 7b2e6ca

Browse files
committed
[GR-43581] Fix multiplication of foreign strings with numbers
PullRequest: graalpython/2613
2 parents f122947 + 1e57033 commit 7b2e6ca

File tree

11 files changed

+181
-85
lines changed

11 files changed

+181
-85
lines changed

docs/user/Interoperability.md

Lines changed: 82 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,77 +14,86 @@ In fact, GraalVM uses this API internally to execute Python C extensions using t
1414

1515
You can import the `polyglot` module to interact with other languages:
1616
```python
17-
import polyglot
17+
>>> import polyglot
18+
```
19+
20+
You can evaluate some inlined code from another language:
21+
```python
22+
>>> polyglot.eval(string="1 + 1", language="ruby")
23+
2
24+
```
25+
26+
You can evaluate some code from a file, by passing the path to it:
27+
```python
28+
>>> with open("./my_ruby_file.rb", "w") as f:
29+
... f.write("Polyglot.export('RubyPolyglot', Polyglot)")
30+
41
31+
>>> polyglot.eval(path="./my_ruby_file.rb", language="ruby")
32+
<foreign object at ...>
1833
```
1934

2035
You can import a global value from the entire polyglot scope:
2136
```python
22-
imported_polyglot_global = polyglot.import_value("global_name")
37+
>>> ruby_polyglot = polyglot.import_value("RubyPolyglot")
2338
```
2439

2540
This global value should then work as expected:
2641
* Accessing attributes assumes it reads from the `members` namespace.
27-
* Accessing items is supported both with strings and numbers.
28-
* Calling methods on the result tries to do a straight invoke and falls
29-
back to reading the member and trying to execute it.
30-
31-
You can evaluate some inlined code from another language:
3242
```python
33-
polyglot.eval(string="1 + 1", language="ruby")
43+
>>> ruby_polyglot.to_s
44+
<foreign object at ...>
3445
```
3546

36-
You can evaluate some code from a file, by passing the path to it:
47+
* Calling methods on the result tries to do a straight invoke and falls
48+
back to reading the member and trying to execute it.
3749
```python
38-
polyglot.eval(path="./my_ruby_file.rb", language="ruby")
50+
>>> ruby_polyglot.to_s()
51+
Polyglot
3952
```
4053

41-
If you pass a file, you can also rely on the file-based language detection:
54+
* Accessing items is supported both with strings and numbers.
4255
```python
43-
polyglot.eval(path="./my_ruby_file.rb")
56+
>>> ruby_polyglot.methods()[10] is not None
57+
True
4458
```
4559

46-
You can export some oblect from Python to other supported languages so they can import
60+
You can export some object from Python to other supported languages so they can import
4761
it:
4862
```python
49-
foo = object()
50-
polyglot.export_value(foo, name="python_foo")
63+
>>> foo = object()
64+
>>> polyglot.export_value(value=foo, name="python_foo")
65+
<object object at ...>
66+
>>> jsfoo = polyglot.eval(language="js", string="Polyglot.import('python_foo')")
67+
>>> jsfoo is foo
68+
True
5169
```
5270

5371
The export function can be used as a decorator.
5472
In this case the function name is used as the globally exported name:
5573
```python
56-
@polyglot.export_value
57-
def python_method():
58-
return "Hello from Python!"
74+
>>> @polyglot.export_value
75+
... def python_method():
76+
... return "Hello from Python!"
5977
```
6078

6179
Here is an example of how to use the JavaScript regular expression engine to
62-
match Python strings. Save this code to the `polyglot_example.py` file:
80+
match Python strings.
6381
```python
64-
import polyglot
82+
>>> js_re = polyglot.eval(string="RegExp()", language="js")
6583

66-
re = polyglot.eval(string="RegExp()", language="js")
84+
>>> pattern = js_re.compile(".*(?:we have (?:a )?matching strings?(?:[!\\?] )?)(.*)")
6785

68-
pattern = re.compile(".*(?:we have (?:a )?matching strings?(?:[!\\?] )?)(.*)")
86+
>>> if pattern.exec("This string does not match"):
87+
... raise SystemError("that shouldn't happen")
6988

70-
if pattern.exec("This string does not match"):
71-
raise SystemError("that shouldn't happen")
89+
>>> md = pattern.exec("Look, we have matching strings! This string was matched by Graal.js")
7290

73-
md = pattern.exec("Look, we have matching strings! This string was matched by Graal.js")
74-
if not md:
75-
raise SystemError("this should have matched")
76-
77-
print("Here is what we found: '%s'" % md[1])
78-
```
79-
80-
To run it, pass the `--jvm --polyglot` option to the `graalpy` launcher:
81-
```shell
82-
graalpy --jvm --polyglot polyglot_example.py
91+
>>> "Here is what we found: '%s'" % md[1]
92+
"Here is what we found: 'This string was matched by Graal.js'"
8393
```
8494

8595
This program matches Python strings using the JavaScript regular expression object.
86-
Python reads the captured group from the JavaScript result and prints:
87-
*Here is what we found: 'This string was matched by Graal.js'*.
96+
Python reads the captured group from the JavaScript result and prints it.
8897

8998
As a more complex example, see how you can read a file using R, process the data in Python, and use R again to display the resulting data image, using both the R and Python libraries in conjunction.
9099
To run this example, first install the required R library:
@@ -135,31 +144,36 @@ time.sleep(10)
135144

136145
Finally, to interoperate with Java (only when running on the JVM), you can use the `java` module:
137146
```python
138-
import java
139-
BigInteger = java.type("java.math.BigInteger")
140-
myBigInt = BigInteger.valueOf(42)
141-
# public Java methods can just be called
142-
myBigInt.shiftLeft(128)
143-
# Java method names that are keywords in Python can be accessed using `getattr`
144-
getattr(myBigInt, "not")()
145-
byteArray = myBigInt.toByteArray()
146-
# Java arrays can act like Python lists
147-
print(list(byteArray))
147+
>>> import java
148+
>>> BigInteger = java.type("java.math.BigInteger")
149+
>>> myBigInt = BigInteger.valueOf(42)
150+
>>> # public Java methods can just be called
151+
>>> myBigInt.shiftLeft(128)
152+
<JavaObject[java.math.BigInteger] at ...>
153+
>>> # Java method names that are keywords in Python can be accessed using `getattr`
154+
>>> getattr(myBigInt, "not")()
155+
<JavaObject[java.math.BigInteger] at ...>
156+
>>> byteArray = myBigInt.toByteArray()
157+
>>> # Java arrays can act like Python lists
158+
>>> list(byteArray)
159+
[42]
148160
```
149161

150162
For packages under the `java` package, you can also use the normal Python import
151163
syntax:
152164
```python
153-
import java.util.ArrayList
154-
from java.util import ArrayList
155-
156-
java.util.ArrayList == ArrayList
157-
158-
al = ArrayList()
159-
al.add(1)
160-
al.add(12)
161-
print(al)
162-
# prints [1, 12]
165+
>>> import java.util.ArrayList
166+
>>> from java.util import ArrayList
167+
>>>
168+
>>> java.util.ArrayList == ArrayList
169+
True
170+
>>> al = ArrayList()
171+
>>> al.add(1)
172+
True
173+
>>> al.add(12)
174+
True
175+
>>> al
176+
[1, 12]
163177
```
164178

165179
In addition to the `type` builtin method, the `java` module exposes the following
@@ -173,19 +187,18 @@ Builtin | Specification
173187
`is_symbol(obj)` | returns `True` if `obj` if the argument is a Java host symbol, representing the constructor and static members of a Java class, as obtained by `java.type`
174188

175189
```python
176-
import java
177-
ArrayList = java.type('java.util.ArrayList')
178-
my_list = ArrayList()
179-
print(java.is_symbol(ArrayList))
180-
# prints True
181-
print(java.is_symbol(my_list))
182-
# prints False, my_list is not a Java host symbol
183-
print(java.is_object(ArrayList))
184-
# prints True, symbols are also host objects
185-
print(java.is_function(my_list.add))
186-
# prints True, the add method of ArrayList
187-
print(java.instanceof(my_list, ArrayList))
188-
# prints True
190+
>>> ArrayList = java.type('java.util.ArrayList')
191+
>>> my_list = ArrayList()
192+
>>> java.is_symbol(ArrayList)
193+
True
194+
>>> java.is_symbol(my_list)
195+
False
196+
>>> java.is_object(ArrayList)
197+
True
198+
>>> java.is_function(my_list.add)
199+
True
200+
>>> java.instanceof(my_list, ArrayList)
201+
True
189202
```
190203

191204
See [Polyglot Programming](https://github.com/oracle/graal/blob/master/docs/reference-manual/polyglot-programming.md) and [Embed Languages](https://github.com/oracle/graal/blob/master/docs/reference-manual/embedding/embed-languages.md) for more information about interoperability with other programming languages.

graalpython/com.oracle.graal.python.frozen/freeze_modules.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def add_graalpython_core():
119119
"java",
120120
"pip_hook",
121121
"unicodedata",
122+
"sulong_support",
122123
]:
123124
modname = f"graalpy.{os.path.basename(name)}"
124125
modpath = os.path.join(lib_graalpython, f"{name}.py")

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ protected void launch(Builder contextBuilder) {
649649
if (!noSite) {
650650
contextBuilder.option("python.ForceImportSite", "true");
651651
}
652+
contextBuilder.option("python.SetupLLVMLibraryPaths", "true");
652653
contextBuilder.option("python.IgnoreEnvironmentFlag", Boolean.toString(ignoreEnv));
653654
contextBuilder.option("python.UnbufferedIO", Boolean.toString(unbufferedIO));
654655

@@ -687,14 +688,6 @@ protected void launch(Builder contextBuilder) {
687688
evalInternal(context, "__graalpython__.startup_wall_clock_ts = " + startupWallClockTime + "; __graalpython__.startup_nano = " + startupNanoTime);
688689
}
689690

690-
// Set LD_LIBRARY_PATH so that binaries built with sulong toolchain can find libc++.so
691-
evalInternal(context, """
692-
import os
693-
path = os.environ.get('LD_LIBRARY_PATH', '')
694-
sulong_path = os.pathsep.join(__graalpython__.get_toolchain_paths('LD_LIBRARY_PATH'))
695-
os.environ['LD_LIBRARY_PATH'] = path + os.pathsep + sulong_path if path else sulong_path
696-
""");
697-
698691
if (!quietFlag && (verboseFlag || (commandString == null && inputFile == null && stdinIsInteractive))) {
699692
print("Python " + evalInternal(context, "import sys; sys.version + ' on ' + sys.platform").asString());
700693
if (!noSite) {

graalpython/com.oracle.graal.python.test/src/graalpytest.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -788,11 +788,15 @@ def decorator(f):
788788
return decorator
789789

790790

791-
class TextTestResult():
791+
class TextTestResult :
792792
"Just a dummy to satisfy the unittest.support import"
793793
pass
794794

795795

796+
class TestSuite:
797+
pass
798+
799+
796800
if __name__ == "__main__":
797801
sys.modules["unittest"] = sys.modules["__main__"]
798802
patterns = []

graalpython/com.oracle.graal.python.test/src/tests/test_interop.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -38,14 +38,23 @@
3838
# SOFTWARE.
3939

4040
import os
41-
from unittest import skipIf
41+
import unittest
42+
from unittest import skipIf, skipUnless
4243

4344
import sys
4445

4546
if sys.implementation.name == "graalpy":
4647
import polyglot
4748
from __graalpython__ import is_native
4849

50+
try:
51+
polyglot.eval(language="ruby", string="1")
52+
polyglot.eval(language="js", string="1")
53+
except:
54+
test_polyglot_languages = False
55+
else:
56+
test_polyglot_languages = True
57+
4958
def test_import():
5059
def some_function():
5160
return "hello, polyglot world!"
@@ -640,3 +649,19 @@ def test_jython_star_import():
640649
g = {}
641650
exec('from java.lang.Byte import *', g)
642651
assert type(g['MAX_VALUE']) is int
652+
653+
@skipUnless(test_polyglot_languages, "tests other language access")
654+
def test_doctest():
655+
import doctest
656+
657+
class Example(doctest.Example):
658+
"""
659+
Subclass of doctest.Example that accepts the end of
660+
markdown code blocks as end of examples.
661+
"""
662+
def __init__(self, source, want, *args, **kwargs):
663+
want = want.rstrip("```\n")
664+
super(Example, self).__init__(source, want, *args, **kwargs)
665+
doctest.Example = Example
666+
667+
assert doctest.testmod(m=polyglot, verbose=getattr(unittest, "verbose"), optionflags=doctest.ELLIPSIS).failed == 0

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PolyglotModuleBuiltins.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -132,7 +132,7 @@ public void initialize(Python3Core core) {
132132
TruffleFile coreDir = env.getInternalTruffleFile(coreHome.toJavaStringUncached());
133133
TruffleFile docDir = coreDir.resolveSibling("docs");
134134
if (docDir.exists() || docDir.getParent() != null && (docDir = coreDir.getParent().resolveSibling("docs")).exists()) {
135-
addBuiltinConstant(SpecialAttributeNames.T___DOC__, new String(docDir.resolve("user").resolve("POLYGLOT.md").readAllBytes()));
135+
addBuiltinConstant(SpecialAttributeNames.T___DOC__, new String(docDir.resolve("user").resolve("Interoperability.md").readAllBytes()));
136136
}
137137
} catch (SecurityException | IOException e) {
138138
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/foreign/ForeignObjectBuiltins.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ abstract static class MulNode extends ForeignBinaryNode {
388388
super(BinaryArithmetic.Mul.create(), false);
389389
}
390390

391-
@Specialization(insertBefore = "doComparisonBool", guards = {"!lib.isBoolean(left)", "!lib.isNumber(left)", "lib.hasArrayElements(left)", "lib.fitsInLong(right)"})
391+
@Specialization(insertBefore = "doComparisonBool", guards = {"!lib.isBoolean(left)", "!lib.isNumber(left)", "!lib.isString(left)", "lib.hasArrayElements(left)", "lib.fitsInLong(right)"})
392392
static Object doForeignArray(Object left, Object right,
393393
@Cached PRaiseNode raise,
394394
@Cached PythonObjectFactory factory,
@@ -411,7 +411,7 @@ static Object doForeignArray(Object left, Object right,
411411
}
412412
}
413413

414-
@Specialization(insertBefore = "doComparisonBool", guards = {"!lib.isBoolean(left)", "!lib.isNumber(left)", "lib.hasArrayElements(left)", "lib.isBoolean(right)"})
414+
@Specialization(insertBefore = "doComparisonBool", guards = {"!lib.isBoolean(left)", "!lib.isNumber(left)", "!lib.isString(left)", "lib.hasArrayElements(left)", "lib.isBoolean(right)"})
415415
static Object doForeignArrayForeignBoolean(Object left, Object right,
416416
@Cached PRaiseNode raise,
417417
@Cached PythonObjectFactory factory,

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/FrozenModules.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ private static final class Map {
214214
private static final PythonFrozenModule GRAALPY_JAVA = new PythonFrozenModule("GRAALPY_JAVA", "graalpy.java", false);
215215
private static final PythonFrozenModule GRAALPY_PIP_HOOK = new PythonFrozenModule("GRAALPY_PIP_HOOK", "graalpy.pip_hook", false);
216216
private static final PythonFrozenModule GRAALPY_UNICODEDATA = new PythonFrozenModule("GRAALPY_UNICODEDATA", "graalpy.unicodedata", false);
217+
private static final PythonFrozenModule GRAALPY_SULONG_SUPPORT = new PythonFrozenModule("GRAALPY_SULONG_SUPPORT", "graalpy.sulong_support", false);
217218
}
218219

219220
public static final PythonFrozenModule lookup(String name) {
@@ -572,6 +573,8 @@ public static final PythonFrozenModule lookup(String name) {
572573
return Map.GRAALPY_PIP_HOOK;
573574
case "graalpy.unicodedata":
574575
return Map.GRAALPY_UNICODEDATA;
576+
case "graalpy.sulong_support":
577+
return Map.GRAALPY_SULONG_SUPPORT;
575578
default:
576579
return null;
577580
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494

9595
import com.oracle.graal.python.PythonLanguage;
9696
import com.oracle.graal.python.builtins.Python3Core;
97+
import com.oracle.graal.python.builtins.modules.ImpModuleBuiltins;
9798
import com.oracle.graal.python.builtins.modules.MathGuards;
9899
import com.oracle.graal.python.builtins.modules.ctypes.CtypesModuleBuiltins.CtypesThreadState;
99100
import com.oracle.graal.python.builtins.objects.PNone;
@@ -1430,6 +1431,9 @@ private void importSiteIfForced() {
14301431
// When InputFilePath is set, this is handled by __graalpython__.run_path
14311432
addSysPath0();
14321433
}
1434+
if (getOption(PythonOptions.SetupLLVMLibraryPaths)) {
1435+
ImpModuleBuiltins.importFrozenModuleObject(this, toTruffleStringUncached("graalpy.sulong_support"), false);
1436+
}
14331437
}
14341438

14351439
public void addSysPath0() {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@ private PythonOptions() {
303303
@Option(category = OptionCategory.EXPERT, usageSyntax = "true|false", help = "Force to automatically import site.py module.") //
304304
public static final OptionKey<Boolean> ForceImportSite = new OptionKey<>(false);
305305

306+
@Option(category = OptionCategory.EXPERT, usageSyntax = "true|false", help = "Set-up library search paths to include GraalPy's LLVM toolchain library directories.") //
307+
public static final OptionKey<Boolean> SetupLLVMLibraryPaths = new OptionKey<>(false);
308+
306309
@Option(category = OptionCategory.EXPERT, usageSyntax = "true|false", help = "This option is set by the Python launcher to tell the language it can print exceptions directly") //
307310
public static final OptionKey<Boolean> AlwaysRunExcepthook = new OptionKey<>(false);
308311

0 commit comments

Comments
 (0)