Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ permissions:

jobs:

unit-tests-Ubuntu-jdk-17:
unit-tests-jdk-17:

runs-on: ubuntu-latest
strategy:
# Report each platform, even if the other fails
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]

name: unit tests (${{ matrix.os }}, JDK 17)
runs-on: ${{ matrix.os }}

steps:
- run: echo "Branch ${{ github.ref }} of repository ${{ github.repository }}."
Expand All @@ -26,19 +33,26 @@ jobs:
java-version: '17'

- name: Set up Python 3.11
id: setup-python
uses: actions/setup-python@v4
with:
# This has to match the language version we're targeting
python-version: '3.11'

- name: Unit test with Gradle
# Tell the build exactly which CPython to use (see core/core.gradle).
# An environment variable avoids quoting a Windows path on the
# command line, where PowerShell also mangles -Pjython.cpython=...
env:
JYTHON_CPYTHON: ${{ steps.setup-python.outputs.python-path }}
run: ./gradlew --no-daemon test

- name: Clean up Gradle cache
# Remove some files from the Gradle cache, so they aren't cached
# by GitHub Actions. Restoring these files from a GitHub Actions
# cache might cause problems for future builds.
#https://docs.github.com/en/actions/guides/building-and-testing-java-with-gradle#caching-dependencies
shell: bash
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties
25 changes: 24 additions & 1 deletion core/core.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,23 @@ sourceSets {

// A task that runs Python with the tools library on the path

/*
* The CPython that compiles Python for us must be the version whose byte
* code we execute (3.11). Choose it with -Pjython.cpython=<exe> or the
* environment variable JYTHON_CPYTHON; otherwise "python" from the PATH.
* Tests and the REPL get it as the system property of the same name, which
* applies the same rules when run outside Gradle (see ReplCompiler).
*/
ext.cpythonExecutable = findProperty('jython.cpython')
?: System.getenv('JYTHON_CPYTHON') ?: 'python'

// The REPL tests compile with CPython (see ReplCompiler)
test.systemProperty 'jython.cpython', cpythonExecutable

class PythonExec extends Exec {

PythonExec() {
executable 'python'
executable getProject().cpythonExecutable
environment('PYTHONPATH', getProject().getRootProject().file(
'build-tools/python/lib'))
}
Expand All @@ -95,6 +108,15 @@ task generateObjectDefinitions(type: PythonExec,
compileJava.dependsOn(generateObjectDefinitions)


// Run the interactive REPL (compiling with CPython)
// ./gradlew -q --console=plain core:repl

task repl(type: JavaExec, description: 'Run the interactive REPL') {
classpath = sourceSets.main.runtimeClasspath
mainClass = 'org.python.util.Repl'
standardInput = System.in
systemProperty 'jython.cpython', cpythonExecutable
}
// Generate opcode constants (OpcodeNNN.java) from the supported CPython

task generateOpcodes(type: PythonExec,
Expand Down Expand Up @@ -128,6 +150,7 @@ task compileTestPythonExamples(type: PythonExec,
fileTree(testPythonExampleInputDir) {
include '**/*.py'
})
inputs.property('cpython', cpythonExecutable)
outputs.dir(testPythonExampleOutputDir)
// Exec task is python -m compile_examples <src> <dst>
args '-m'
Expand Down
44 changes: 44 additions & 0 deletions core/src/main/java/org/python/core/BuiltinsModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,50 @@ private static Object minmax(Object arg1, PyTuple args, Object key, Object dflt,
private static final String DEFAULT_WITHOUT_ITERABLE =
"Cannot specify a default for %s() with multiple positional arguments";

/**
* Implementation of {@code print()}, simplified: output always goes
* to {@code System.out}, and {@code file} and {@code flush} are not
* yet supported.
*
* @param sep separator between values ({@code None} means a space)
* @param end appended after the last value ({@code None} means a
* new line)
* @param args the values to print
* @throws Throwable from {@code str()} of an argument
*/
@PythonStaticMethod(positionalOnly = false)
@DocString("Prints the values to sys.stdout.")
// Simplified version of print()
static void print(@KeywordOnly @Default("None") Object sep, @Default("None") Object end,
@PositionalCollector PyTuple args) throws Throwable {
// @PositionalCollector has to be last.
if (sep != Py.None && !PyUnicode.TYPE.check(sep)) { throw noneOrStrError("sep", sep); }
if (end != Py.None && !PyUnicode.TYPE.check(end)) { throw noneOrStrError("end", end); }
String s = sep == Py.None ? " " : sep.toString();
String e = end == Py.None ? "\n" : end.toString();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < args.size(); i++) {
if (i > 0) { sb.append(s); }
sb.append(Abstract.str(args.get(i)));
}
sb.append(e);
System.out.print(sb);
System.out.flush();
}

/**
* Create a {@link TypeError} for an argument that should have been
* {@code None} or a {@code str}.
*
* @param name of the argument
* @param v value of the argument
* @return exception to throw
*/
private static TypeError noneOrStrError(String name, Object v) {
return new TypeError("%s must be None or a string, not %.200s", name,
PyType.of(v).getName());
}

@PythonStaticMethod
@DocString("Return the canonical string representation of the object.\n"
+ "For many object types, including most builtins, eval(repr(obj)) == obj.")
Expand Down
43 changes: 42 additions & 1 deletion core/src/main/java/org/python/core/CPython311Frame.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Map;

import org.python.base.InterpreterError;
import org.python.base.MissingFeature;
import org.python.core.PyCode.Layout;
import org.python.core.PyCode.Trait;
import org.python.core.PyDict.MergeMode;
Expand Down Expand Up @@ -253,6 +254,12 @@ Object eval() {
PySequence.delItem(s[sp], s[sp + 1]);
break;

case Opcode311.PRINT_EXPR:
// v | -> |
// ---^sp -^sp
displayHook(s[--sp]);
break;

case Opcode311.RETURN_VALUE:
returnValue = s[--sp]; // POP
break loop;
Expand Down Expand Up @@ -553,7 +560,7 @@ Object eval() {
int top = sp - 1;
Object v = s[top]; // TOP
s[top] = switch (oparg) {
default -> Py.NotImplemented;
default -> throw new MissingFeature("BINARY_OP %d", oparg);
case Opcode311.NB_ADD -> PyNumber.add(v, w);
case Opcode311.NB_AND -> PyNumber.and(v, w);
// case Opcode311.NB_FLOOR_DIVIDE -> PyNumber.FloorDivide(v, w);
Expand Down Expand Up @@ -593,6 +600,16 @@ Object eval() {
break;
}

case Opcode311.BUILD_SLICE: {
// start | stop | [step] | -> | slice |
// ---------------------^sp ---------^sp
Object step = oparg == 3 ? s[--sp] : null;
Object stop = s[--sp];
int top = sp - 1;
s[top] = new PySlice(s[top], stop, step);
break;
}

case Opcode311.EXTENDED_ARG:
// Pick up the next instruction.
opword = wordcode[ip++] & 0xffff;
Expand Down Expand Up @@ -652,6 +669,30 @@ Object eval() {

// Supporting definitions and methods -----------------------------

/**
Comment thread
jeff5 marked this conversation as resolved.
Resolved
* Display the value of an expression statement in interactive code,
* for the {@code PRINT_EXPR} operation. This prints
* {@code repr(value)} to standard output, unless it is
* {@code None}, and binds {@code builtins._} to the value.
* <p>
* This is a stop-gap until we have a {@code sys} module. CPython's
* {@code PRINT_EXPR} looks up {@code sys.displayhook} and calls
* that, so it may be replaced from Python, and writes to
* {@code sys.stdout}. When we have those, {@code PRINT_EXPR} should
* do the same and this method should go.
*
* @param value to display
* @throws Throwable from {@code repr()}
*/
// Stop-gap for sys.displayhook: compare CPython sys_displayhook in sysmodule.c
private void displayHook(Object value) throws Throwable {
if (value != Py.None) {
builtins.put("_", Py.None);
System.out.println(Abstract.repr(value));
builtins.put("_", value);
}
}

private static final Object[] EMPTY_OBJECT_ARRAY = Py.EMPTY_ARRAY;
private static final String NAME_ERROR_MSG = "name '%.200s' is not defined";
private static final String VALUE_AFTER_STAR = "Value after * must be an iterable, not %.200s";
Expand Down
8 changes: 4 additions & 4 deletions core/src/main/java/org/python/core/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* exposed through the {@code sys} module, rather than any class
* with "interpreter" in the name.
*/
class Interpreter {
public class Interpreter {

/**
* The list of modules created by this interpreter, exposed as
Expand All @@ -30,7 +30,7 @@ class Interpreter {
final PyModule builtinsModule;

/** Create a new {@code Interpreter}. */
Interpreter() {
public Interpreter() {
builtinsModule = new BuiltinsModule();
builtinsModule.exec();
// addModule(builtinsModule);
Expand Down Expand Up @@ -62,7 +62,7 @@ void addModule(PyModule m) {
* @return result of evaluation
*/
// Compare CPython PyEval_EvalCode in ceval.c
Object eval(PyCode code, PyDict globals, Object locals) {
public Object eval(PyCode code, PyDict globals, Object locals) {
if (locals == null) { locals = globals; }
globals.putIfAbsent("__builtins__", builtinsModule);
PyFunction<?> func = code.createFunction(this, globals);
Expand All @@ -79,7 +79,7 @@ Object eval(PyCode code, PyDict globals, Object locals) {
* @param globals global context dictionary
* @return result of evaluation
*/
Object eval(PyCode code, PyDict globals) { return eval(code, globals, globals); }
public Object eval(PyCode code, PyDict globals) { return eval(code, globals, globals); }

/**
* Get the value of an attribute of the built-in module, equivalent
Expand Down
Loading
Loading