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
409 changes: 184 additions & 225 deletions core/src/main/java/org/python/core/ArgParser.java

Large diffs are not rendered by default.

185 changes: 185 additions & 0 deletions core/src/main/java/org/python/core/BuiltinsModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright (c)2023 Jython Developers.
// Licensed to PSF under a contributor agreement.
package org.python.core;

import java.lang.invoke.MethodHandles;
import java.util.Iterator;

import org.python.core.Exposed.Default;
import org.python.core.Exposed.DocString;
import org.python.core.Exposed.KeywordOnly;
import org.python.core.Exposed.Name;
import org.python.core.Exposed.PositionalCollector;
import org.python.core.Exposed.PythonStaticMethod;

/**
* The {@code builtins} module is definitely called "builtins".
* <p>
* Although it is fully a module, the {@link BuiltinsModule} lives
* in the {@code core} package because it needs privileged access to
* the core implementation that extension modules do not.
*/
class BuiltinsModule extends JavaModule {

private static final ModuleDef DEFINITION = new ModuleDef("builtins", MethodHandles.lookup());

/** Construct an instance of the {@code builtins} module. */
BuiltinsModule() {
super(DEFINITION);

// This list is taken from CPython bltinmodule.c
add("None", Py.None);
// add("Ellipsis", Py.Ellipsis);
add("NotImplemented", Py.NotImplemented);
add("False", Py.False);
add("True", Py.True);
add("bool", PyBool.TYPE);
// add("memoryview", PyMemoryView.TYPE);
// add("bytearray", PyByteArray.TYPE);
add("bytes", PyBytes.TYPE);
// add("classmethod", PyClassMethod.TYPE);
// add("complex", PyComplex.TYPE);
add("dict", PyDict.TYPE);
// add("enumerate", PyEnum.TYPE);
// add("filter", PyFilter.TYPE);
add("float", PyFloat.TYPE);
// add("frozenset", PyFrozenSet.TYPE);
// add("property", PyProperty.TYPE);
add("int", PyLong.TYPE);
add("list", PyList.TYPE);
// add("map", PyMap.TYPE);
add("object", PyBaseObject.TYPE);
// add("range", PyRange.TYPE);
// add("reversed", PyReversed.TYPE);
// add("set", PySet.TYPE);
add("slice", PySlice.TYPE);
// add("staticmethod", PyStaticMethod.TYPE);
add("str", PyUnicode.TYPE);
// add("super", PySuper.TYPE);
add("tuple", PyTuple.TYPE);
add("type", PyType.TYPE);
// add("zip", PyZip.TYPE);
}

@PythonStaticMethod
@DocString("Return the absolute value of the argument.")
static Object abs(Object x) throws Throwable { return PyNumber.absolute(x); }

@PythonStaticMethod
@DocString("Return the number of items in a container.")
static Object len(Object v) throws Throwable { return PySequence.size(v); }

/**
* Implementation of {@code max()}.
*
* @param arg1 a first argument or iterable of arguments
* @param args contains other positional arguments
* @param key function
* @param dflt to return when iterable is empty
* @return {@code max} result or {@code dflt}
* @throws Throwable from calling {@code key} or comparison
*/
@PythonStaticMethod(positionalOnly = false)
@DocString("Return the largest item in an iterable"
+ " or the largest of two or more arguments.")
// Simplified version of max()
static Object max(Object arg1, @KeywordOnly @Default("None") Object key,
@Name("default") @Default("None") Object dflt, @PositionalCollector PyTuple args)
throws Throwable {
// @PositionalCollector has to be last.
return minmax(arg1, args, key, dflt, Comparison.GT);
}

/**
* Implementation of {@code min()}.
*
* @param arg1 a first argument or iterable of arguments
* @param args contains other positional arguments
* @param key function
* @param dflt to return when iterable is empty
* @return {@code min} result or {@code dflt}
* @throws Throwable from calling {@code key} or comparison
*/
@PythonStaticMethod(positionalOnly = false)
@DocString("Return the smallest item in an iterable"
+ " or the smallest of two or more arguments.")
// Simplified version of min()
static Object min(Object arg1, @KeywordOnly @Default("None") Object key,
@Name("default") @Default("None") Object dflt, @PositionalCollector PyTuple args)
throws Throwable {
// @PositionalCollector has to be last.
return minmax(arg1, args, key, dflt, Comparison.LT);
}

/**
* Implementation of both
* {@link #min(Object, Object, Object, PyTuple) min()} and
* {@link #max(Object, Object, Object, PyTuple) max()}.
*
* @param arg1 a first argument or iterable of arguments
* @param args contains other positional arguments
*
* @param key function
* @param dflt to return when iterable is empty
* @param op {@code LT} for {@code min} and {@code GT} for
* {@code max}.
* @return min or max result as appropriate
* @throws Throwable from calling {@code op} or {@code key}
*/
// Compare CPython min_max in Python/bltinmodule.c
private static Object minmax(Object arg1, PyTuple args, Object key, Object dflt, Comparison op)
throws Throwable {

int n = args.size();
Object result;
Iterator<Object> others;
assert key != null;

if (n > 0) {
/*
* Positional mode: arg1 is the first value, args contains the other
* values to compare
*/
result = key == Py.None ? arg1 : Callables.callFunction(key, arg1);
others = args.iterator();
if (dflt != Py.None) {
String name = op == Comparison.LT ? "min" : "max";
throw new TypeError(DEFAULT_WITHOUT_ITERABLE, name);
}

} else {
// Single iterable argument of the values to compare
result = null;
// XXX define PySequence.iterable like PyMapping.map?
others = PySequence.fastList(arg1, null).iterator();
}

// Now we can get on with the comparison
while (others.hasNext()) {
Object item = others.next();
if (key != Py.None) { item = Callables.callFunction(key, item); }
if (result == null) {
result = item;
} else if (Abstract.richCompareBool(item, result, op)) { result = item; }
}

// result may be null if the single iterable argument is empty
if (result != null) {
return result;
} else if (dflt != Py.None) {
assert dflt != null;
return dflt;
} else {
String name = op == Comparison.LT ? "min" : "max";
throw new ValueError("%s() arg is an empty sequence", name);
}
}

private static final String DEFAULT_WITHOUT_ITERABLE =
"Cannot specify a default for %s() with multiple positional arguments";

@PythonStaticMethod
@DocString("Return the canonical string representation of the object.\n"
+ "For many object types, including most builtins, eval(repr(obj)) == obj.")
static Object repr(Object obj) throws Throwable { return Abstract.repr(obj); }
}
27 changes: 24 additions & 3 deletions core/src/main/java/org/python/core/CPython38Frame.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c)2022 Jython Developers.
// Copyright (c)2023 Jython Developers.
// Licensed to PSF under a contributor agreement.
package org.python.core;

