diff --git a/README.md b/README.md index 6f32a5c6..9ede52fc 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,9 @@ The `_demo` directory contains our demos (it start with `_` to prevent the `go` * [qsort](_demo/qsort/qsort.go): call C function with a callback (eg. qsort) * [genints](_demo/genints/genints.go): various forms of closure usage (including C function, recv.method and anonymous function) * [llama2-c](_demo/llama2-c): inference Llama 2 (It's the first llgo AI example) -* [hellopy](https://github.com/goplus/cpython/blob/main/_demo/hellopy/hello.go): link Python to Go and say `Hello world` +* [hellopy](_demo/hellopy/hello.go): link Python to Go and say `Hello world` +* [clpy](_demo/clpy/cleval.go): compile Python code and eval. +* [callpy](_demo/callpy/call.go): call Python standard library function `math.sqrt`. ### How to run demos diff --git a/_demo/callpy/call.go b/_demo/callpy/call.go new file mode 100644 index 00000000..abc21275 --- /dev/null +++ b/_demo/callpy/call.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/py" +) + +func main() { + py.Initialize() + py.SetProgramName(*c.Argv) + math := py.ImportModule(c.Str("math")) + sqrt := math.GetAttrString(c.Str("sqrt")) + sqrt2 := sqrt.CallOneArg(py.Float(2)).FloatAsDouble() + c.Printf(c.Str("sqrt(2) = %f\n"), sqrt2) + py.Finalize() +} diff --git a/_demo/clpy/cleval.go b/_demo/clpy/cleval.go new file mode 100644 index 00000000..13f78b80 --- /dev/null +++ b/_demo/clpy/cleval.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/py" +) + +func main() { + py.Initialize() + py.SetProgramName(*c.Argv) + code := py.CompileString(c.Str(`print('Hello, World!')`), c.Str(`hello.py`), py.EvalInput) + if code != nil { + mod := py.ImportModule(c.Str("__main__")) + gbl := mod.GetDict() + + result := py.EvalCode(code, gbl, nil) + + result.DecRef() + mod.DecRef() + code.DecRef() + } + py.Finalize() +} diff --git a/_demo/hellopy/hello.go b/_demo/hellopy/hello.go new file mode 100644 index 00000000..d0f7de52 --- /dev/null +++ b/_demo/hellopy/hello.go @@ -0,0 +1,13 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/py" +) + +func main() { + py.Initialize() + py.SetProgramName(*c.Argv) + py.RunSimpleString(c.Str(`print('Hello, World!')`)) + py.Finalize() +} diff --git a/py/float.go b/py/float.go new file mode 100644 index 00000000..a4916225 --- /dev/null +++ b/py/float.go @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package py + +import ( + _ "unsafe" +) + +// https://docs.python.org/3/c-api/float.html + +//go:linkname Float C.PyFloat_FromDouble +func Float(v float64) *Object + +//go:linkname FloatFromSring C.PyFloat_FromString +func FloatFromSring(v *Object) *Object + +// llgo:link (*Object).FloatAsDouble C.PyFloat_AsDouble +func (o *Object) FloatAsDouble() float64 { panic("unreachable") } diff --git a/py/module.go b/py/module.go new file mode 100644 index 00000000..a65e2987 --- /dev/null +++ b/py/module.go @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package py + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +// Module represents a Python module object. +type Module struct { + Object +} + +// ----------------------------------------------------------------------------- + +// This is a wrapper around py.Import which takes a const char* as an argument +// instead of an Object. +// +//go:linkname ImportModule C.PyImport_ImportModule +func ImportModule(name *c.Char) *Module + +// This is a higher-level interface that calls the current “import hook function” (with +// an explicit level of 0, meaning absolute import). It invokes the __import__() function +// from the __builtins__ of the current globals. This means that the import is done using +// whatever import hooks are installed in the current environment. +// +// This function always uses absolute imports. +// +//go:linkname Import C.PyImport_Import +func Import(name *Object) *Module + +// Return the dictionary object that implements module’s namespace; this object is the same +// as the __dict__ attribute of the module object. If module is not a module object (or a +// subtype of a module object), SystemError is raised and nil is returned. +// +// It is recommended extensions use other Module and Object functions rather than directly +// manipulate a module’s __dict__. +// +// llgo:link (*Module).GetDict C.PyModule_GetDict +func (m *Module) GetDict() *Object { panic("unreachable") } + +// Retrieve an attribute named attrName from object o. Returns the attribute value on success, +// or nil on failure. This is the equivalent of the Python expression o.attrName. +// +// llgo:link (*Object).GetAttr C.PyObject_GetAttr +func (o *Object) GetAttr(attrName *Object) *Object { panic("unreachable") } + +// llgo:link (*Object).GetAttrString C.PyObject_GetAttrString +func (o *Object) GetAttrString(attrName *c.Char) *Object { panic("unreachable") } + +// ----------------------------------------------------------------------------- diff --git a/py/object.go b/py/object.go new file mode 100644 index 00000000..387b02da --- /dev/null +++ b/py/object.go @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package py + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +// Object represents a Python object. +type Object struct { + Unused [8]byte +} + +// Create a new value based on a format string similar to those accepted by the +// PyArg_Parse* family of functions and a sequence of values. Returns the value or +// nil in the case of an error; an exception will be raised if nil is returned. +// See https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue +// +//go:linkname BuildValue C.Py_BuildValue +func BuildValue(format *c.Char, __llgo_va_list ...any) *Object + +// llgo:link (*Object).DecRef C.Py_DecRef +func (o *Object) DecRef() { panic("unreachable") } + +// ----------------------------------------------------------------------------- + +// Determine if the object o is callable. Return 1 if the object is callable and +// 0 otherwise. This function always succeeds. +// +// llgo:link (*Object).Callable C.PyCallable_Check +func (o *Object) Callable() int { panic("unreachable") } + +// Call a callable Python object o, with arguments given by the tuple args, and +// named arguments given by the dictionary kwargs. +// +// args must not be nil; use an empty tuple if no arguments are needed. If no named +// arguments are needed, kwargs can be nil. +// +// Return the result of the call on success, or raise an exception and return nil +// on failure. +// +// This is the equivalent of the Python expression: o(*args, **kwargs). +// +// llgo:link (*Object).Call C.PyObject_Call +func (o *Object) Call(args, kwargs *Object) *Object { panic("unreachable") } + +// Call a callable Python object callable without any arguments. It is the most +// efficient way to call a callable Python object without any argument. +// +// Return the result of the call on success, or raise an exception and return nil +// on failure. +// +// llgo:link (*Object).CallNoArgs C.PyObject_CallNoArgs +func (o *Object) CallNoArgs() *Object { panic("unreachable") } + +// Call a callable Python object callable with exactly 1 positional argument arg +// and no keyword arguments. +// +// Return the result of the call on success, or raise an exception and return nil +// on failure. +// +// llgo:link (*Object).CallOneArg C.PyObject_CallOneArg +func (o *Object) CallOneArg(arg *Object) *Object { panic("unreachable") } + +// Call a callable Python object o, with arguments given by the tuple args. If no +// arguments are needed, then args can be nil. +// +// Return the result of the call on success, or raise an exception and return nil +// on failure. +// +// This is the equivalent of the Python expression: o(*args). +// +// llgo:link (*Object).CallObject C.PyObject_CallObject +func (o *Object) CallObject(callable, args *Object) *Object { panic("unreachable") } + +// Call a callable Python object o, with a variable number of C arguments. The C +// arguments are described using a py.BuildValue style format string. The format +// can be nil, indicating that no arguments are provided. +// +// Return the result of the call on success, or raise an exception and return nil +// on failure. +// +// This is the equivalent of the Python expression: o(*args). +// +// Note that if you only pass PyObject* args, (*Object).CallFunctionObjArgs is a +// faster alternative. +// +// llgo:link (*Object).CallFunction C.PyObject_CallFunction +func (o *Object) CallFunction(format *c.Char, __llgo_va_list ...any) *Object { panic("unreachable") } + +// Call a callable Python object o, with a variable number of PyObject* arguments. +// The arguments are provided as a variable number of parameters followed by nil. +// +// Return the result of the call on success, or raise an exception and return nil +// on failure. +// +// This is the equivalent of the Python expression: o(arg1, arg2, ...). +// +// llgo:link (*Object).CallFunctionObjArgs C.PyObject_CallFunctionObjArgs +func (o *Object) CallFunctionObjArgs(__llgo_va_list ...any) *Object { panic("unreachable") } + +// llgo:link (*Object).CallMethod C.PyObject_CallMethod +func (o *Object) CallMethod(name *c.Char, format *c.Char, __llgo_va_list ...any) *Object { + panic("unreachable") +} + +// llgo:link (*Object).CallMethodObjArgs C.PyObject_CallMethodObjArgs +func (o *Object) CallMethodObjArgs(name *Object, __llgo_va_list ...any) *Object { panic("unreachable") } + +// llgo:link (*Object).CallMethodNoArgs C.PyObject_CallMethodNoArgs +func (o *Object) CallMethodNoArgs(name *Object) *Object { panic("unreachable") } + +// llgo:link (*Object).CallMethodOneArg C.PyObject_CallMethodOneArg +func (o *Object) CallMethodOneArg(name, arg *Object) *Object { panic("unreachable") } + +// llgo:link (*Object).Vectorcall C.PyObject_Vectorcall +func (o *Object) Vectorcall(args **Object, nargs uintptr, kwnames *Object) *Object { + panic("unreachable") +} + +// llgo:link (*Object).VectorcallDict C.PyObject_VectorcallDict +func (o *Object) VectorcallDict(args **Object, nargs uintptr, kwdict *Object) *Object { + panic("unreachable") +} + +// llgo:link (*Object).VectorcallMethod C.PyObject_VectorcallMethod +func (o *Object) VectorcallMethod(name *Object, args **Object, nargs uintptr, kwnames *Object) *Object { + panic("unreachable") +} + +// ----------------------------------------------------------------------------- diff --git a/py/python.go b/py/python.go new file mode 100644 index 00000000..7140ab3b --- /dev/null +++ b/py/python.go @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package py + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +const ( + LLGoPackage = "link: $LLGO_PYTHON_ROOT/python3.12" +) + +// ----------------------------------------------------------------------------- + +//go:linkname SetProgramName C.Py_SetProgramName +func SetProgramName(name *c.Char) + +//go:linkname Initialize C.Py_Initialize +func Initialize() + +// This function works like Initialize() if initsigs is 1. +// If initsigs is 0, it skips initialization registration of signal handlers, +// which might be useful when Python is embedded. +// +//go:linkname InitializeEx C.Py_InitializeEx +func InitializeEx(initsigs c.Int) + +//go:linkname Finalize C.Py_Finalize +func Finalize() + +// ----------------------------------------------------------------------------- + +//go:linkname RunSimpleString C.PyRun_SimpleString +func RunSimpleString(command *c.Char) c.Int + +//go:linkname RunSimpleStringFlags C.PyRun_SimpleStringFlags +func RunSimpleStringFlags(command *c.Char, flags *CompilerFlags) c.Int + +//go:linkname RunSimpleFile C.PyRun_SimpleFile +func RunSimpleFile(fp c.FilePtr, filename *c.Char) c.Int + +//go:linkname RunSimpleFileFlags C.PyRun_SimpleFileFlags +func RunSimpleFileFlags(fp c.FilePtr, filename *c.Char, flags *CompilerFlags) c.Int + +// ----------------------------------------------------------------------------- + +type InputType c.Int + +const ( + SingleInput InputType = 256 // read code from i/o + FileInput InputType = 257 // read code from filename + EvalInput InputType = 258 // read code from string + // FuncTypeInput InputType = 345 +) + +// llgo:type C +type CompilerFlags struct { + CfFlags c.Int +} + +//go:linkname CompileString C.Py_CompileString +func CompileString(str, filename *c.Char, start InputType) *Object + +//go:linkname CompileStringFlags C.Py_CompileStringFlags +func CompileStringFlags(str, filename *c.Char, start InputType, flags *CompilerFlags) *Object + +//go:linkname CompileStringExFlags C.Py_CompileStringExFlags +func CompileStringExFlags(str, filename *c.Char, start InputType, flags *CompilerFlags, optimize c.Int) *Object + +// Parse and compile the Python source code in str, returning the resulting code object. +// The start token is given by start; this can be used to constrain the code which can be +// compiled and should be py.EvalInput, py.FileInput, or py.SingleInput. The filename +// specified by filename is used to construct the code object and may appear in tracebacks +// or SyntaxError exception messages. This returns NULL if the code cannot be parsed or +// compiled. +// +// The integer optimize specifies the optimization level of the compiler; a value of -1 +// selects the optimization level of the interpreter as given by -O options. Explicit levels +// are 0 (no optimization; __debug__ is true), 1 (asserts are removed, __debug__ is false) or +// 2 (docstrings are removed too). +// +//go:linkname CompileStringObject C.Py_CompileStringObject +func CompileStringObject(str *c.Char, filename *Object, start InputType, flags *CompilerFlags, optimize c.Int) *Object + +// ----------------------------------------------------------------------------- + +// This is a simplified interface to EvalCodeEx, with just the code object, and global and +// local variables. The other arguments are set to nil. +// +//go:linkname EvalCode C.PyEval_EvalCode +func EvalCode(code, globals, locals *Object) *Object + +// Evaluate a precompiled code object, given a particular environment for its evaluation. +// This environment consists of a dictionary of global variables, a mapping object of local +// variables, arrays of arguments, keywords and defaults, a dictionary of default values for +// keyword-only arguments and a closure tuple of cells. +// +//go:linkname EvalCodeEx C.PyEval_EvalCodeEx +func EvalCodeEx( + code, globals, locals *Object, + args *Object, argcount c.Int, kws *Object, kwcount c.Int, + defs *Object, defcount c.Int, kwdefs, closure *Object) *Object + +// -----------------------------------------------------------------------------