Compare commits

...

24 Commits

Author SHA1 Message Date
Kanika Kapoor
657e54d2f7
Merge c048ec9faf4bfe02da004dce69471897933c3617 into f319fedf7e8f57c72393cc5c7b7b02a75aaf3279 2026-01-06 18:20:56 +05:30
blueloveTH
f319fedf7e Update deploy.md 2026-01-06 17:41:13 +08:00
blueloveTH
32d104039c update docs 2026-01-06 16:46:44 +08:00
blueloveTH
f6f078807d bump c to 2026 2026-01-06 16:42:18 +08:00
blueloveTH
8bc7bf3bd7 Update bindings-cpp.md 2026-01-06 16:36:16 +08:00
blueloveTH
3c051df06a Create periphery.md 2026-01-06 16:32:25 +08:00
blueloveTH
5f8d9fefc3 update docs 2026-01-06 16:29:31 +08:00
blueloveTH
f10cc37c63 Create deploy.md 2026-01-06 16:17:01 +08:00
blueloveTH
a1bfebd30c Update 300_import.py 2026-01-06 15:18:04 +08:00
blueloveTH
f3bf7c6321 Update compileall.py 2026-01-06 15:15:50 +08:00
blueloveTH
3e182d5c6b add tests for .pyc 2026-01-06 15:11:39 +08:00
blueloveTH
d3d83bd126 minor fix 2026-01-06 14:56:27 +08:00
blueloveTH
b37d3cae86 revert main.c readfile 2026-01-06 14:45:51 +08:00
blueloveTH
82bd2838ce fix abs name 2026-01-06 14:32:00 +08:00
blueloveTH
45f18f8431 add some more registers 2026-01-06 14:09:11 +08:00
blueloveTH
0bdbf17d46 add mark for serializer 2026-01-06 13:53:26 +08:00
blueloveTH
07a65a8f21 add py_execo 2026-01-06 13:24:06 +08:00
blueloveTH
b4ba0d91d5 minor fix 2026-01-06 13:08:14 +08:00
blueloveTH
b775d239a5 fix import pyc 2026-01-06 11:24:33 +08:00
blueloveTH
3c379402c8 Update compileall.py 2026-01-05 19:58:00 +08:00
blueloveTH
3bac59701b Update compileall.py 2026-01-05 19:56:45 +08:00
blueloveTH
134144e6fa Update compileall.py 2026-01-05 19:51:46 +08:00
BLUELOVETH
5f7bf4924b
Support running compiled bytecodes (#421) 2026-01-05 19:40:21 +08:00
Kanika Kapoor
c048ec9faf Fix context manager __exit__ not being called on exception (#395)
Problem: When an exception occurs in a WITH block, __exit__ was not called,
preventing proper cleanup of context managers.

Solution:
1. Wrap WITH block body in try-except structure
2. On normal exit: call __exit__(None, None, None)
3. On exception: call __exit__ with exception info before re-raising

Changes:
- compiler.c: Wrap WITH body in try-except, ensure __exit__ called in both paths
- ceval.c: Update OP_WITH_EXIT to accept three arguments (exc_type, exc_val, exc_tb)
- tests/520_context.py: Add test to verify __exit__ called on exceptions
2025-12-27 01:12:15 +05:30
59 changed files with 1180 additions and 290 deletions

2
.gitignore vendored
View File

@ -42,3 +42,5 @@ docs/C-API/functions.md
cmake-build-*
tmp/
profiler_report.json
tmp/

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 blueloveTH
Copyright (c) 2026 blueloveTH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -49,17 +49,15 @@ These platforms are officially tested.
+ iOS 64-bit
+ Emscripten 32-bit
+ Raspberry Pi OS 64-bit
+ [Luckfox Pico SDK](https://github.com/LuckfoxTECH/luckfox-pico) 32-bit
On Windows platform, only MSVC compiler is officially supported.
## Quick Start
You have two options to integrate pkpy into your project.
#### Use the single header file
Download the `pocketpy.h` and `pocketpy.c` on our [GitHub Release](https://github.com/pocketpy/pocketpy/releases) page.
And `#include` it in your project.
#### Use CMake
#### Use CMake (Recommended)
Clone the whole repository as a submodule into your project,
In your CMakelists.txt, add the following lines:
@ -73,6 +71,11 @@ See [CMakeLists.txt](https://github.com/pocketpy/pocketpy/blob/main/CMakeLists.t
It is safe to use `main` branch in production if CI badge is green.
#### Use the single header file
Download the `pocketpy.h` and `pocketpy.c` on our [GitHub Release](https://github.com/pocketpy/pocketpy/releases) page.
And `#include` it in your project.
### Compile Flags
To compile it with your project, these flags must be set:

View File

@ -11,7 +11,7 @@ ROOT = 'include/pocketpy'
PUBLIC_HEADERS = ['config.h', 'export.h', 'vmath.h', 'pocketpy.h']
COPYRIGHT = '''/*
* Copyright (c) 2025 blueloveTH
* Copyright (c) 2026 blueloveTH
* Distributed Under The MIT License
* https://github.com/pocketpy/pocketpy
*/

36
compileall.py Normal file
View File

@ -0,0 +1,36 @@
import sys
import os
if len(sys.argv) != 4:
print('Usage: python compileall.py <pocketpy_executable> <source_dir> <output_dir>')
exit(1)
pkpy_exe = sys.argv[1]
source_dir = sys.argv[2]
output_dir = sys.argv[3]
def do_compile(src_path, dst_path):
assert os.path.isfile(src_path)
cmd = f'{pkpy_exe} --compile "{src_path}" "{dst_path}"'
if os.system(cmd) != 0:
print(src_path)
exit(1)
if os.path.isfile(source_dir):
if output_dir.endswith('.py'):
output_dir += 'c'
do_compile(source_dir, output_dir)
exit(0)
for root, _, files in os.walk(source_dir):
for file in files:
if not file.endswith('.py'):
continue
src_path = os.path.join(root, file)
dst_path = os.path.join(
output_dir,
os.path.relpath(root, source_dir),
file + 'c'
)
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
do_compile(src_path, dst_path)

View File

@ -7,6 +7,7 @@ order: 17
## Quick Start
pkpy provides a [pybind11](https://pybind11.readthedocs.io/en/stable/) compatible layer which allows users to do convenient bindings.
Header files are located in the `include/pybind11` directory. Make sure you have added `-Iinclude` to your compiler flags.
To begin with, use `py::scoped_interpreter guard{}` to start the interpreter before using any Python objects.
Or explicitly call `py::interpreter::initialize()` and `py::interpreter::finalize()`.

53
docs/features/deploy.md Normal file
View File

@ -0,0 +1,53 @@
---
icon: dot
title: Deploy Bytecodes
order: 81
---
!!!
The feature requires pocketpy version >= `2.1.7`
!!!
You can deploy your pocketpy program as `.pyc` files, which are compiled bytecodes with necessary metadata.
This slightly improves the loading speed of your program.
It also makes your users unable to get your source code directly, unless they do expensive reverse engineering.
To compile a `.py` file into a `.pyc` bytecode file, you need the command-line executable `main`,
which can be simply built by running `python cmake_build.py` in the repository root.
## Example
Once you have `main` executable, you can run the following command to compile `input_file.py`:
```sh
./main --compile input_file.py output_file.pyc
```
Alternatively, you can invoke the `compileall.py` script in the repository root.
It compiles all `.py` files in the specified directory into `.pyc` files.
```sh
python compileall.py ./main input_path output_path
```
## Running `.pyc` files
The command-line executable `main` can run `.pyc` files directly:
```sh
./main output_file.pyc
```
If you are using C-APIs, you can use the `py_execo()` function.
```c
/// Run a compiled code object.
PK_API bool py_execo(const void* data, int size, const char* filename, py_Ref module) PY_RAISE PY_RETURN;
```
## Trackback Support
Since `.pyc` files do not contain raw sources,
trackbacks will show line numbers but not the actual source code lines.

View File

@ -22,7 +22,7 @@ The easiest way to test a feature is to [try it on your browser](https://pocketp
1. Descriptor protocol `__get__` and `__set__`. However, `@property` is implemented.
2. `__slots__` in class definition.
3. `else` clause in try..except.
3. `else` and `finally` clause in try..except.
4. Inplace methods like `__iadd__` and `__imul__`.
5. `__del__` in class definition.
6. Multiple inheritance.

View File

@ -1,6 +1,6 @@
---
icon: dot
title: Threading
title: Compute Threads
---
pocketpy organizes its state by `VM` structure.

View File

@ -44,6 +44,9 @@ These platforms are officially tested.
+ iOS 64-bit
+ Emscripten 32-bit
+ Raspberry Pi OS 64-bit
+ [Luckfox Pico SDK](https://github.com/LuckfoxTECH/luckfox-pico) 32-bit
On Windows platform, only MSVC compiler is officially supported.
## Star the repo

View File

@ -11,7 +11,7 @@ pkpy is licensed under the [MIT License](http://opensource.org/licenses/MIT).
```
MIT License
Copyright (c) 2025 blueloveTH
Copyright (c) 2026 blueloveTH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

12
docs/modules/periphery.md Normal file
View File

@ -0,0 +1,12 @@
---
icon: package
label: periphery
---
!!!
This module is optional. Set option `PK_BUILD_MODULE_PERIPHERY` to `ON` in your `CMakeLists.txt` to enable it.
!!!
#### Source code
:::code source="../../include/typings/periphery.pyi" :::

View File

@ -6,12 +6,7 @@ label: Quick Start
You have two options to integrate pkpy into your project.
#### Use the single header file
Download the `pocketpy.h` and `pocketpy.c` on our [GitHub Release](https://github.com/pocketpy/pocketpy/releases) page.
And `#include` it in your project.
#### Use CMake
#### Use CMake (Recommended)
Clone the whole repository as a submodule into your project,
In your CMakelists.txt, add the following lines:
@ -25,6 +20,11 @@ See [CMakeLists.txt](https://github.com/pocketpy/pocketpy/blob/main/CMakeLists.t
It is safe to use `main` branch in production if CI badge is green.
#### Use the single header file
Download the `pocketpy.h` and `pocketpy.c` on our [GitHub Release](https://github.com/pocketpy/pocketpy/releases) page.
And `#include` it in your project.
### Compile flags
To compile it with your project, these flags must be set:

View File

@ -3,7 +3,7 @@ output: .retype
url: https://pocketpy.dev
branding:
title: pocketpy
label: v2.1.6
label: v2.1.7
logo: "./static/logo.png"
favicon: "./static/logo.png"
meta:

View File

@ -0,0 +1,58 @@
#pragma once
#include <stdint.h>
#include "pocketpy/common/vector.h"
#include "pocketpy/common/str.h"
typedef struct c11_serializer {
c11_vector data;
} c11_serializer;
void c11_serializer__ctor(c11_serializer* self, int16_t magic, int8_t major_ver, int8_t minor_ver);
void c11_serializer__dtor(c11_serializer* self);
void c11_serializer__write_mark(c11_serializer* self, char mark);
void c11_serializer__write_cstr(c11_serializer* self, const char*);
void c11_serializer__write_bytes(c11_serializer* self, const void* data, int size);
void* c11_serializer__submit(c11_serializer* self, int* size);
typedef struct c11_deserializer {
char error_msg[64];
const uint8_t* data;
int size;
int index;
int8_t major_ver;
int8_t minor_ver;
} c11_deserializer;
void c11_deserializer__ctor(c11_deserializer* self, const void* data, int size);
void c11_deserializer__dtor(c11_deserializer* self);
void c11_deserializer__consume_mark(c11_deserializer* self, char expected);
bool c11_deserializer__check_header(c11_deserializer* self, int16_t magic, int8_t major_ver, int8_t minor_ver_min);
const char* c11_deserializer__read_cstr(c11_deserializer* self);
void* c11_deserializer__read_bytes(c11_deserializer* self, int size);
#define DEF_ATOMIC_INLINE_RW(name, T) \
static inline void c11_serializer__write_##name(c11_serializer* self, T value){ \
c11_serializer__write_bytes(self, &value, sizeof(T)); \
} \
static inline T c11_deserializer__read_##name(c11_deserializer* self){ \
const void* p = self->data + self->index; \
self->index += sizeof(T); \
T retval;\
memcpy(&retval, p, sizeof(T)); \
return retval; \
}
DEF_ATOMIC_INLINE_RW(i8, int8_t)
DEF_ATOMIC_INLINE_RW(i16, int16_t)
DEF_ATOMIC_INLINE_RW(i32, int32_t)
DEF_ATOMIC_INLINE_RW(i64, int64_t)
DEF_ATOMIC_INLINE_RW(f32, float)
DEF_ATOMIC_INLINE_RW(f64, double)
DEF_ATOMIC_INLINE_RW(type, py_Type)
#undef DEF_ATOMIC_INLINE_RW

View File

@ -56,6 +56,7 @@ c11_sv c11_sv__slice2(c11_sv sv, int start, int stop);
c11_sv c11_sv__strip(c11_sv sv, c11_sv chars, bool left, bool right);
int c11_sv__index(c11_sv self, char c);
int c11_sv__rindex(c11_sv self, char c);
c11_sv c11_sv__filename(c11_sv self);
int c11_sv__index2(c11_sv self, c11_sv sub, int start);
int c11_sv__count(c11_sv self, c11_sv sub);
bool c11_sv__startswith(c11_sv self, c11_sv prefix);

View File

@ -22,6 +22,7 @@ bool c11_vector__contains(const c11_vector* self, void* elem);
void* c11_vector__submit(c11_vector* self, int* length);
void c11_vector__swap(c11_vector* self, c11_vector* other);
int c11_vector__nextcap(c11_vector* self);
void c11_vector__extend(c11_vector* self, const void* p, int size);
#define c11__getitem(T, self, index) (((T*)(self)->data)[index])
#define c11__setitem(T, self, index, value) ((T*)(self)->data)[index] = value;
@ -40,17 +41,6 @@ int c11_vector__nextcap(c11_vector* self);
#define c11_vector__back(T, self) (((T*)(self)->data)[(self)->length - 1])
#define c11_vector__extend(T, self, p, size) \
do { \
int min_capacity = (self)->length + (size); \
if((self)->capacity < min_capacity) { \
int nextcap = c11_vector__nextcap(self); \
c11_vector__reserve((self), c11__max(nextcap, min_capacity)); \
} \
memcpy((T*)(self)->data + (self)->length, (p), (size) * sizeof(T)); \
(self)->length += (size); \
} while(0)
#define c11_vector__insert(T, self, index, elem) \
do { \
if((self)->length == (self)->capacity) { \

View File

@ -1,10 +1,10 @@
#pragma once
// clang-format off
#define PK_VERSION "2.1.6"
#define PK_VERSION "2.1.7"
#define PK_VERSION_MAJOR 2
#define PK_VERSION_MINOR 1
#define PK_VERSION_PATCH 6
#define PK_VERSION_PATCH 7
/*************** feature settings ***************/
#ifndef PK_ENABLE_OS // can be overridden by cmake

View File

@ -58,11 +58,10 @@ typedef struct VM {
int recursion_depth;
int max_recursion_depth;
py_TValue reg[8]; // users' registers
void* ctx; // user-defined context
py_TValue reg[14]; // users' registers
void* ctx; // user-defined context
CachedNames cached_names;
NameDict compile_time_funcs;
py_StackRef curr_class;
py_StackRef curr_decl_based_function; // this is for get current function without frame

View File

@ -45,26 +45,26 @@ typedef enum Opcode {
} Opcode;
typedef struct Bytecode {
uint8_t op;
uint16_t op;
uint16_t arg;
} Bytecode;
void Bytecode__set_signed_arg(Bytecode* self, int arg);
bool Bytecode__is_forward_jump(const Bytecode* self);
typedef struct CodeBlock {
CodeBlockType type;
int parent; // parent index in blocks
int start; // start index of this block in codes, inclusive
int end; // end index of this block in codes, exclusive
int end2; // ...
} CodeBlock;
typedef struct BytecodeEx {
int lineno; // line number for each bytecode
int iblock; // block index
int32_t lineno; // line number for each bytecode
int32_t iblock; // block index
} BytecodeEx;
typedef struct CodeBlock {
int32_t type;
int32_t parent; // parent index in blocks
int32_t start; // start index of this block in codes, inclusive
int32_t end; // end index of this block in codes, exclusive
int32_t end2; // ...
} CodeBlock;
typedef struct CodeObject {
SourceData_ src;
c11_string* name;
@ -74,8 +74,8 @@ typedef struct CodeObject {
c11_vector /*T=py_TValue*/ consts; // constants
c11_vector /*T=py_Name*/ varnames; // local variables
c11_vector /*T=py_Name*/ names;
int nlocals;
c11_vector /*T=py_Name*/ names; // non-local names
int nlocals; // number of local variables
c11_smallmap_n2d varnames_inv;
c11_smallmap_n2d names_inv;
@ -93,6 +93,10 @@ int CodeObject__add_varname(CodeObject* self, py_Name name);
int CodeObject__add_name(CodeObject* self, py_Name name);
void CodeObject__gc_mark(const CodeObject* self, c11_vector* p_stack);
// Serialization
void* CodeObject__dumps(const CodeObject* co, int* size);
char* CodeObject__loads(const void* data, int size, const char* filename, CodeObject* out);
typedef struct FuncDeclKwArg {
int index; // index in co->varnames
py_Name key; // name of this argument
@ -103,14 +107,14 @@ typedef struct FuncDecl {
RefCounted rc;
CodeObject code; // strong ref
c11_vector /*T=int*/ args; // indices in co->varnames
c11_vector /*T=KwArg*/ kwargs; // indices in co->varnames
c11_vector /*T=int32_t*/ args; // indices in co->varnames
c11_vector /*T=FuncDeclKwArg*/ kwargs; // indices in co->varnames
int starred_arg; // index in co->varnames, -1 if no *arg
int starred_kwarg; // index in co->varnames, -1 if no **kwarg
bool nested; // whether this function is nested
const char* docstring; // docstring of this function (weak ref)
char* docstring;
FuncType type;
c11_smallmap_n2d kw_to_index;
@ -125,6 +129,7 @@ void FuncDecl__add_kwarg(FuncDecl* self, py_Name name, const py_TValue* value);
void FuncDecl__add_starred_arg(FuncDecl* self, py_Name name);
void FuncDecl__add_starred_kwarg(FuncDecl* self, py_Name name);
void FuncDecl__gc_mark(const FuncDecl* self, c11_vector* p_stack);
void FuncDecl__dtor(FuncDecl* self);
// runtime function
typedef struct Function {

View File

@ -79,8 +79,8 @@ typedef void (*py_TraceFunc)(py_Frame* frame, enum py_TraceEvent);
/// A struct contains the callbacks of the VM.
typedef struct py_Callbacks {
/// Used by `__import__` to load a source module.
char* (*importfile)(const char*);
/// Used by `__import__` to load a source or compiled module.
char* (*importfile)(const char* path, int* data_size);
/// Called before `importfile` to lazy-import a C module.
PY_MAYBENULL py_GlobalRef (*lazyimport)(const char*);
/// Used by `print` to output a string.
@ -182,6 +182,11 @@ PK_API bool py_compile(const char* source,
const char* filename,
enum py_CompileMode mode,
bool is_dynamic) PY_RAISE PY_RETURN;
/// Compile a `.py` file into a `.pyc` file.
PK_API bool py_compilefile(const char* src_path,
const char* dst_path) PY_RAISE;
/// Run a compiled code object.
PK_API bool py_execo(const void* data, int size, const char* filename, py_Ref module) PY_RAISE PY_RETURN;
/// Run a source string.
/// @param source source string.
/// @param filename filename (for error messages).
@ -300,10 +305,6 @@ PK_API void
py_bindproperty(py_Type type, const char* name, py_CFunction getter, py_CFunction setter);
/// Bind a magic method to type.
PK_API void py_bindmagic(py_Type type, py_Name name, py_CFunction f);
/// Bind a compile-time function via "decl-based" style.
PK_API void py_macrobind(const char* sig, py_CFunction f);
/// Get a compile-time function by name.
PK_API py_ItemRef py_macroget(py_Name name);
/************* Value Cast *************/
@ -440,6 +441,13 @@ PK_API py_GlobalRef py_retval();
#define py_r6() py_getreg(6)
#define py_r7() py_getreg(7)
#define py_tmpr0() py_getreg(8)
#define py_tmpr1() py_getreg(9)
#define py_tmpr2() py_getreg(10)
#define py_tmpr3() py_getreg(11)
#define py_sysr0() py_getreg(12) // for debugger
#define py_sysr1() py_getreg(13) // for pybind11
/// Get an item from the object's `__dict__`.
/// Return `NULL` if not found.
PK_API py_ItemRef py_getdict(py_Ref self, py_Name name);

View File

@ -16,6 +16,7 @@ OPCODE(LOAD_TRUE)
OPCODE(LOAD_FALSE)
/**************************/
OPCODE(LOAD_SMALL_INT)
OPCODE(LOAD_NAME_AS_INT)
/**************************/
OPCODE(LOAD_ELLIPSIS)
OPCODE(LOAD_FUNCTION)

View File

@ -44,8 +44,7 @@ struct object_pool {
};
static void initialize(int size) noexcept {
// use 8th register.
pool = py_getreg(7);
pool = py_sysr1();
py_newtuple(pool, size);
indices_ = new std::vector<int>(size, 0);
}

View File

@ -94,8 +94,8 @@ struct import_callback {
_importfile = nullptr;
};
static char* importfile(const char* path) {
if(value.empty()) return _importfile(path);
static char* importfile(const char* path, int* data_size) {
if(value.empty()) return _importfile(path, data_size);
// +1 for the null terminator
char* cstr = new char[value.size() + 1];

View File

@ -35,7 +35,7 @@ A new Flutter FFI plugin project.
s.prepare_command = <<-CMD
rm -rf pocketpy
git clone --branch v2.1.6 --depth 1 https://github.com/pocketpy/pocketpy.git
git clone --branch v2.1.7 --depth 1 https://github.com/pocketpy/pocketpy.git
cd pocketpy
git submodule update --init --recursive --depth 1
bash build_ios_libs.sh

View File

@ -357,6 +357,47 @@ class PocketpyBindings {
late final _py_compile = _py_compilePtr.asFunction<
bool Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, int, bool)>();
/// Compile a `.py` file into a `.pyc` file.
bool py_compilefile(
ffi.Pointer<ffi.Char> src_path,
ffi.Pointer<ffi.Char> dst_path,
) {
return _py_compilefile(
src_path,
dst_path,
);
}
late final _py_compilefilePtr = _lookup<
ffi.NativeFunction<
ffi.Bool Function(
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>)>>('py_compilefile');
late final _py_compilefile = _py_compilefilePtr.asFunction<
bool Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>)>();
/// Run a compiled code object.
bool py_execo(
ffi.Pointer<ffi.Void> data,
int size,
ffi.Pointer<ffi.Char> filename,
py_Ref module,
) {
return _py_execo(
data,
size,
filename,
module,
);
}
late final _py_execoPtr = _lookup<
ffi.NativeFunction<
ffi.Bool Function(ffi.Pointer<ffi.Void>, ffi.Int,
ffi.Pointer<ffi.Char>, py_Ref)>>('py_execo');
late final _py_execo = _py_execoPtr.asFunction<
bool Function(
ffi.Pointer<ffi.Void>, int, ffi.Pointer<ffi.Char>, py_Ref)>();
/// Run a source string.
/// @param source source string.
/// @param filename filename (for error messages).
@ -960,38 +1001,6 @@ class PocketpyBindings {
late final _py_bindmagic =
_py_bindmagicPtr.asFunction<void Function(int, py_Name, py_CFunction)>();
/// Bind a compile-time function via "decl-based" style.
void py_macrobind(
ffi.Pointer<ffi.Char> sig,
py_CFunction f,
) {
return _py_macrobind(
sig,
f,
);
}
late final _py_macrobindPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Pointer<ffi.Char>, py_CFunction)>>('py_macrobind');
late final _py_macrobind = _py_macrobindPtr
.asFunction<void Function(ffi.Pointer<ffi.Char>, py_CFunction)>();
/// Get a compile-time function by name.
py_ItemRef py_macroget(
py_Name name,
) {
return _py_macroget(
name,
);
}
late final _py_macrogetPtr =
_lookup<ffi.NativeFunction<py_ItemRef Function(py_Name)>>('py_macroget');
late final _py_macroget =
_py_macrogetPtr.asFunction<py_ItemRef Function(py_Name)>();
/// Convert an `int` object in python to `int64_t`.
int py_toint(
py_Ref arg0,
@ -3946,10 +3955,12 @@ abstract class py_TraceEvent {
/// A struct contains the callbacks of the VM.
final class py_Callbacks extends ffi.Struct {
/// Used by `__import__` to load a source module.
/// Used by `__import__` to load a source or compiled module.
external ffi.Pointer<
ffi.NativeFunction<
ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>> importfile;
ffi.NativeFunction<
ffi.Pointer<ffi.Char> Function(
ffi.Pointer<ffi.Char> path, ffi.Pointer<ffi.Int> data_size)>>
importfile;
/// Called before `importfile` to lazy-import a C module.
external ffi
@ -4035,13 +4046,13 @@ typedef py_CFunction = ffi.Pointer<
/// A pointer that represents a python identifier. For fast name resolution.
typedef py_Name = ffi.Pointer<py_OpaqueName>;
/// An item reference to a container object. It invalidates when the container is modified.
typedef py_ItemRef = ffi.Pointer<py_TValue>;
/// A generic destructor function.
typedef py_Dtor
= ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>;
/// An item reference to a container object. It invalidates when the container is modified.
typedef py_ItemRef = ffi.Pointer<py_TValue>;
/// A reference which has the same lifespan as the python object.
typedef py_ObjectRef = ffi.Pointer<py_TValue>;

View File

@ -32,7 +32,7 @@ A new Flutter FFI plugin project.
s.prepare_command = <<-CMD
rm -rf pocketpy
git clone --branch v2.1.6 --depth 1 https://github.com/pocketpy/pocketpy.git
git clone --branch v2.1.7 --depth 1 https://github.com/pocketpy/pocketpy.git
cd pocketpy
git submodule update --init --recursive --depth 1
bash build_darwin_libs.sh

View File

@ -1,6 +1,6 @@
name: pocketpy
description: A lightweight Python interpreter for game engines. It supports Android/iOS/Windows/Linux/MacOS.
version: 2.1.6
version: 2.1.7
homepage: https://pocketpy.dev
repository: https://github.com/pocketpy/pocketpy

View File

@ -21,7 +21,7 @@ set(PK_BUILD_SHARED_LIB ON CACHE BOOL "" FORCE)
FetchContent_Declare(
pocketpy
GIT_REPOSITORY https://github.com/pocketpy/pocketpy.git
GIT_TAG v2.1.6
GIT_TAG v2.1.7
)
FetchContent_MakeAvailable(pocketpy)

View File

@ -3,14 +3,12 @@ import sys
import time
import subprocess
pkpy_exe = 'main.exe' if sys.platform == 'win32' else './main'
def test_file(filepath, cpython=False):
if cpython:
return os.system("python " + filepath) == 0
if sys.platform == 'win32':
code = os.system("main.exe " + filepath)
else:
code = os.system("./main " + filepath)
code = os.system(pkpy_exe + ' ' + filepath)
if code != 0:
print('Return code:', code)
return code == 0
@ -18,7 +16,7 @@ def test_file(filepath, cpython=False):
def test_dir(path):
print("Testing directory:", path)
for filename in sorted(os.listdir(path)):
if not filename.endswith('.py'):
if not filename.endswith('.py') and not filename.endswith('.pyc'):
continue
filepath = os.path.join(path, filename)
print("> " + filepath, flush=True)
@ -76,6 +74,8 @@ exit()
print(res.stdout)
exit(1)
code = os.system(f'python compileall.py {pkpy_exe} tests tmp/tests')
assert code == 0
if len(sys.argv) == 2:
assert 'benchmark' in sys.argv[1]
@ -84,5 +84,11 @@ else:
test_dir('tests/')
test_repl()
if os.path.exists('tmp/tests'):
print('-' * 50)
time.sleep(3)
test_dir('tmp/tests/')
print("ALL TESTS PASSED")

View File

@ -10,7 +10,7 @@ PK_INLINE int c11__bit_length(unsigned long x) {
#if(defined(__clang__) || defined(__GNUC__))
return x == 0 ? 0 : (int)sizeof(unsigned long) * 8 - __builtin_clzl(x);
#elif defined(_MSC_VER)
static_assert(sizeof(unsigned long) <= 4, "unsigned long is greater than 4 bytes");
_Static_assert(sizeof(unsigned long) <= 4, "unsigned long is greater than 4 bytes");
unsigned long msb;
if(_BitScanReverse(&msb, x)) { return (int)msb + 1; }
return 0;

113
src/common/serialize.c Normal file
View File

@ -0,0 +1,113 @@
#include "pocketpy/common/serialize.h"
// >>> ord('🥕')
// 129365
// >>> ord('🍋')
// 127819
static uint32_t c11__checksum_32bit(const void* data, int size){
const uint8_t* p = (const uint8_t*)data;
uint32_t res = 0;
for(int i = 0; i < size; i++){
res = res * 31 + p[i];
}
return res;
}
void c11_serializer__ctor(c11_serializer* self, int16_t magic, int8_t major_ver, int8_t minor_ver){
c11_vector__ctor(&self->data, 1);
c11_serializer__write_i16(self, magic);
c11_serializer__write_i8(self, major_ver);
c11_serializer__write_i8(self, minor_ver);
}
void c11_serializer__dtor(c11_serializer* self){
c11_vector__dtor(&self->data);
}
void c11_serializer__write_mark(c11_serializer *self, char mark) {
c11_serializer__write_i8(self, (int8_t)mark);
}
void c11_serializer__write_cstr(c11_serializer *self, const char* cstr) {
int len = (int)strlen(cstr);
c11_serializer__write_bytes(self, cstr, len + 1);
}
void c11_serializer__write_bytes(c11_serializer* self, const void* data, int size){
c11_vector__extend(&self->data, data, size);
}
void* c11_serializer__submit(c11_serializer* self, int* size){
uint32_t checksum = c11__checksum_32bit(self->data.data, self->data.length);
c11_serializer__write_bytes(self, &checksum, sizeof(uint32_t));
return c11_vector__submit(&self->data, size);
}
void c11_deserializer__ctor(c11_deserializer* self, const void* data, int size){
self->data = (const uint8_t*)data;
self->size = size;
self->index = 0;
self->error_msg[0] = '\0';
}
void c11_deserializer__dtor(c11_deserializer* self){
// nothing to do
}
bool c11_deserializer__error(c11_deserializer* self, const char* msg){
snprintf(self->error_msg, sizeof(self->error_msg), "%s", msg);
return false;
}
void c11_deserializer__consume_mark(c11_deserializer* self, char expected) {
int8_t mark = c11_deserializer__read_i8(self);
if (mark != (int8_t)expected) {
c11__abort("internal error: deserialize mark mismatch: expected %c but got %c", expected, (char)mark);
}
}
bool c11_deserializer__check_header(c11_deserializer* self, int16_t magic, int8_t major_ver, int8_t minor_ver_min){
if(self->size < 8){
return c11_deserializer__error(self, "bad header: size < 8");
}
// read 16bit magic
int16_t file_magic = c11_deserializer__read_i16(self);
if(file_magic != magic){
return c11_deserializer__error(self, "bad header: magic mismatch");
}
// read 16bit version
self->major_ver = c11_deserializer__read_i8(self);
self->minor_ver = c11_deserializer__read_i8(self);
// check checksum
uint32_t checksum;
memcpy(&checksum, self->data + self->size - 4, sizeof(uint32_t));
uint32_t actual_checksum = c11__checksum_32bit(self->data, self->size - 4);
if(checksum != actual_checksum){
return c11_deserializer__error(self, "bad header: checksum mismatch");
}
// check version
if(self->major_ver != major_ver){
return c11_deserializer__error(self, "bad header: major version mismatch");
}
if(self->minor_ver < minor_ver_min){
// file_ver: 1.1, require_ver: 1.0 => ok
// file_ver: 1.1, require_ver: 1.1 => ok
// file_ver: 1.1, require_ver: 1.2 => error
return c11_deserializer__error(self, "bad header: minor version mismatch");
}
return true;
}
const char* c11_deserializer__read_cstr(c11_deserializer* self){
const char* p = (const char*)(self->data + self->index);
self->index += (strlen(p) + 1);
return p;
}
void* c11_deserializer__read_bytes(c11_deserializer* self, int size){
void* p = (void*)(self->data + self->index);
self->index += size;
return p;
}

View File

@ -8,10 +8,16 @@ static void SourceData__ctor(struct SourceData* self,
const char* filename,
enum py_CompileMode mode,
bool is_dynamic) {
self->filename = c11_string__new(filename);
self->mode = mode;
self->is_dynamic = is_dynamic;
self->filename = c11_string__new(filename);
c11_vector__ctor(&self->line_starts, sizeof(const char*));
if(!source) {
self->source = NULL;
return;
}
// Skip utf8 BOM if there is any.
if(strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3;
// Drop all '\r'
@ -34,13 +40,12 @@ static void SourceData__ctor(struct SourceData* self,
self->source->data[last_index + 1] = '\0';
}
self->is_dynamic = is_dynamic;
c11_vector__push(const char*, &self->line_starts, self->source->data);
}
static void SourceData__dtor(struct SourceData* self) {
c11_string__delete(self->filename);
c11_string__delete(self->source);
if(self->source) c11_string__delete(self->source);
c11_vector__dtor(&self->line_starts);
}
@ -59,7 +64,7 @@ bool SourceData__get_line(const struct SourceData* self,
int lineno,
const char** st,
const char** ed) {
if(lineno < 0) return false;
if(lineno < 0 || !self->source) return false;
lineno -= 1;
if(lineno < 0) lineno = 0;
const char* _start = c11__getitem(const char*, &self->line_starts, lineno);
@ -84,9 +89,9 @@ void SourceData__snapshot(const struct SourceData* self,
c11_sbuf__write_cstr(ss, name);
}
c11_sbuf__write_char(ss, '\n');
const char *st = NULL, *ed;
if(SourceData__get_line(self, lineno, &st, &ed)) {
c11_sbuf__write_char(ss, '\n');
while(st < ed && isblank(*st))
++st;
if(st < ed) {
@ -103,5 +108,5 @@ void SourceData__snapshot(const struct SourceData* self,
}
}
if(!st) { c11_sbuf__write_cstr(ss, " <?>"); }
// if(!st) { c11_sbuf__write_cstr(ss, " <?>"); }
}

View File

@ -79,7 +79,7 @@ void c11_sbuf__write_cstr(c11_sbuf* self, const char* str) {
}
void c11_sbuf__write_cstrn(c11_sbuf* self, const char* str, int n) {
c11_vector__extend(char, &self->data, str, n);
c11_vector__extend(&self->data, str, n);
}
void c11_sbuf__write_quoted(c11_sbuf* self, c11_sv sv, char quote) {

View File

@ -147,6 +147,14 @@ int c11_sv__rindex(c11_sv self, char c) {
return -1;
}
c11_sv c11_sv__filename(c11_sv self) {
int sep_index_1 = c11_sv__rindex(self, '/');
int sep_index_2 = c11_sv__rindex(self, '\\');
int sep_index = c11__max(sep_index_1, sep_index_2);
if(sep_index == -1) return self;
return c11_sv__slice(self, sep_index + 1);
}
int c11_sv__index2(c11_sv self, c11_sv sub, int start) {
if(sub.size == 0) return start;
int max_end = self.size - sub.size;

View File

@ -75,4 +75,16 @@ int c11_vector__nextcap(c11_vector* self) {
// increase by 25%
return self->capacity + (self->capacity >> 2);
}
}
}
void c11_vector__extend(c11_vector* self, const void* p, int size) {
if (size <= 0) return;
int min_capacity = self->length + size;
if(self->capacity < min_capacity) {
int nextcap = c11_vector__nextcap(self);
c11_vector__reserve((self), c11__max(nextcap, min_capacity));
}
void* dst = (char*)self->data + self->length * self->elem_size;
memcpy(dst, p, size * self->elem_size);
self->length += size;
}

View File

@ -76,8 +76,8 @@ static int Ctx__prepare_loop_divert(Ctx* self, int line, bool is_break);
static int Ctx__enter_block(Ctx* self, CodeBlockType type);
static void Ctx__exit_block(Ctx* self);
static int Ctx__emit_(Ctx* self, Opcode opcode, uint16_t arg, int line);
// static void Ctx__revert_last_emit_(Ctx* self);
static int Ctx__emit_int(Ctx* self, int64_t value, int line);
static int Ctx__emit_name(Ctx* self, py_Name name, int line);
static void Ctx__patch_jump(Ctx* self, int index);
static void Ctx__emit_jump(Ctx* self, int target, int line);
static int Ctx__add_varname(Ctx* self, py_Name name);
@ -1071,7 +1071,12 @@ void CallExpr__emit_(Expr* self_, Ctx* ctx) {
c11__foreach(Expr*, &self->args, e) { vtemit_(*e, ctx); }
c11__foreach(CallExprKwArg, &self->kwargs, e) {
Ctx__emit_int(ctx, (uintptr_t)e->key, self->line);
if(e->key == 0) {
// special key for **kwargs
Ctx__emit_int(ctx, 0, self->line);
} else {
Ctx__emit_name(ctx, e->key, self->line);
}
vtemit_(e->val, ctx);
}
int KWARGC = self->kwargs.length;
@ -1177,7 +1182,7 @@ static void Ctx__s_emit_decorators(Ctx* self, int count) {
}
static int Ctx__emit_(Ctx* self, Opcode opcode, uint16_t arg, int line) {
Bytecode bc = {(uint8_t)opcode, arg};
Bytecode bc = {(uint16_t)opcode, arg};
BytecodeEx bcx = {line, self->curr_iblock};
c11_vector__push(Bytecode, &self->co->codes, bc);
c11_vector__push(BytecodeEx, &self->co->codes_ex, bcx);
@ -1187,11 +1192,6 @@ static int Ctx__emit_(Ctx* self, Opcode opcode, uint16_t arg, int line) {
return i;
}
// static void Ctx__revert_last_emit_(Ctx* self) {
// c11_vector__pop(&self->co->codes);
// c11_vector__pop(&self->co->codes_ex);
// }
static int Ctx__emit_int(Ctx* self, int64_t value, int line) {
if(INT16_MIN <= value && value <= INT16_MAX) {
return Ctx__emit_(self, OP_LOAD_SMALL_INT, (uint16_t)value, line);
@ -1202,6 +1202,12 @@ static int Ctx__emit_int(Ctx* self, int64_t value, int line) {
}
}
static int Ctx__emit_name(Ctx* self, py_Name name, int line) {
int index = Ctx__add_name(self, name);
assert(index <= UINT16_MAX);
return Ctx__emit_(self, OP_LOAD_NAME_AS_INT, (uint16_t)index, line);
}
static void Ctx__patch_jump(Ctx* self, int index) {
Bytecode* co_codes = (Bytecode*)self->co->codes.data;
int target = self->co->codes.length;
@ -1220,7 +1226,10 @@ static int Ctx__add_varname(Ctx* self, py_Name name) {
return CodeObject__add_varname(self->co, name);
}
static int Ctx__add_name(Ctx* self, py_Name name) { return CodeObject__add_name(self->co, name); }
static int Ctx__add_name(Ctx* self, py_Name name) {
assert(name != 0);
return CodeObject__add_name(self->co, name);
}
static int Ctx__add_const_string(Ctx* self, c11_sv key) {
if(key.size > 100) {
@ -1881,65 +1890,11 @@ static Error* exprMap(Compiler* self) {
static Error* read_literal(Compiler* self, py_Ref out);
static Error* exprCompileTimeCall(Compiler* self, py_ItemRef func, int line) {
Error* err;
py_push(func);
py_pushnil();
uint16_t argc = 0;
uint16_t kwargc = 0;
// copied from `exprCall`
do {
if(curr()->type == TK_RPAREN) break;
if(curr()->type == TK_ID && next()->type == TK_ASSIGN) {
consume(TK_ID);
py_Name key = py_namev(Token__sv(prev()));
consume(TK_ASSIGN);
// k=v
py_pushname(key);
check(read_literal(self, py_pushtmp()));
kwargc += 1;
} else {
if(kwargc > 0) {
return SyntaxError(self, "positional argument follows keyword argument");
}
check(read_literal(self, py_pushtmp()));
argc += 1;
}
} while(match(TK_COMMA));
consume(TK_RPAREN);
py_StackRef p0 = py_peek(0);
bool ok = py_vectorcall(argc, kwargc);
if(!ok) {
char* msg = py_formatexc();
py_clearexc(p0);
err = SyntaxError(self, "compile-time call error:\n%s", msg);
PK_FREE(msg);
return err;
}
// TODO: optimize string dedup
int index = Ctx__add_const(ctx(), py_retval());
Ctx__s_push(ctx(), (Expr*)LoadConstExpr__new(line, index));
return NULL;
}
static Error* exprCall(Compiler* self) {
Error* err;
Expr* callable = Ctx__s_popx(ctx());
int line = prev()->line;
if(callable->vt->is_name) {
NameExpr* ne = (NameExpr*)callable;
py_ItemRef func = py_macroget(ne->name);
if(func != NULL) {
py_StackRef p0 = py_peek(0);
err = exprCompileTimeCall(self, func, line);
if(err != NULL) py_clearexc(p0);
return err;
}
}
CallExpr* e = CallExpr__new(line, callable);
Ctx__s_push(ctx(), (Expr*)e); // push onto the stack in advance
do {
@ -2425,7 +2380,7 @@ static Error* compile_function(Compiler* self, int decorators) {
py_TValue* consts = decl->code.consts.data;
py_TValue* c = &consts[codes[0].arg];
if(py_isstr(c)) {
decl->docstring = py_tostr(c);
decl->docstring = c11_strdup(py_tostr(c));
codes[0].op = OP_NO_OP;
codes[1].op = OP_NO_OP;
}
@ -2803,6 +2758,8 @@ static Error* compile_stmt(Compiler* self) {
case TK_WITH: {
check(EXPR(self)); // [ <expr> ]
Ctx__s_emit_top(ctx());
// Save context manager for later __exit__ call
Ctx__emit_(ctx(), OP_DUP_TOP, BC_NOARG, prev()->line);
Ctx__enter_block(ctx(), CodeBlockType_WITH);
NameExpr* as_name = NULL;
if(match(TK_AS)) {
@ -2811,17 +2768,33 @@ static Error* compile_stmt(Compiler* self) {
as_name = NameExpr__new(prev()->line, name, name_scope(self));
}
Ctx__emit_(ctx(), OP_WITH_ENTER, BC_NOARG, prev()->line);
// [ <expr> <expr>.__enter__() ]
if(as_name) {
bool ok = vtemit_store((Expr*)as_name, ctx());
vtdelete((Expr*)as_name);
if(!ok) return SyntaxError(self, "invalid syntax");
} else {
// discard `__enter__()`'s return value
Ctx__emit_(ctx(), OP_POP_TOP, BC_NOARG, BC_KEEPLINE);
}
// Wrap body in try-except to ensure __exit__ is called even on exception
Ctx__enter_block(ctx(), CodeBlockType_TRY);
Ctx__emit_(ctx(), OP_BEGIN_TRY, BC_NOARG, prev()->line);
check(compile_block_body(self));
Ctx__emit_(ctx(), OP_END_TRY, BC_NOARG, BC_KEEPLINE);
// Normal exit: call __exit__(None, None, None)
Ctx__emit_(ctx(), OP_LOAD_NONE, BC_NOARG, prev()->line);
Ctx__emit_(ctx(), OP_LOAD_NONE, BC_NOARG, prev()->line);
Ctx__emit_(ctx(), OP_LOAD_NONE, BC_NOARG, prev()->line);
Ctx__emit_(ctx(), OP_WITH_EXIT, BC_NOARG, prev()->line);
int jump_patch = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
Ctx__exit_block(ctx());
// Exception handler: call __exit__ with exception info, then re-raise
Ctx__emit_(ctx(), OP_PUSH_EXCEPTION, BC_NOARG, BC_KEEPLINE);
Ctx__emit_(ctx(), OP_LOAD_NONE, BC_NOARG, BC_KEEPLINE); // exc_type
Ctx__emit_(ctx(), OP_ROT_TWO, BC_NOARG, BC_KEEPLINE); // reorder: [cm, None, exc]
Ctx__emit_(ctx(), OP_LOAD_NONE, BC_NOARG, BC_KEEPLINE); // exc_tb
Ctx__emit_(ctx(), OP_WITH_EXIT, BC_NOARG, prev()->line);
Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE);
Ctx__patch_jump(ctx(), jump_patch);
Ctx__exit_block(ctx());
} break;
/*************************************************/

View File

@ -46,7 +46,7 @@ static struct c11_debugger {
c11_vector py_frames;
c11_smallmap_d2index scopes_query_cache;
#define python_vars py_r7()
#define python_vars py_sysr0()
} debugger;

View File

@ -189,6 +189,11 @@ __NEXT_STEP:
py_newint(SP()++, (int16_t)byte.arg);
DISPATCH();
}
case OP_LOAD_NAME_AS_INT: {
py_Name name = co_names[byte.arg];
py_newint(SP()++, (uintptr_t)name);
DISPATCH();
}
/*****************************************/
case OP_LOAD_ELLIPSIS: {
py_newellipsis(SP()++);
@ -1117,14 +1122,35 @@ __NEXT_STEP:
DISPATCH();
}
case OP_WITH_EXIT: {
// [expr]
py_push(TOP());
// Stack: [cm, exc_type, exc_val, exc_tb]
// Call cm.__exit__(exc_type, exc_val, exc_tb)
py_Ref exc_tb = TOP();
py_Ref exc_val = SECOND();
py_Ref exc_type = THIRD();
py_Ref cm = FOURTH();
// Save all values from stack
py_TValue saved_cm = *cm;
py_TValue saved_exc_type = *exc_type;
py_TValue saved_exc_val = *exc_val;
py_TValue saved_exc_tb = *exc_tb;
self->stack.sp -= 4;
// Push cm and get __exit__ method
py_push(&saved_cm);
if(!py_pushmethod(__exit__)) {
TypeError("'%t' object does not support the context manager protocol", TOP()->type);
TypeError("'%t' object does not support the context manager protocol", saved_cm.type);
goto __ERROR;
}
if(!py_vectorcall(0, 0)) goto __ERROR;
POP();
// Push arguments: exc_type, exc_val, exc_tb
PUSH(&saved_exc_type);
PUSH(&saved_exc_val);
PUSH(&saved_exc_tb);
// Call __exit__(exc_type, exc_val, exc_tb)
if(!py_vectorcall(3, 0)) goto __ERROR;
py_pop(); // discard return value
DISPATCH();
}
///////////

View File

@ -0,0 +1,39 @@
#include "pocketpy/pocketpy.h"
#include "pocketpy/interpreter/vm.h"
#include <errno.h>
bool py_compilefile(const char* src_path, const char* dst_path) {
// read
FILE* fp = fopen(src_path, "rb");
if(fp == NULL) {
const char* msg = strerror(errno);
return OSError("[Errno %d] %s: '%s'", errno, msg, src_path);
}
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char* buffer = PK_MALLOC(size + 1);
size = fread(buffer, 1, size, fp);
buffer[size] = 0;
fclose(fp);
// compile
bool ok = py_compile(buffer, src_path, EXEC_MODE, false);
PK_FREE(buffer);
if(!ok) return false;
// dump
py_assign(py_pushtmp(), py_retval());
int bc_size;
void* bc_data = CodeObject__dumps(py_touserdata(py_peek(-1)), &bc_size);
py_pop();
// write
fp = fopen(dst_path, "wb");
if(fp == NULL) {
PK_FREE(bc_data);
const char* msg = strerror(errno);
return OSError("[Errno %d] %s: '%s'", errno, msg, dst_path);
}
fwrite(bc_data, 1, bc_size, fp);
fclose(fp);
PK_FREE(bc_data);
return true;
}

View File

@ -12,7 +12,7 @@
#include <stdbool.h>
#include <assert.h>
static char* pk_default_importfile(const char* path) {
static char* pk_default_importfile(const char* path, int* data_size) {
#if PK_ENABLE_OS
FILE* f = fopen(path, "rb");
if(f == NULL) return NULL;
@ -23,6 +23,7 @@ static char* pk_default_importfile(const char* path) {
size = fread(buffer, 1, size, f);
buffer[size] = 0;
fclose(f);
if(data_size) *data_size = (int)size;
return buffer;
#else
return NULL;
@ -98,6 +99,8 @@ void VM__ctor(VM* self) {
self->recursion_depth = 0;
self->max_recursion_depth = 1000;
memset(self->reg, 0, sizeof(self->reg));
self->ctx = NULL;
self->curr_class = NULL;
self->curr_decl_based_function = NULL;
@ -112,7 +115,6 @@ void VM__ctor(VM* self) {
self->stack.end = self->stack.begin + PK_VM_STACK_SIZE;
CachedNames__ctor(&self->cached_names);
NameDict__ctor(&self->compile_time_funcs, PK_TYPE_ATTR_LOAD_FACTOR);
/* Init Builtin Types */
// 0: unused
@ -306,7 +308,6 @@ void VM__dtor(VM* self) {
BinTree__dtor(&self->modules);
FixedMemoryPool__dtor(&self->pool_frame);
CachedNames__dtor(&self->cached_names);
NameDict__dtor(&self->compile_time_funcs);
c11_vector__dtor(&self->types);
}
@ -671,12 +672,6 @@ void ManagedHeap__mark(ManagedHeap* self) {
CachedNames_KV* kv = c11_chunkedvector__at(&vm->cached_names.entries, i);
pk__mark_value(&kv->val);
}
// mark compile time functions
for(int i = 0; i < vm->compile_time_funcs.capacity; i++) {
NameDict_KV* kv = &vm->compile_time_funcs.items[i];
if(kv->key == NULL) continue;
pk__mark_value(&kv->value);
}
// mark types
int types_length = vm->types.length;
// 0-th type is placeholder

View File

@ -114,10 +114,8 @@ py_Ref py_name2ref(py_Name name) {
py_Ref res = CachedNames__try_get(d, name);
if(res != NULL) return res;
// not found, create a new one
py_StackRef tmp = py_pushtmp();
py_newstrv(tmp, py_name2sv(name));
CachedNames__set(d, name, tmp);
py_pop();
py_newstrv(py_tmpr0(), py_name2sv(name));
CachedNames__set(d, name, py_tmpr0());
return CachedNames__try_get(d, name);
}

View File

@ -72,7 +72,7 @@ static bool disassemble(CodeObject* co) {
pk_sprintf(&ss, " (%q)", py_tosv(path));
break;
}
case OP_LOAD_NAME:
case OP_LOAD_NAME: case OP_LOAD_NAME_AS_INT:
case OP_LOAD_GLOBAL:
case OP_LOAD_NONLOCAL:
case OP_STORE_GLOBAL:

View File

@ -59,7 +59,7 @@ static void PickleObject__dtor(PickleObject* self) {
static bool PickleObject__py_submit(PickleObject* self, py_OutRef out);
static void PickleObject__write_bytes(PickleObject* buf, const void* data, int size) {
c11_vector__extend(char, &buf->codes, data, size);
c11_vector__extend(&buf->codes, data, size);
}
static void c11_sbuf__write_type_path(c11_sbuf* path_buf, py_Type type) {

View File

@ -17,11 +17,12 @@ bool Bytecode__is_forward_jump(const Bytecode* self) {
(op == OP_FOR_ITER || op == OP_FOR_ITER_YIELD_VALUE);
}
static void FuncDecl__dtor(FuncDecl* self) {
void FuncDecl__dtor(FuncDecl* self) {
CodeObject__dtor(&self->code);
c11_vector__dtor(&self->args);
c11_vector__dtor(&self->kwargs);
c11_smallmap_n2d__dtor(&self->kw_to_index);
if(self->docstring) py_free(self->docstring);
}
FuncDecl_ FuncDecl__rcnew(SourceData_ src, c11_sv name) {
@ -30,7 +31,7 @@ FuncDecl_ FuncDecl__rcnew(SourceData_ src, c11_sv name) {
self->rc.dtor = (void (*)(void*))FuncDecl__dtor;
CodeObject__ctor(&self->code, src, name);
c11_vector__ctor(&self->args, sizeof(int));
c11_vector__ctor(&self->args, sizeof(int32_t));
c11_vector__ctor(&self->kwargs, sizeof(FuncDeclKwArg));
self->starred_arg = -1;
@ -66,8 +67,8 @@ bool FuncDecl__is_duplicated_arg(const FuncDecl* decl, py_Name name) {
}
void FuncDecl__add_arg(FuncDecl* self, py_Name name) {
int index = CodeObject__add_varname(&self->code, name);
c11_vector__push(int, &self->args, index);
int32_t index = CodeObject__add_varname(&self->code, name);
c11_vector__push(int32_t, &self->args, index);
}
void FuncDecl__add_kwarg(FuncDecl* self, py_Name name, const py_TValue* value) {
@ -89,32 +90,6 @@ void FuncDecl__add_starred_kwarg(FuncDecl* self, py_Name name) {
self->starred_kwarg = index;
}
FuncDecl_ FuncDecl__build(c11_sv name,
c11_sv* args,
int argc,
c11_sv starred_arg,
c11_sv* kwargs,
int kwargc,
py_Ref kwdefaults, // a tuple contains default values
c11_sv starred_kwarg,
const char* docstring) {
SourceData_ source = SourceData__rcnew("pass", "<bind>", EXEC_MODE, false);
FuncDecl_ decl = FuncDecl__rcnew(source, name);
for(int i = 0; i < argc; i++) {
FuncDecl__add_arg(decl, py_namev(args[i]));
}
if(starred_arg.size) { FuncDecl__add_starred_arg(decl, py_namev(starred_arg)); }
assert(py_istype(kwdefaults, tp_tuple));
assert(py_tuple_len(kwdefaults) == kwargc);
for(int i = 0; i < kwargc; i++) {
FuncDecl__add_kwarg(decl, py_namev(kwargs[i]), py_tuple_getitem(kwdefaults, i));
}
if(starred_kwarg.size) FuncDecl__add_starred_kwarg(decl, py_namev(starred_kwarg));
decl->docstring = docstring;
PK_DECREF(source);
return decl;
}
void CodeObject__ctor(CodeObject* self, SourceData_ src, c11_sv name) {
self->src = src;
PK_INCREF(src);

View File

@ -0,0 +1,411 @@
#include "pocketpy/objects/codeobject.h"
#include "pocketpy/common/serialize.h"
#include "pocketpy/common/utils.h"
// Magic number for CodeObject serialization: "CO" = 0x434F
#define CODEOBJECT_MAGIC 0x434F
#define CODEOBJECT_VER_MAJOR 1
#define CODEOBJECT_VER_MINOR 0
#define CODEOBJECT_VER_MINOR_MIN 0
// Forward declarations
static void FuncDecl__serialize(c11_serializer* s,
const FuncDecl* decl,
const struct SourceData* parent_src);
static FuncDecl_ FuncDecl__deserialize(c11_deserializer* d, SourceData_ embedded_src);
static void CodeObject__serialize(c11_serializer* s,
const CodeObject* co,
const struct SourceData* parent_src);
static CodeObject CodeObject__deserialize(c11_deserializer* d, const char* filename, SourceData_ embedded_src);
// Serialize a py_TValue constant
static void TValue__serialize(c11_serializer* s, py_Ref val) {
c11_serializer__write_type(s, val->type);
// 1. co_consts: int | float | str
// 2. function defaults: see `read_literal()` in compiler.c
switch(val->type) {
case tp_int: c11_serializer__write_i64(s, val->_i64); break;
case tp_float: c11_serializer__write_f64(s, val->_f64); break;
case tp_str: {
c11_sv sv = py_tosv((py_Ref)val);
c11_serializer__write_i32(s, sv.size);
c11_serializer__write_bytes(s, sv.data, sv.size);
break;
}
case tp_bool: {
bool value = py_tobool(val);
c11_serializer__write_i8(s, value ? 1 : 0);
break;
}
case tp_NoneType: break;
case tp_ellipsis: break;
case tp_tuple: {
int len = py_tuple_len(val);
c11_serializer__write_i32(s, len);
for(int i = 0; i < len; i++) {
py_Ref item = py_tuple_getitem(val, i);
TValue__serialize(s, item);
}
break;
}
default: c11__abort("TValue__serialize: invalid type '%s'", py_tpname(val->type));
}
}
// Deserialize a py_TValue constant
static void TValue__deserialize(c11_deserializer* d, py_OutRef out) {
py_Type type = c11_deserializer__read_type(d);
switch(type) {
case tp_int: {
py_i64 v = c11_deserializer__read_i64(d);
py_newint(out, v);
break;
}
case tp_float: {
py_f64 v = c11_deserializer__read_f64(d);
py_newfloat(out, v);
break;
}
case tp_str: {
int size = c11_deserializer__read_i32(d);
char* dst = py_newstrn(out, size);
char* src = c11_deserializer__read_bytes(d, size);
memcpy(dst, src, size);
break;
}
case tp_bool: {
bool v = c11_deserializer__read_i8(d) != 0;
py_newbool(out, v);
break;
}
case tp_NoneType: {
py_newnone(out);
break;
}
case tp_ellipsis: {
py_newellipsis(out);
break;
}
case tp_tuple: {
int len = c11_deserializer__read_i32(d);
py_newtuple(out, len);
for(int i = 0; i < len; i++) {
py_ItemRef item = py_tuple_getitem(out, i);
TValue__deserialize(d, item);
}
break;
}
default:
c11__abort("TValue__deserialize: invalid type '%s'", py_tpname(type));
}
}
// Serialize CodeObject
static void CodeObject__serialize(c11_serializer* s,
const CodeObject* co,
const struct SourceData* parent_src) {
// SourceData
if(parent_src) {
c11__rtassert(co->src == parent_src);
}
// name
c11_serializer__write_cstr(s, co->name->data);
// codes
_Static_assert(sizeof(Bytecode) == sizeof(uint16_t) * 2, "");
c11_serializer__write_i32(s, co->codes.length);
c11_serializer__write_mark(s, '[');
c11_serializer__write_bytes(s, co->codes.data, co->codes.length * sizeof(Bytecode));
c11_serializer__write_mark(s, ']');
// codes_ex
_Static_assert(sizeof(BytecodeEx) == sizeof(int32_t) * 2, "");
c11_serializer__write_i32(s, co->codes_ex.length);
c11_serializer__write_mark(s, '[');
c11_serializer__write_bytes(s, co->codes_ex.data, co->codes_ex.length * sizeof(BytecodeEx));
c11_serializer__write_mark(s, ']');
// consts
c11_serializer__write_i32(s, co->consts.length);
c11_serializer__write_mark(s, '[');
for(int i = 0; i < co->consts.length; i++) {
py_Ref val = c11__at(py_TValue, &co->consts, i);
TValue__serialize(s, val);
}
c11_serializer__write_mark(s, ']');
// varnames (as cstr via py_name2str)
c11_serializer__write_i32(s, co->varnames.length);
c11_serializer__write_mark(s, '[');
for(int i = 0; i < co->varnames.length; i++) {
py_Name name = c11__getitem(py_Name, &co->varnames, i);
c11_serializer__write_cstr(s, py_name2str(name));
}
c11_serializer__write_mark(s, ']');
// names (as cstr via py_name2str)
c11_serializer__write_i32(s, co->names.length);
c11_serializer__write_mark(s, '[');
for(int i = 0; i < co->names.length; i++) {
py_Name name = c11__getitem(py_Name, &co->names, i);
c11_serializer__write_cstr(s, py_name2str(name));
}
c11_serializer__write_mark(s, ']');
// nlocals
c11_serializer__write_i32(s, co->nlocals);
// blocks
_Static_assert(sizeof(CodeBlock) == sizeof(int32_t) * 5, "");
c11_serializer__write_i32(s, co->blocks.length);
c11_serializer__write_mark(s, '[');
c11_serializer__write_bytes(s, co->blocks.data, co->blocks.length * sizeof(CodeBlock));
c11_serializer__write_mark(s, ']');
// func_decls
c11_serializer__write_i32(s, co->func_decls.length);
c11_serializer__write_mark(s, '[');
for(int i = 0; i < co->func_decls.length; i++) {
const FuncDecl* decl = c11__getitem(FuncDecl_, &co->func_decls, i);
FuncDecl__serialize(s, decl, co->src);
c11_serializer__write_mark(s, '|');
}
c11_serializer__write_mark(s, ']');
// start_line, end_line
c11_serializer__write_i32(s, co->start_line);
c11_serializer__write_i32(s, co->end_line);
}
// Deserialize CodeObject (initialize co before calling)
static CodeObject CodeObject__deserialize(c11_deserializer* d, const char* filename, SourceData_ embedded_src) {
CodeObject co;
// SourceData
SourceData_ src;
if(embedded_src != NULL) {
c11__rtassert(filename == NULL);
src = embedded_src;
PK_INCREF(src);
} else {
c11__rtassert(filename != NULL);
src = SourceData__rcnew(NULL, filename, EXEC_MODE, false);
}
// name
const char* name = c11_deserializer__read_cstr(d);
c11_sv name_sv = {name, strlen(name)};
// Initialize the CodeObject
CodeObject__ctor(&co, src, name_sv);
PK_DECREF(src); // CodeObject__ctor increments ref count
// Clear the default root block that CodeObject__ctor adds
c11_vector__clear(&co.blocks);
// codes
int codes_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
c11_vector__extend(&co.codes,
c11_deserializer__read_bytes(d, codes_len * sizeof(Bytecode)),
codes_len);
c11_deserializer__consume_mark(d, ']');
// codes_ex
int codes_ex_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
c11_vector__extend(&co.codes_ex,
c11_deserializer__read_bytes(d, codes_ex_len * sizeof(BytecodeEx)),
codes_ex_len);
c11_deserializer__consume_mark(d, ']');
// consts
int consts_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
for(int i = 0; i < consts_len; i++) {
py_Ref p_val = c11_vector__emplace(&co.consts);
TValue__deserialize(d, p_val);
}
c11_deserializer__consume_mark(d, ']');
// varnames
int varnames_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
for(int i = 0; i < varnames_len; i++) {
const char* s = c11_deserializer__read_cstr(d);
py_Name n = py_name(s);
c11_vector__push(py_Name, &co.varnames, n);
c11_smallmap_n2d__set(&co.varnames_inv, n, i);
}
c11_deserializer__consume_mark(d, ']');
// names
int names_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
for(int i = 0; i < names_len; i++) {
const char* s = c11_deserializer__read_cstr(d);
py_Name n = py_name(s);
c11_vector__push(py_Name, &co.names, n);
c11_smallmap_n2d__set(&co.names_inv, n, i);
}
c11_deserializer__consume_mark(d, ']');
// nlocals
co.nlocals = c11_deserializer__read_i32(d);
// blocks
int blocks_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
c11_vector__extend(&co.blocks,
c11_deserializer__read_bytes(d, blocks_len * sizeof(CodeBlock)),
blocks_len);
c11_deserializer__consume_mark(d, ']');
// func_decls
int func_decls_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
for(int i = 0; i < func_decls_len; i++) {
FuncDecl_ decl = FuncDecl__deserialize(d, src);
c11_vector__push(FuncDecl_, &co.func_decls, decl);
c11_deserializer__consume_mark(d, '|');
}
c11_deserializer__consume_mark(d, ']');
// start_line, end_line
co.start_line = c11_deserializer__read_i32(d);
co.end_line = c11_deserializer__read_i32(d);
return co;
}
// Serialize FuncDecl
static void FuncDecl__serialize(c11_serializer* s,
const FuncDecl* decl,
const struct SourceData* parent_src) {
// CodeObject (embedded)
c11_serializer__write_mark(s, '{');
CodeObject__serialize(s, &decl->code, parent_src);
c11_serializer__write_mark(s, '}');
// args
c11_serializer__write_i32(s, decl->args.length);
c11_serializer__write_mark(s, '[');
c11_serializer__write_bytes(s, decl->args.data, decl->args.length * sizeof(int32_t));
c11_serializer__write_mark(s, ']');
// kwargs
c11_serializer__write_i32(s, decl->kwargs.length);
c11_serializer__write_mark(s, '[');
for(int i = 0; i < decl->kwargs.length; i++) {
FuncDeclKwArg* kw = c11__at(FuncDeclKwArg, &decl->kwargs, i);
c11_serializer__write_i32(s, kw->index);
c11_serializer__write_cstr(s, py_name2str(kw->key));
TValue__serialize(s, &kw->value);
}
c11_serializer__write_mark(s, ']');
// starred_arg, starred_kwarg
c11_serializer__write_i32(s, decl->starred_arg);
c11_serializer__write_i32(s, decl->starred_kwarg);
// nested
c11_serializer__write_i8(s, decl->nested ? 1 : 0);
// docstring
int has_docstring = decl->docstring != NULL ? 1 : 0;
c11_serializer__write_i8(s, has_docstring);
if(has_docstring) c11_serializer__write_cstr(s, decl->docstring);
// type
c11_serializer__write_i8(s, (int8_t)decl->type);
}
// Deserialize FuncDecl
static FuncDecl_ FuncDecl__deserialize(c11_deserializer* d, SourceData_ embedded_src) {
FuncDecl_ self = PK_MALLOC(sizeof(FuncDecl));
self->rc.count = 1;
self->rc.dtor = (void (*)(void*))FuncDecl__dtor;
c11_vector__ctor(&self->args, sizeof(int32_t));
c11_vector__ctor(&self->kwargs, sizeof(FuncDeclKwArg));
c11_smallmap_n2d__ctor(&self->kw_to_index);
// CodeObject (embedded)
c11_deserializer__consume_mark(d, '{');
self->code = CodeObject__deserialize(d, NULL, embedded_src);
c11_deserializer__consume_mark(d, '}');
// args
int args_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
c11_vector__extend(&self->args,
c11_deserializer__read_bytes(d, args_len * sizeof(int32_t)),
args_len);
c11_deserializer__consume_mark(d, ']');
// kwargs
int kwargs_len = c11_deserializer__read_i32(d);
c11_deserializer__consume_mark(d, '[');
for(int i = 0; i < kwargs_len; i++) {
FuncDeclKwArg* kw = c11_vector__emplace(&self->kwargs);
kw->index = c11_deserializer__read_i32(d);
const char* key_str = c11_deserializer__read_cstr(d);
kw->key = py_name(key_str);
TValue__deserialize(d, &kw->value);
c11_smallmap_n2d__set(&self->kw_to_index, kw->key, kw->index);
}
c11_deserializer__consume_mark(d, ']');
// starred_arg
self->starred_arg = c11_deserializer__read_i32(d);
// starred_kwarg
self->starred_kwarg = c11_deserializer__read_i32(d);
// nested
self->nested = c11_deserializer__read_i8(d) != 0;
// docstring
int has_docstring = c11_deserializer__read_i8(d);
if(has_docstring) {
const char* docstring = c11_deserializer__read_cstr(d);
self->docstring = c11_strdup(docstring);
} else {
self->docstring = NULL;
}
// type
self->type = (FuncType)c11_deserializer__read_i8(d);
return self;
}
// Public API: Serialize CodeObject to bytes
void* CodeObject__dumps(const CodeObject* co, int* size) {
c11_serializer s;
c11_serializer__ctor(&s, CODEOBJECT_MAGIC, CODEOBJECT_VER_MAJOR, CODEOBJECT_VER_MINOR);
CodeObject__serialize(&s, co, NULL);
return c11_serializer__submit(&s, size);
}
// Public API: Deserialize CodeObject from bytes
// Returns error message or NULL on success
char* CodeObject__loads(const void* data, int size, const char* filename, CodeObject* out) {
c11_deserializer d;
c11_deserializer__ctor(&d, data, size);
if(!c11_deserializer__check_header(&d,
CODEOBJECT_MAGIC,
CODEOBJECT_VER_MAJOR,
CODEOBJECT_VER_MINOR_MIN)) {
char* error_msg = c11_strdup(d.error_msg);
c11_deserializer__dtor(&d);
return error_msg;
}
*out = CodeObject__deserialize(&d, filename, NULL);
c11_deserializer__dtor(&d);
return NULL;
}
#undef CODEOBJECT_MAGIC
#undef CODEOBJECT_VER_MAJOR
#undef CODEOBJECT_VER_MINOR
#undef CODEOBJECT_VER_MINOR_MIN

View File

@ -46,16 +46,3 @@ void py_bindmagic(py_Type type, py_Name name, py_CFunction f) {
py_Ref tmp = py_emplacedict(py_tpobject(type), name);
py_newnativefunc(tmp, f);
}
void py_macrobind(const char* sig, py_CFunction f) {
py_Ref tmp = py_pushtmp();
py_Name name = py_newfunction(tmp, sig, f, NULL, 0);
NameDict__set(&pk_current_vm->compile_time_funcs, name, tmp);
py_pop();
}
py_ItemRef py_macroget(py_Name name) {
NameDict* d = &pk_current_vm->compile_time_funcs;
if(d->length == 0) return NULL;
return NameDict__try_get(d, name);
}

View File

@ -149,6 +149,20 @@ bool py_exec(const char* source, const char* filename, enum py_CompileMode mode,
return ok;
}
bool py_execo(const void* data, int size, const char* filename, py_Ref module) {
CodeObject co;
char* err = CodeObject__loads(data, size, filename, &co);
if(err == NULL) {
c11__rtassert(co.src->mode == EXEC_MODE);
c11__rtassert(co.src->is_dynamic == false);
bool ok = pk_exec(&co, module);
CodeObject__dtor(&co);
return ok;
} else {
return RuntimeError("bad code object %s: %s", filename, err);
}
}
bool py_eval(const char* source, py_Ref module) {
return py_exec(source, "<string>", EVAL_MODE, module);
}

View File

@ -1,8 +1,8 @@
#include "pocketpy/interpreter/vm.h"
py_Ref py_getreg(int i) { return pk_current_vm->reg + i; }
PK_INLINE py_Ref py_getreg(int i) { return pk_current_vm->reg + i; }
void py_setreg(int i, py_Ref val) { pk_current_vm->reg[i] = *val; }
PK_INLINE void py_setreg(int i, py_Ref val) { pk_current_vm->reg[i] = *val; }
PK_INLINE py_Ref py_retval() { return &pk_current_vm->last_retval; }

View File

@ -32,6 +32,10 @@ void py_initialize() {
_Static_assert(sizeof(py_TValue) == 24, "sizeof(py_TValue) != 24");
_Static_assert(offsetof(py_TValue, extra) == 4, "offsetof(py_TValue, extra) != 4");
// check sizes
_Static_assert(sizeof(float) == 4, "");
_Static_assert(sizeof(double) == 8, "");
pk_current_vm = pk_all_vm[0] = &pk_default_vm;
// initialize some convenient references
@ -117,7 +121,7 @@ void py_sys_setargv(int argc, char** argv) {
py_GlobalRef sys = py_getmodule("sys");
py_Ref argv_list = py_getdict(sys, py_name("argv"));
py_list_clear(argv_list);
for(int i = 0; i < argc; i++) {
for(int i = 1; i < argc; i++) {
py_newstr(py_list_emplace(argv_list), argv[i]);
}
}

View File

@ -78,8 +78,10 @@ int py_import(const char* path_cstr) {
while(dot_count < path.size && path.data[dot_count] == '.')
dot_count++;
c11_sv top_filename = c11_string__sv(vm->top_frame->co->src->filename);
int is_init = c11_sv__endswith(top_filename, (c11_sv){"__init__.py", 11});
// */__init__.py[c]
c11_sv top_filepath = c11_string__sv(vm->top_frame->co->src->filename);
c11_sv top_filename = c11_sv__filename(top_filepath);
int is_init = c11__sveq2(top_filename, "__init__.py") || c11__sveq2(top_filename, "__init__.pyc");
py_ModuleInfo* mi = py_touserdata(vm->top_frame->module);
c11_sv package_sv = c11_string__sv(mi->path);
@ -140,20 +142,39 @@ int py_import(const char* path_cstr) {
c11_string* filename = c11_string__new3("%s.py", slashed_path->data);
bool need_free = true;
bool is_pyc = false;
const char* data = load_kPythonLib(path_cstr);
int data_size = -1;
if(data != NULL) {
need_free = false;
goto __SUCCESS;
}
data = vm->callbacks.importfile(filename->data);
data = vm->callbacks.importfile(filename->data, &data_size);
if(data != NULL) goto __SUCCESS;
c11_string__delete(filename);
filename = c11_string__new3("%s.pyc", slashed_path->data);
data = vm->callbacks.importfile(filename->data, &data_size);
if(data != NULL) {
is_pyc = true;
goto __SUCCESS;
}
c11_string__delete(filename);
filename = c11_string__new3("%s%c__init__.py", slashed_path->data, PK_PLATFORM_SEP);
data = vm->callbacks.importfile(filename->data);
data = vm->callbacks.importfile(filename->data, &data_size);
if(data != NULL) goto __SUCCESS;
c11_string__delete(filename);
filename = c11_string__new3("%s%c__init__.pyc", slashed_path->data, PK_PLATFORM_SEP);
data = vm->callbacks.importfile(filename->data, &data_size);
if(data != NULL) {
is_pyc = true;
goto __SUCCESS;
}
c11_string__delete(filename);
c11_string__delete(slashed_path);
// not found
@ -162,8 +183,15 @@ int py_import(const char* path_cstr) {
__SUCCESS:
do {
} while(0);
py_GlobalRef mod = py_newmodule(path_cstr);
bool ok = py_exec((const char*)data, filename->data, EXEC_MODE, mod);
bool ok;
if(is_pyc) {
ok = py_execo(data, data_size, filename->data, mod);
} else {
ok = py_exec(data, filename->data, EXEC_MODE, mod);
}
py_assign(py_retval(), mod);
c11_string__delete(filename);
@ -180,11 +208,13 @@ bool py_importlib_reload(py_Ref module) {
c11_sv path = c11_string__sv(mi->path);
c11_string* slashed_path = c11_sv__replace(path, '.', PK_PLATFORM_SEP);
c11_string* filename = c11_string__new3("%s.py", slashed_path->data);
char* data = vm->callbacks.importfile(filename->data);
// Here we only consider source modules.
// Because compiled modules have no source file (it cannot be reloaded)
char* data = vm->callbacks.importfile(filename->data, NULL);
if(data == NULL) {
c11_string__delete(filename);
filename = c11_string__new3("%s%c__init__.py", slashed_path->data, PK_PLATFORM_SEP);
data = vm->callbacks.importfile(filename->data);
data = vm->callbacks.importfile(filename->data, NULL);
}
c11_string__delete(slashed_path);
if(data == NULL) return ImportError("module '%v' not found", path);

View File

@ -203,8 +203,8 @@ static bool list__add__(int argc, py_Ref argv) {
List* list_1 = py_touserdata(_1);
py_newlist(py_retval());
List* list = py_touserdata(py_retval());
c11_vector__extend(py_TValue, list, list_0->data, list_0->length);
c11_vector__extend(py_TValue, list, list_1->data, list_1->length);
c11_vector__extend(list, list_0->data, list_0->length);
c11_vector__extend(list, list_1->data, list_1->length);
} else {
py_newnotimplemented(py_retval());
}
@ -221,7 +221,7 @@ static bool list__mul__(int argc, py_Ref argv) {
List* list = py_touserdata(py_retval());
List* list_0 = py_touserdata(_0);
for(int i = 0; i < n; i++) {
c11_vector__extend(py_TValue, list, list_0->data, list_0->length);
c11_vector__extend(list, list_0->data, list_0->length);
}
} else {
py_newnotimplemented(py_retval());
@ -264,7 +264,7 @@ static bool list_extend(int argc, py_Ref argv) {
py_TValue* p;
int length = pk_arrayview(py_arg(1), &p);
if(length == -1) return TypeError("extend() argument must be a list or tuple");
c11_vector__extend(py_TValue, self, p, length);
c11_vector__extend(self, p, length);
py_newnone(py_retval());
return true;
}
@ -293,7 +293,7 @@ static bool list_copy(int argc, py_Ref argv) {
py_newlist(py_retval());
List* self = py_touserdata(py_arg(0));
List* list = py_touserdata(py_retval());
c11_vector__extend(py_TValue, list, self->data, self->length);
c11_vector__extend(list, self->data, self->length);
return true;
}

View File

@ -121,7 +121,7 @@ py_Name py_newfunction(py_OutRef out,
c11__abort("py_newfunction(): invalid signature '%s'", sig);
}
FuncDecl_ decl = c11__getitem(FuncDecl_, &code.func_decls, 0);
decl->docstring = docstring;
if(docstring) decl->docstring = c11_strdup(docstring);
// construct the function
Function* ud = py_newobject(out, tp_function, slots, sizeof(Function));
Function__ctor(ud, decl, NULL, NULL);

View File

@ -9,18 +9,17 @@
#include <windows.h>
#endif
static char* read_file(const char* path) {
FILE* file = fopen(path, "rb");
if(file == NULL) {
printf("Error: file not found\n");
return NULL;
}
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
static char* readfile(const char* path, int* data_size) {
FILE* f = fopen(path, "rb");
if(f == NULL) return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
char* buffer = PK_MALLOC(size + 1);
size = fread(buffer, 1, size, file);
size = fread(buffer, 1, size, f);
buffer[size] = 0;
fclose(f);
if(data_size) *data_size = (int)size;
return buffer;
}
@ -34,7 +33,9 @@ int main(int argc, char** argv) {
bool profile = false;
bool debug = false;
const char* filename = NULL;
bool compile = false;
const char* arg1 = NULL;
const char* arg2 = NULL;
for(int i = 1; i < argc; i++) {
if(strcmp(argv[i], "--profile") == 0) {
@ -45,11 +46,19 @@ int main(int argc, char** argv) {
debug = true;
continue;
}
if(filename == NULL) {
filename = argv[i];
if(strcmp(argv[i], "--compile") == 0) {
compile = true;
continue;
}
printf("Usage: pocketpy [--profile] [--debug] filename\n");
if(arg1 == NULL) {
arg1 = argv[i];
continue;
}
if(arg2 == NULL) {
arg2 = argv[i];
continue;
}
printf("Usage: pocketpy [--profile] [--debug] [--compile] filename\n");
}
if(debug && profile) {
@ -57,9 +66,22 @@ int main(int argc, char** argv) {
return 1;
}
if(compile && (debug || profile)) {
printf("Error: --compile cannot be used with --debug or --profile.\n");
return 1;
}
py_initialize();
py_sys_setargv(argc, argv);
if(compile) {
bool ok = py_compilefile(arg1, arg2);
if(!ok) py_printexc();
py_finalize();
return ok ? 0 : 1;
}
const char* filename = arg1;
if(filename == NULL) {
if(profile) printf("Warning: --profile is ignored in REPL mode.\n");
if(debug) printf("Warning: --debug is ignored in REPL mode.\n");
@ -92,9 +114,28 @@ int main(int argc, char** argv) {
if(profile) py_profiler_begin();
if(debug) py_debugger_waitforattach("127.0.0.1", 6110);
char* source = read_file(filename);
if(source) {
if(!py_exec(source, filename, EXEC_MODE, NULL)) py_printexc();
int data_size;
char* data = readfile(filename, &data_size);
// check filename endswith .pyc
bool is_pyc = false;
int filename_len = (int)strlen(filename);
if(filename_len >= 4) {
if(filename[filename_len - 4] == '.' &&
filename[filename_len - 3] == 'p' &&
filename[filename_len - 2] == 'y' &&
filename[filename_len - 1] == 'c') {
is_pyc = true;
}
}
if(data) {
bool ok;
if(is_pyc) {
ok = py_execo(data, data_size, filename, NULL);
} else {
ok = py_exec(data, filename, EXEC_MODE, NULL);
}
if(!ok) py_printexc();
if(profile) {
char* json_report = py_profiler_report();
@ -105,8 +146,11 @@ int main(int argc, char** argv) {
}
PK_FREE(json_report);
}
PK_FREE(source);
PK_FREE(data);
} else {
printf("Error: cannot open file '%s'\n", filename);
py_finalize();
return 1;
}
}

View File

@ -3,7 +3,14 @@ try:
except ImportError:
exit(0)
os.chdir('tests')
import sys
is_pyc = sys.argv[0].endswith('.pyc')
if is_pyc:
os.chdir('tmp/tests')
else:
os.chdir('tests')
assert os.getcwd().endswith('tests')
import test1
@ -49,8 +56,11 @@ from math import (
cos
)
# test reload (dummy)
import importlib
importlib.reload(test2.a)
assert __import__('math').pi > 3
# test reload (dummy)
if not is_pyc:
import importlib
importlib.reload(test2.a)

View File

@ -27,4 +27,29 @@ assert path == ['enter', 'in', 'exit']
path.clear()
# Test that __exit__ is called even when an exception occurs
class B:
def __init__(self):
self.path = []
def __enter__(self):
path.append('enter')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
path.append('exit')
if exc_type is not None:
path.append('exception')
return False # propagate exception
try:
with B():
path.append('before_raise')
raise ValueError('test')
path.append('after_raise') # should not be reached
except ValueError:
pass
assert path == ['enter', 'before_raise', 'exit', 'exception'], f"Expected ['enter', 'before_raise', 'exit', 'exception'], got {path}"

View File

@ -1,4 +1,7 @@
import sys
assert len(sys.argv) == 2
assert (sys.argv[1] == 'tests/801_sys.py'), sys.argv
assert (sys.argv in [
['tests/801_sys.py'],
['tmp/tests/801_sys.pyc'],
]), sys.argv

View File

@ -1,4 +1,8 @@
import traceback
import sys
if sys.argv[0].endswith('.pyc'):
exit()
try:
a = {'123': 4}
@ -7,7 +11,7 @@ except KeyError:
actual = traceback.format_exc()
expected = '''Traceback (most recent call last):
File "tests/802_traceback.py", line 5
File "tests/802_traceback.py", line 9
b = a[6]
KeyError: 6'''

26
tests/922_py_compile.py Normal file
View File

@ -0,0 +1,26 @@
try:
import os
except ImportError:
print('os is not enabled, skipping test...')
exit(0)
import sys
if sys.platform == 'win32':
exe_name = 'main.exe'
else:
exe_name = './main'
assert os.system(f'{exe_name} --compile python/heapq.py heapq1.pyc') == 0
assert os.path.exists('heapq1.pyc')
import heapq1
import heapq
a = [1, 2, -3, 2, 1, 5, 11, 123] * 10
b = a.copy()
heapq.heapify(a)
heapq1.heapify(b)
assert a == b
os.remove('heapq1.pyc')