Expand Down Expand Up @@ -155,11 +155,21 @@ Object eval() {
try {
locals.put(name, s[--sp]);
} catch (NullPointerException npe) {
throw new SystemError("no locals found when storing '%s'", name);
throw noLocals("storing", name);
}
oparg = 0;
break;

case Opcode.DELETE_NAME:
name = names[oparg | opword & 0xff];
oparg = 0;
try {
locals.remove(name);
} catch (NullPointerException npe) {
throw noLocals("deleting", name);
}
break;

case Opcode.BUILD_MAP:
// k1 | v1 | ... | kN | vN | -> | map |
// -------------------------^sp -------^sp
Expand Down Expand Up @@ -355,7 +365,7 @@ Object eval() {
* regular method in it. {@code CALL_METHOD} will detect and use
* this optimised form if the first element is not {@code null}.
*
* @param obj of whichg the callable is an attribute
* @param obj of which the callable is an attribute
* @param name of callable attribute
* @param offset in stack at which to place results
* @throws AttributeError ifthe named attribute does not exist
Expand Down Expand Up @@ -463,4 +473,15 @@ private void getMethod(Object obj, String name, int offset) throws AttributeErro
// All the look-ups and descriptors came to nothing :(
throw Abstract.noAttributeError(obj, name);
}

/**
* Generate error to throw when we cannot access locals.
*
* @param action "loading", "storing" or "deleting"
* @param name variable name
* @return
*/
private static SystemError noLocals(String action, String name) {
return new SystemError("no locals found when %s '%s'", name);
}
}
55 changes: 48 additions & 7 deletions core/src/main/java/org/python/core/Exposer.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.python.core.Exposed.PositionalOnly;
import org.python.core.Exposed.PythonMethod;
import org.python.core.Exposed.PythonStaticMethod;
import org.python.core.ModuleDef.MethodDef;

/**
* An object for tabulating the attributes of classes that define
Expand Down Expand Up @@ -67,6 +68,25 @@ protected Exposer() {
/** @return which {@link ScopeKind} of {@code Exposer} is this? */
abstract ScopeKind kind();

/**
* On behalf of the given module defined in Java, build a
* description of the attributes discovered by introspection of the
* class provided.
* <p>
* Attributes are identified by annotations. (See {@link Exposed}.)
*
* @param definingClass to introspect for members
* @return exposure result
* @throws InterpreterError on errors of definition
*/
static ModuleExposer exposeModule(Class<?> definingClass) throws InterpreterError {
// Create an instance of Exposer to hold specs, type, etc.
ModuleExposer exposer = new ModuleExposer();
// Let the exposer control the logic
exposer.expose(definingClass);
return exposer;
}

/**
* On behalf of the given type defined in Java, build a description
* of the attributes discovered by introspection of the class (or
Expand Down Expand Up @@ -617,14 +637,35 @@ private boolean isDefined() {
ArgParser getParser() {
if (parser == null && parameterNames != null
&& parameterNames.length >= posonlyargcount) {
parser = new ArgParser(name, scopeKind, methodKind, varArgsIndex >= 0,
varKeywordsIndex >= 0, posonlyargcount, kwonlyargcount, parameterNames,
regargcount);
parser = new ArgParser(name, scopeKind, methodKind, parameterNames, regargcount,
posonlyargcount, kwonlyargcount, varArgsIndex >= 0, varKeywordsIndex >= 0);
parser.defaults(defaults).kwdefaults(kwdefaults);
}
return parser;
}

/**
* Produce a method definition from this specification that
* references a method handle on the (single) defining method and
* the parser created from this specification. This is used in the
* construction of a module defined in Java (a {@link ModuleDef}).
*
* @param lookup authorisation to access methods
* @return corresponding method definition
* @throws InterpreterError on lookup prohibited
*/
MethodDef getMethodDef(Lookup lookup) throws InterpreterError {
assert methods.size() == 1;
Method m = methods.get(0);
MethodHandle mh;
try {
mh = lookup.unreflect(m);
} catch (IllegalAccessException e) {
throw cannotGetHandle(m, e);
}
return new MethodDef(getParser(), mh);
}

/**
* Add a method implementation. (A test that the signature is
* acceptable follows when we construct the {@link PyMethodDescr}.)
Expand Down Expand Up @@ -1126,9 +1167,9 @@ static class MethodSpec extends CallableSpec {
@Override
PyMethodDescr asAttribute(PyType objclass, Lookup lookup) throws InterpreterError {

ArgParser ap = new ArgParser(name, scopeKind, MethodKind.INSTANCE, varArgsIndex >= 0,
varKeywordsIndex >= 0, posonlyargcount, kwonlyargcount, parameterNames,
regargcount);
ArgParser ap = new ArgParser(name, scopeKind, MethodKind.INSTANCE, parameterNames,
regargcount, posonlyargcount, kwonlyargcount, varArgsIndex >= 0,
varKeywordsIndex >= 0);
ap.defaults(defaults).kwdefaults(kwdefaults);

// Methods have self + this many args:
Expand Down Expand Up @@ -1166,7 +1207,7 @@ static class StaticMethodSpec extends CallableSpec {
StaticMethodSpec(String name, ScopeKind scopeKind) { super(name, scopeKind); }

@Override
PyJavaMethod asAttribute(PyType objclass, Lookup lookup) {
PyJavaFunction asAttribute(PyType objclass, Lookup lookup) {
// TODO Auto-generated method stub
return null;
}
Expand Down
7 changes: 4 additions & 3 deletions core/src/main/java/org/python/core/Interpreter.java
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Copyright (c)2023 Jython Developers.
// Licensed to PSF under a contributor agreement.
package org.python.core;

import org.python.base.InterpreterError;
Expand Down Expand Up @@ -29,10 +31,9 @@ class Interpreter {

/** Create a new {@code Interpreter}. */
Interpreter() {
// builtinsModule = new BuiltinsModule();
// builtinsModule.init();
builtinsModule = new BuiltinsModule();
builtinsModule.exec();
// addModule(builtinsModule);
builtinsModule = null;
}

void addModule(PyModule m) {
Expand Down
42 changes: 42 additions & 0 deletions core/src/main/java/org/python/core/JavaModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c)2023 Jython Developers.
// Licensed to PSF under a contributor agreement.
package org.python.core;

/** Common mechanisms for all Python modules defined in Java. */
public abstract class JavaModule extends PyModule {

final ModuleDef definition;

/**
* Construct the base {@code JavaModule}, saving the module
* definition, which is normally created during static
* initialisation of the concrete class defining the module. In
* terms of PEP 489 phases, the constructor performs the
* {@code Py_mod_create}. We defer filling the module dictionary
* from the definition and other sources until {@link #exec()} is
* called.
*
* @param definition of the module
*/
protected JavaModule(ModuleDef definition) {
super(definition.name);
this.definition = definition;
}

/**
* {@inheritDoc}
* <p>
* In the case of a {@code JavaModule}, the base implementation
* mines the method definitions from the {@link #definition}. The
* module should extend this method, that is call
* {@code super.exec()} to add boilerplate and the methods, then add
* other definitions (typically constants) to the module namespace
* with {@link #add(String, Object) #add(String, Object)}. In terms
* of PEP 489 phases, this is the {@code Py_mod_exec} phase.
*/
@Override
void exec() {
super.exec();
definition.addMembers(this);
}
}
Loading