Compare commits

...

11 Commits

Author SHA1 Message Date
blueloveTH
3d5a59bd15 ... 2024-08-14 19:59:54 +08:00
blueloveTH
3cc46df47b ... 2024-08-14 19:51:42 +08:00
blueloveTH
0844dbaff3 ... 2024-08-14 19:46:30 +08:00
blueloveTH
35eb793a08 ... 2024-08-14 19:44:09 +08:00
blueloveTH
d709d7b531 ... 2024-08-14 19:38:51 +08:00
blueloveTH
5f7006e2cb ... 2024-08-14 19:33:29 +08:00
blueloveTH
13f691bc37 ... 2024-08-14 19:21:44 +08:00
blueloveTH
d08ecbffd8 ... 2024-08-14 18:31:02 +08:00
blueloveTH
6ba0aff3a4 ... 2024-08-14 18:30:38 +08:00
blueloveTH
bc991249b9 ... 2024-08-14 18:28:31 +08:00
blueloveTH
b94b535de8 ... 2024-08-14 12:37:43 +08:00
24 changed files with 510 additions and 540 deletions

201
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,201 @@
name: build
on:
push:
paths-ignore:
- 'docs/**'
- 'web/**'
- '**.md'
pull_request:
paths-ignore:
- 'docs/**'
- 'web/**'
- '**.md'
jobs:
# build_win32_amalgamated:
# runs-on: windows-latest
# steps:
# - uses: actions/checkout@v4
# - uses: ilammy/msvc-dev-cmd@v1
# - name: Compile
# shell: powershell
# run: |
# python amalgamate.py
# cd amalgamated
# cl.exe /std:c++17 /EHsc /utf-8 /Ox /I. /DPK_ENABLE_OS=1 main.cpp /link /out:pkpy.exe
build_win32:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: ilammy/msvc-dev-cmd@v1
- name: Compile
shell: bash
run: |
mkdir -p output/x86_64
python cmake_build.py
cp main.exe output/x86_64
cp pocketpy.dll output/x86_64
- uses: actions/upload-artifact@v4
with:
name: windows
path: output
- name: Unit Test
run: python scripts/run_tests.py
- name: Benchmark
run: python scripts/run_tests.py benchmark
build_linux:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Setup Clang
uses: egor-tensin/setup-clang@v1
with:
version: 15
platform: x64
- name: Install dependencies
run: sudo apt-get install -y libclang-rt-15-dev
- name: Unit Test with Coverage
run: bash run_tests.sh
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: .coverage
if: github.ref == 'refs/heads/main'
- name: Compile and Test
run: |
mkdir -p output/x86_64
python cmake_build.py
python scripts/run_tests.py
cp main output/x86_64
cp libpocketpy.so output/x86_64
env:
CC: clang
- uses: actions/upload-artifact@v4
with:
name: linux
path: output
- name: Benchmark
run: python scripts/run_tests.py benchmark
build_linux_x86:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Alpine Linux for aarch64
uses: jirutka/setup-alpine@v1
with:
arch: x86
packages: gcc g++ make cmake libc-dev linux-headers python3
- name: Build and Test
run: |
uname -m
python cmake_build.py
python scripts/run_tests.py
python scripts/run_tests.py benchmark
shell: alpine.sh --root {0}
build_darwin:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Compile and Test
run: |
python cmake_build.py
python scripts/run_tests.py
- name: Benchmark
run: python scripts/run_tests.py benchmark
# - run: |
# python amalgamate.py
# cd plugins/macos/pocketpy
# mkdir output
# xcodebuild clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
# cp -r build/Release/pocketpy.bundle output
# - uses: actions/upload-artifact@v4
# with:
# name: macos
# path: plugins/macos/pocketpy/output
build_android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r23
local-cache: false
add-to-path: false
- name: Compile Shared Library
run: |
bash build_android.sh arm64-v8a
bash build_android.sh armeabi-v7a
bash build_android.sh x86_64
mkdir -p output/arm64-v8a
mkdir -p output/armeabi-v7a
mkdir -p output/x86_64
cp build/android/arm64-v8a/libpocketpy.so output/arm64-v8a
cp build/android/armeabi-v7a/libpocketpy.so output/armeabi-v7a
cp build/android/x86_64/libpocketpy.so output/x86_64
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- uses: actions/upload-artifact@v4
with:
name: android
path: output
build_ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Compile Frameworks
run: |
git clone https://github.com/leetal/ios-cmake --depth 1 ~/ios-cmake
bash build_ios.sh
mkdir -p output
cp -r build/pocketpy.xcframework output/pocketpy.xcframework
- uses: actions/upload-artifact@v4
with:
name: ios
path: output
merge:
runs-on: ubuntu-latest
needs: [ build_win32, build_linux, build_darwin, build_android, build_ios ]
steps:
- name: "Create output directory"
run: "mkdir $GITHUB_WORKSPACE/output"
- name: "Merge win32"
uses: actions/download-artifact@v4.1.7
with:
name: windows
path: $GITHUB_WORKSPACE/output/windows
- name: "Merge linux"
uses: actions/download-artifact@v4.1.7
with:
name: linux
path: $GITHUB_WORKSPACE/output/linux
# - name: "Merge darwin"
# uses: actions/download-artifact@v4.1.7
# with:
# name: macos
# path: $GITHUB_WORKSPACE/output/macos
- name: "Merge android"
uses: actions/download-artifact@v4.1.7
with:
name: android
path: $GITHUB_WORKSPACE/output/android
- name: "Merge ios"
uses: actions/download-artifact@v4.1.7
with:
name: ios
path: $GITHUB_WORKSPACE/output/ios
- name: "Upload merged artifact"
uses: actions/upload-artifact@v4.3.3
with:
name: all-in-one
path: $GITHUB_WORKSPACE/output

Binary file not shown.

2
.gitignore vendored
View File

@ -34,3 +34,5 @@ docs/references.md
.xmake
.vs
tests/00_tmp.py

View File

@ -11,12 +11,12 @@ if(MSVC)
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Ox")
add_definitions(-DNDEBUG)
endif()
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
add_definitions(-DNDEBUG)
endif()
# disable -Wshorten-64-to-32 for apple
@ -28,12 +28,6 @@ endif()
include_directories(${CMAKE_CURRENT_LIST_DIR}/include)
file(GLOB_RECURSE POCKETPY_SRC ${CMAKE_CURRENT_LIST_DIR}/src/*.c)
# option(PK_USE_CJSON "" OFF)
# if(PK_USE_CJSON)
# add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/3rd/cjson)
# add_definitions(-DPK_USE_CJSON)
# endif()
option(PK_ENABLE_OS "" OFF)
if(PK_ENABLE_OS)
add_definitions(-DPK_ENABLE_OS=1)
@ -80,15 +74,11 @@ elseif(PK_BUILD_STATIC_LIB)
else()
set(PROJECT_EXE_NAME main)
add_executable(${PROJECT_EXE_NAME} src2/main.c)
# static linked main
add_library(${PROJECT_NAME} STATIC ${POCKETPY_SRC})
# shared linked main
add_library(${PROJECT_NAME} SHARED ${POCKETPY_SRC})
target_link_libraries(${PROJECT_EXE_NAME} ${PROJECT_NAME})
endif()
if(PK_USE_CJSON)
target_link_libraries(${PROJECT_NAME} PRIVATE cjson)
endif()
# link math library
if(UNIX)
target_link_libraries(${PROJECT_NAME} PRIVATE m)

View File

@ -1,46 +0,0 @@
try:
import os
except ImportError:
exit(0)
import sys
is_pkpy = not hasattr(sys, 'getrefcount')
os.chdir('benchmarks')
if is_pkpy:
try:
import cjson as json
except ImportError:
print('[cJSON not Enabled]')
exit(0)
else:
import json
_2489KB = 'WorldMap_GridVania_layout.ldtk'
_1093KB = 'WorldMap_Free_layout.ldtk'
_339KB = 'Typical_2D_platformer_example.ldtk'
with open(f'res/{_2489KB}', 'r') as f:
json_content = f.read()
data: dict = json.loads(json_content)
assert isinstance(data, dict)
# serialize and deserialize
dumped: str = json.dumps(data)
for _ in range(10):
loaded: dict = json.loads(dumped)
assert len(data) == len(loaded)
assert data == loaded
#### very very slow!!
import pickle
with open(f'res/{_339KB}', 'r') as f:
json_content = f.read()
data: dict = json.loads(json_content)
data_pickled: bytes = pickle.dumps(data)
assert isinstance(data_pickled, bytes)
assert pickle.loads(data_pickled) == data

View File

@ -1,7 +1,4 @@
try:
import os
except ImportError:
exit(0)
exit(0)
os.chdir('benchmarks')

View File

@ -1,9 +1,6 @@
import sys
is_cpython = hasattr(sys, 'getrefcount')
if is_cpython:
class vec2:
class vec2:
def __init__(self, x, y):
self.x = x
self.y = y
@ -13,8 +10,6 @@ if is_cpython:
def __eq__(self, other):
return self.x == other.x and self.y == other.y
else:
from linalg import vec2
x = vec2(0, 0)
for i in range(10000000):

View File

@ -25,10 +25,10 @@ assert code == 0
if sys.platform == "win32":
shutil.copy(f"{config}/main.exe", "../main.exe")
# shutil.copy(f"{config}/pocketpy.dll", "../pocketpy.dll")
shutil.copy(f"{config}/pocketpy.dll", "../pocketpy.dll")
elif sys.platform == "darwin":
shutil.copy("main", "../main")
# shutil.copy("libpocketpy.dylib", "../libpocketpy.dylib")
shutil.copy("libpocketpy.dylib", "../libpocketpy.dylib")
else:
shutil.copy("main", "../main")
# shutil.copy("libpocketpy.so", "../libpocketpy.so")
shutil.copy("libpocketpy.so", "../libpocketpy.so")

View File

@ -10,7 +10,7 @@ extern const char* TokenSymbols[];
typedef enum TokenIndex{
TK_EOF, TK_EOL, TK_SOF,
TK_ID, TK_NUM, TK_STR, TK_FSTR, TK_BYTES, TK_IMAG,
TK_ID, TK_NUM, TK_STR, TK_FSTR_BEGIN, TK_FSTR_CPNT, TK_FSTR_SPEC, TK_FSTR_END, TK_BYTES, TK_IMAG,
TK_INDENT, TK_DEDENT,
/***************/
TK_IS_NOT, TK_NOT_IN, TK_YIELD_FROM,
@ -87,7 +87,6 @@ enum Precedence {
typedef c11_array TokenArray;
Error* Lexer__process(SourceData_ src, TokenArray* out_tokens);
Error* Lexer__process_and_dump(SourceData_ src, c11_string** out_string);
void TokenArray__dtor(TokenArray* self);
#define Token__sv(self) (c11_sv){(self)->start, (self)->length}

View File

@ -2,7 +2,7 @@
#include "pocketpy/objects/codeobject.h"
#include "pocketpy/pocketpy.h"
#include "pocketpy/interpreter/gc.h"
#include "pocketpy/interpreter/heap.h"
#include "pocketpy/interpreter/frame.h"
#include "pocketpy/interpreter/modules.h"

View File

@ -9,14 +9,12 @@
struct SourceData {
RefCounted rc;
enum py_CompileMode mode;
bool is_precompiled;
bool is_dynamic; // for exec() and eval()
c11_string* filename;
c11_string* source;
c11_vector /*T=const char* */ line_starts;
c11_vector /*T=c11_string* */ _precompiled_tokens;
};
typedef struct SourceData* SourceData_;

View File

@ -67,7 +67,6 @@ OPCODE(LOOP_BREAK)
OPCODE(JUMP_ABSOLUTE_TOP)
OPCODE(GOTO)
/**************************/
OPCODE(REPR)
OPCODE(CALL)
OPCODE(CALL_VARGS)
OPCODE(RETURN_VALUE)
@ -108,7 +107,6 @@ OPCODE(PUSH_EXCEPTION)
OPCODE(BEGIN_EXC_HANDLING)
OPCODE(END_EXC_HANDLING)
/**************************/
OPCODE(FSTRING_EVAL)
OPCODE(FORMAT_STRING)
/**************************/
#endif

View File

@ -4,7 +4,7 @@ python prebuild.py
SRC=$(find src/ -name "*.c")
clang -std=c11 --coverage -O1 -Wfatal-errors -o main src2/main.c $SRC -Iinclude -DPK_ENABLE_OS=1 -DPK_DEBUG_PRECOMPILED_EXEC=1 -DPK_ENABLE_PROFILER=1
clang -std=c11 --coverage -O1 -Wfatal-errors -o main src2/main.c $SRC -Iinclude -DPK_ENABLE_OS=1 -DPK_ENABLE_PROFILER=1 -lm -DNDEBUG
python scripts/run_tests.py
@ -15,7 +15,6 @@ fi
rm -rf .coverage
mkdir .coverage
rm pocketpy_c.gcno
UNITS=$(find ./ -name "*.gcno")
llvm-cov-15 gcov ${UNITS} -r -s include/ -r -s src/ >> .coverage/coverage.txt

View File

@ -56,12 +56,9 @@ def add(a, b):
class A:
def __init__(self, x):
self.x = x
def get(self):
return self.x
print('ans_1:', add(1, 2))
print('ans_2:', A('abc').get())
exit()

View File

@ -12,7 +12,6 @@ static void SourceData__ctor(struct SourceData* self,
self->filename = c11_string__new(filename);
self->mode = mode;
c11_vector__ctor(&self->line_starts, sizeof(const char*));
c11_vector__ctor(&self->_precompiled_tokens, sizeof(c11_string*));
// Skip utf8 BOM if there is any.
if(strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3;
@ -26,7 +25,6 @@ static void SourceData__ctor(struct SourceData* self,
source++;
}
self->source = c11_sbuf__submit(&ss);
self->is_precompiled = (strncmp(source, "pkpy:", 5) == 0);
self->is_dynamic = is_dynamic;
c11_vector__push(const char*, &self->line_starts, self->source->data);
}
@ -34,13 +32,7 @@ static void SourceData__ctor(struct SourceData* self,
static void SourceData__dtor(struct SourceData* self) {
c11_string__delete(self->filename);
c11_string__delete(self->source);
c11_vector__dtor(&self->line_starts);
for(int i = 0; i < self->_precompiled_tokens.count; i++) {
c11_string__delete(c11__getitem(c11_string*, &self->_precompiled_tokens, i));
}
c11_vector__dtor(&self->_precompiled_tokens);
}
SourceData_ SourceData__rcnew(const char* source,
@ -58,7 +50,7 @@ bool SourceData__get_line(const struct SourceData* self,
int lineno,
const char** st,
const char** ed) {
if(self->is_precompiled || lineno == -1) { return false; }
if(lineno < 0) return false;
lineno -= 1;
if(lineno < 0) lineno = 0;
const char* _start = c11__getitem(const char*, &self->line_starts, lineno);
@ -83,7 +75,6 @@ void SourceData__snapshot(const struct SourceData* self,
c11_sbuf__write_cstr(ss, name);
}
if(!self->is_precompiled) {
c11_sbuf__write_char(ss, '\n');
const char *st = NULL, *ed;
if(SourceData__get_line(self, lineno, &st, &ed)) {
@ -104,5 +95,4 @@ void SourceData__snapshot(const struct SourceData* self,
}
if(!st) { c11_sbuf__write_cstr(ss, " <?>"); }
}
}

View File

@ -7,6 +7,7 @@
#include "pocketpy/common/sstream.h"
#include "pocketpy/common/config.h"
#include "pocketpy/common/memorypool.h"
#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
@ -227,6 +228,30 @@ UnaryExpr* UnaryExpr__new(int line, Expr* child, Opcode opcode) {
return self;
}
typedef struct FStringSpecExpr {
EXPR_COMMON_HEADER
Expr* child;
c11_sv spec;
} FStringSpecExpr;
void FStringSpecExpr__emit_(Expr* self_, Ctx* ctx) {
FStringSpecExpr* self = (FStringSpecExpr*)self_;
vtemit_(self->child, ctx);
int index = Ctx__add_const_string(ctx, self->spec);
Ctx__emit_(ctx, OP_FORMAT_STRING, index, self->line);
}
FStringSpecExpr* FStringSpecExpr__new(int line, Expr* child, c11_sv spec) {
const static ExprVt Vt = {.emit_ = FStringSpecExpr__emit_, .dtor = UnaryExpr__dtor};
static_assert_expr_size(FStringSpecExpr);
FStringSpecExpr* self = PoolExpr_alloc();
self->vt = &Vt;
self->line = line;
self->child = child;
self->spec = spec;
return self;
}
typedef struct RawStringExpr {
EXPR_COMMON_HEADER
c11_sv value;
@ -236,8 +261,7 @@ typedef struct RawStringExpr {
void RawStringExpr__emit_(Expr* self_, Ctx* ctx) {
RawStringExpr* self = (RawStringExpr*)self_;
int index = Ctx__add_const_string(ctx, self->value);
Ctx__emit_(ctx, OP_LOAD_CONST, index, self->line);
Ctx__emit_(ctx, self->opcode, BC_NOARG, self->line);
Ctx__emit_(ctx, self->opcode, index, self->line);
}
RawStringExpr* RawStringExpr__new(int line, c11_sv value, Opcode opcode) {
@ -505,6 +529,11 @@ static SequenceExpr* SequenceExpr__new(int line, const ExprVt* vt, int count, Op
return self;
}
SequenceExpr* FStringExpr__new(int line, int count) {
const static ExprVt ListExprVt = {.dtor = SequenceExpr__dtor, .emit_ = SequenceExpr__emit_};
return SequenceExpr__new(line, &ListExprVt, count, OP_BUILD_STRING);
}
SequenceExpr* ListExpr__new(int line, int count) {
const static ExprVt ListExprVt = {.dtor = SequenceExpr__dtor, .emit_ = SequenceExpr__emit_};
return SequenceExpr__new(line, &ListExprVt, count, OP_BUILD_LIST);
@ -611,162 +640,6 @@ LambdaExpr* LambdaExpr__new(int line, int index) {
return self;
}
typedef struct FStringExpr {
EXPR_COMMON_HEADER
c11_sv src;
} FStringExpr;
static bool is_fmt_valid_char(char c) {
switch(c) {
// clang-format off
case '-': case '=': case '*': case '#': case '@': case '!': case '~':
case '<': case '>': case '^':
case '.': case 'f': case 'd': case 's':
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
return true;
default: return false;
// clang-format on
}
}
static void _load_expr(Ctx* ctx, c11_sv expr, int line) {
bool repr = false;
const char* expr_end = expr.data + expr.size;
if(expr.size >= 2 && expr_end[-2] == '!') {
switch(expr_end[-1]) {
case 'r':
repr = true;
expr.size -= 2; // expr[:-2]
break;
case 's':
repr = false;
expr.size -= 2; // expr[:-2]
break;
default: break; // nothing happens
}
}
c11_string* source = c11_string__new2(expr.data, expr.size);
bool ok = py_compile(source->data, "<f-string>", EVAL_MODE, true);
if(!ok) {
py_printexc();
c11__abort("f-string: invalid expression");
}
int index = Ctx__add_const(ctx, py_retval());
c11_string__delete(source);
Ctx__emit_(ctx, OP_FSTRING_EVAL, index, line);
if(repr) Ctx__emit_(ctx, OP_REPR, BC_NOARG, line);
}
static void FStringExpr__emit_(Expr* self_, Ctx* ctx) {
FStringExpr* self = (FStringExpr*)self_;
int i = 0; // left index
int j = 0; // right index
int count = 0; // how many string parts
bool flag = false; // true if we are in a expression
const char* src = self->src.data;
while(j < self->src.size) {
if(flag) {
if(src[j] == '}') {
// add expression
c11_sv expr = {src + i, j - i}; // src[i:j]
// BUG: ':' is not a format specifier in f"{stack[2:]}"
int conon = c11_sv__index(expr, ':');
if(conon >= 0) {
c11_sv spec = {expr.data + (conon + 1),
expr.size - (conon + 1)}; // expr[conon+1:]
// filter some invalid spec
bool ok = true;
for(int k = 0; k < spec.size; k++) {
char c = spec.data[k];
if(!is_fmt_valid_char(c)) {
ok = false;
break;
}
}
if(ok) {
expr.size = conon; // expr[:conon]
_load_expr(ctx, expr, self->line);
Ctx__emit_(ctx,
OP_FORMAT_STRING,
Ctx__add_const_string(ctx, spec),
self->line);
} else {
// ':' is not a spec indicator
_load_expr(ctx, expr, self->line);
}
} else {
_load_expr(ctx, expr, self->line);
}
flag = false;
count++;
}
} else {
if(src[j] == '{') {
// look at next char
if(j + 1 < self->src.size && src[j + 1] == '{') {
// {{ -> {
j++;
Ctx__emit_(ctx,
OP_LOAD_CONST,
Ctx__add_const_string(ctx, (c11_sv){"{", 1}),
self->line);
count++;
} else {
// { -> }
flag = true;
i = j + 1;
}
} else if(src[j] == '}') {
// look at next char
if(j + 1 < self->src.size && src[j + 1] == '}') {
// }} -> }
j++;
Ctx__emit_(ctx,
OP_LOAD_CONST,
Ctx__add_const_string(ctx, (c11_sv){"}", 1}),
self->line);
count++;
} else {
// } -> error
// throw std::runtime_error("f-string: unexpected }");
// just ignore
}
} else {
// literal
i = j;
while(j < self->src.size && src[j] != '{' && src[j] != '}')
j++;
c11_sv literal = {src + i, j - i}; // src[i:j]
Ctx__emit_(ctx, OP_LOAD_CONST, Ctx__add_const_string(ctx, literal), self->line);
count++;
continue; // skip j++
}
}
j++;
}
if(flag) {
// literal
c11_sv literal = {src + i, self->src.size - i}; // src[i:]
Ctx__emit_(ctx, OP_LOAD_CONST, Ctx__add_const_string(ctx, literal), self->line);
count++;
}
Ctx__emit_(ctx, OP_BUILD_STRING, count, self->line);
}
FStringExpr* FStringExpr__new(int line, c11_sv src) {
const static ExprVt Vt = {.emit_ = FStringExpr__emit_};
static_assert_expr_size(FStringExpr);
FStringExpr* self = PoolExpr_alloc();
self->vt = &Vt;
self->line = line;
self->src = src;
return self;
}
// AndExpr, OrExpr
typedef struct LogicBinaryExpr {
EXPR_COMMON_HEADER
@ -1669,9 +1542,39 @@ static Error* exprBytes(Compiler* self) {
}
static Error* exprFString(Compiler* self) {
c11_sv sv = c11_string__sv(prev()->value._str);
Ctx__s_push(ctx(), (Expr*)FStringExpr__new(prev()->line, sv));
// @fstr-begin, [@fstr-cpnt | <expr>]*, @fstr-end
int count = 0;
int line = prev()->line;
while(true) {
if(match(TK_FSTR_END)) {
SequenceExpr* e = FStringExpr__new(line, count);
for(int i = count - 1; i >= 0; i--) {
Expr* item = Ctx__s_popx(ctx());
c11__setitem(Expr*, &e->items, i, item);
}
Ctx__s_push(ctx(), (Expr*)e);
return NULL;
} else if(match(TK_FSTR_CPNT)) {
// OP_LOAD_CONST
LiteralExpr* e = LiteralExpr__new(prev()->line, &prev()->value);
Ctx__s_push(ctx(), (Expr*)e);
count++;
} else {
// {a!r:.2f}
Error* err = EXPR(self);
if(err) return err;
count++;
if(match(TK_FSTR_SPEC)) {
c11_sv spec = Token__sv(prev());
// ':.2f}' -> ':.2f'
spec.size--;
Expr* child = Ctx__s_popx(ctx());
FStringSpecExpr* e = FStringSpecExpr__new(prev()->line, child, spec);
Ctx__s_push(ctx(), (Expr*)e);
}
}
}
}
static Error* exprImag(Compiler* self) {
@ -2766,14 +2669,25 @@ Error* pk_compile(SourceData_ src, CodeObject* out) {
Error* err = Lexer__process(src, &tokens);
if(err) return err;
// Token* data = (Token*)tokens.data;
// printf("%s\n", src->filename->data);
// for(int i = 0; i < tokens.count; i++) {
// Token* t = data + i;
// c11_string* tmp = c11_string__new2(t->start, t->length);
// printf("[%d] %s: %s\n", t->line, TokenSymbols[t->type], tmp->data);
// c11_string__delete(tmp);
// }
#if 0
Token* data = (Token*)tokens.data;
printf("%s\n", src->filename->data);
for(int i = 0; i < tokens.count; i++) {
Token* t = data + i;
c11_string* tmp = c11_string__new2(t->start, t->length);
if(t->value.index == TokenValue_STR) {
const char* value_str = t->value._str->data;
printf("[%d] %s: %s (value._str=%s)\n",
t->line,
TokenSymbols[t->type],
tmp->data,
value_str);
} else {
printf("[%d] %s: %s\n", t->line, TokenSymbols[t->type], tmp->data);
}
c11_string__delete(tmp);
}
#endif
Compiler compiler;
Compiler__ctor(&compiler, src, tokens);
@ -2829,7 +2743,7 @@ const static PrattRule rules[TK__COUNT__] = {
[TK_ID] = { exprName, },
[TK_NUM] = { exprLiteral, },
[TK_STR] = { exprLiteral, },
[TK_FSTR] = { exprFString, },
[TK_FSTR_BEGIN] = { exprFString, },
[TK_IMAG] = { exprImag, },
[TK_BYTES] = { exprBytes, },
[TK_LBRACE] = { exprMap },

View File

@ -36,6 +36,8 @@ double TokenDeserializer__read_float(TokenDeserializer* self, char c);
const static TokenValue EmptyTokenValue;
static Error* lex_one_token(Lexer* self, bool* eof, bool is_fstring);
static void Lexer__ctor(Lexer* self, SourceData_ src) {
PK_INCREF(src);
self->src = src;
@ -267,18 +269,24 @@ static Error* eat_name(Lexer* self) {
return NULL;
}
static Error* eat_string_until(Lexer* self, char quote, bool raw, c11_string** out) {
enum StringType { NORMAL_STRING, RAW_STRING, F_STRING, NORMAL_BYTES };
static Error* _eat_string(Lexer* self, c11_sbuf* buff, char quote, enum StringType type) {
bool is_raw = type == RAW_STRING;
bool is_fstring = type == F_STRING;
if(is_fstring) { add_token(self, TK_FSTR_BEGIN); }
// previous char is quote
bool quote3 = match_n_chars(self, 2, quote);
c11_sbuf buff;
c11_sbuf__ctor(&buff);
while(true) {
char c = eatchar_include_newline(self);
if(c == quote) {
if(quote3 && !match_n_chars(self, 2, quote)) {
c11_sbuf__write_char(&buff, c);
c11_sbuf__write_char(buff, c);
continue;
}
// end of string
break;
}
if(c == '\0') { return SyntaxError(self, "EOL while scanning string literal"); }
@ -286,47 +294,88 @@ static Error* eat_string_until(Lexer* self, char quote, bool raw, c11_string** o
if(!quote3)
return SyntaxError(self, "EOL while scanning string literal");
else {
c11_sbuf__write_char(&buff, c);
c11_sbuf__write_char(buff, c);
continue;
}
}
if(!raw && c == '\\') {
if(!is_raw && c == '\\') {
switch(eatchar_include_newline(self)) {
case '"': c11_sbuf__write_char(&buff, '"'); break;
case '\'': c11_sbuf__write_char(&buff, '\''); break;
case '\\': c11_sbuf__write_char(&buff, '\\'); break;
case 'n': c11_sbuf__write_char(&buff, '\n'); break;
case 'r': c11_sbuf__write_char(&buff, '\r'); break;
case 't': c11_sbuf__write_char(&buff, '\t'); break;
case 'b': c11_sbuf__write_char(&buff, '\b'); break;
case '"': c11_sbuf__write_char(buff, '"'); break;
case '\'': c11_sbuf__write_char(buff, '\''); break;
case '\\': c11_sbuf__write_char(buff, '\\'); break;
case 'n': c11_sbuf__write_char(buff, '\n'); break;
case 'r': c11_sbuf__write_char(buff, '\r'); break;
case 't': c11_sbuf__write_char(buff, '\t'); break;
case 'b': c11_sbuf__write_char(buff, '\b'); break;
case 'x': {
char hex[3] = {eatchar(self), eatchar(self), '\0'};
int code;
if(sscanf(hex, "%x", &code) != 1) {
return SyntaxError(self, "invalid hex char");
}
c11_sbuf__write_char(&buff, (char)code);
c11_sbuf__write_char(buff, (char)code);
} break;
default: return SyntaxError(self, "invalid escape char");
}
} else {
c11_sbuf__write_char(&buff, c);
if(is_fstring) {
if(c == '{') {
if(matchchar(self, '{')) {
// '{{' -> '{'
c11_sbuf__write_char(buff, '{');
} else {
// submit previous string
c11_string* res = c11_sbuf__submit(buff);
if(res->size > 0) {
TokenValue value = {TokenValue_STR, ._str = res};
add_token_with_value(self, TK_FSTR_CPNT, value);
} else {
c11_string__delete(res);
}
}
*out = c11_sbuf__submit(&buff);
return NULL;
}
c11_sbuf__ctor(buff); // re-init buffer
enum StringType { NORMAL_STRING, RAW_STRING, F_STRING, NORMAL_BYTES };
static Error* eat_string(Lexer* self, char quote, enum StringType type) {
c11_string* s;
Error* err = eat_string_until(self, quote, type == RAW_STRING, &s);
// submit {expr} tokens
bool eof = false;
int token_count = self->nexts.count;
while(!eof) {
Error* err = lex_one_token(self, &eof, true);
if(err) return err;
TokenValue value = {TokenValue_STR, ._str = s};
if(type == F_STRING) {
add_token_with_value(self, TK_FSTR, value);
} else if(type == NORMAL_BYTES) {
}
if(self->nexts.count == token_count) {
// f'{}' is not allowed
return SyntaxError(self, "f-string: empty expression not allowed");
}
}
} else if(c == '}') {
if(matchchar(self, '}')) {
// '}}' -> '}'
c11_sbuf__write_char(buff, '}');
} else {
return SyntaxError(self, "f-string: single '}' is not allowed");
}
}else{
c11_sbuf__write_char(buff, c);
}
} else {
c11_sbuf__write_char(buff, c);
}
}
}
c11_string* res = c11_sbuf__submit(buff);
TokenValue value = {TokenValue_STR, ._str = res};
if(is_fstring) {
if(res->size > 0) {
add_token_with_value(self, TK_FSTR_CPNT, value);
} else {
c11_string__delete(res);
}
add_token(self, TK_FSTR_END);
return NULL;
}
if(type == NORMAL_BYTES) {
add_token_with_value(self, TK_BYTES, value);
} else {
add_token_with_value(self, TK_STR, value);
@ -334,6 +383,14 @@ static Error* eat_string(Lexer* self, char quote, enum StringType type) {
return NULL;
}
static Error* eat_string(Lexer* self, char quote, enum StringType type) {
c11_sbuf buff;
c11_sbuf__ctor(&buff);
Error* err = _eat_string(self, &buff, quote, type);
c11_sbuf__dtor(&buff);
return err;
}
static Error* eat_number(Lexer* self) {
const char* i = self->token_start;
while(is_possible_number_char(*i))
@ -380,7 +437,22 @@ static Error* eat_number(Lexer* self) {
return SyntaxError(self, "invalid number literal");
}
static Error* lex_one_token(Lexer* self, bool* eof) {
static Error* eat_fstring_spec(Lexer* self, bool* eof) {
while(true) {
char c = eatchar_include_newline(self);
if(c == '\n' || c == '\0') {
return SyntaxError(self, "EOL while scanning f-string format spec");
}
if(c == '}') {
add_token(self, TK_FSTR_SPEC);
*eof = true;
break;
}
}
return NULL;
}
static Error* lex_one_token(Lexer* self, bool* eof, bool is_fstring) {
*eof = false;
while(*self->curr_char) {
self->token_start = self->curr_char;
@ -395,9 +467,20 @@ static Error* lex_one_token(Lexer* self, bool* eof) {
case '#': skip_line_comment(self); break;
case '~': add_token(self, TK_INVERT); return NULL;
case '{': add_token(self, TK_LBRACE); return NULL;
case '}': add_token(self, TK_RBRACE); return NULL;
case '}': {
if(is_fstring) {
*eof = true;
return NULL;
}
add_token(self, TK_RBRACE);
return NULL;
}
case ',': add_token(self, TK_COMMA); return NULL;
case ':': add_token(self, TK_COLON); return NULL;
case ':': {
if(is_fstring && self->brackets_level == 0) { return eat_fstring_spec(self, eof); }
add_token(self, TK_COLON);
return NULL;
}
case ';': add_token(self, TK_SEMICOLON); return NULL;
case '(': add_token(self, TK_LPAREN); return NULL;
case ')': add_token(self, TK_RPAREN); return NULL;
@ -465,13 +548,15 @@ static Error* lex_one_token(Lexer* self, bool* eof) {
return NULL;
}
case '!':
if(is_fstring && self->brackets_level == 0) {
if(matchchar(self, 'r')) { return eat_fstring_spec(self, eof); }
}
if(matchchar(self, '=')) {
add_token(self, TK_NE);
return NULL;
} else {
Error* err = SyntaxError(self, "expected '=' after '!'");
if(err) return err;
return SyntaxError(self, "expected '=' after '!'");
}
break;
case '*':
if(matchchar(self, '*')) {
add_token(self, TK_POW); // '**'
@ -512,6 +597,8 @@ static Error* lex_one_token(Lexer* self, bool* eof) {
}
}
if(is_fstring) return SyntaxError(self, "unterminated f-string expression");
self->token_start = self->curr_char;
while(self->indents.count > 1) {
c11_vector__pop(&self->indents);
@ -523,85 +610,10 @@ static Error* lex_one_token(Lexer* self, bool* eof) {
return NULL;
}
static Error* from_precompiled(Lexer* self) {
TokenDeserializer deserializer;
TokenDeserializer__ctor(&deserializer, self->src->source->data);
deserializer.curr += 5; // skip "pkpy:"
c11_sv version = TokenDeserializer__read_string(&deserializer, '\n');
if(c11_sv__cmp2(version, PK_VERSION) != 0) {
return SyntaxError(self, "precompiled version mismatch");
}
if(TokenDeserializer__read_uint(&deserializer, '\n') != (int64_t)self->src->mode) {
return SyntaxError(self, "precompiled mode mismatch");
}
int count = TokenDeserializer__read_count(&deserializer);
c11_vector* precompiled_tokens = &self->src->_precompiled_tokens;
for(int i = 0; i < count; i++) {
c11_sv item = TokenDeserializer__read_string(&deserializer, '\n');
c11_string* copied_item = c11_string__new2(item.data, item.size);
c11_vector__push(c11_string*, precompiled_tokens, copied_item);
}
count = TokenDeserializer__read_count(&deserializer);
for(int i = 0; i < count; i++) {
Token t;
t.type = (TokenIndex)TokenDeserializer__read_uint(&deserializer, ',');
if(is_raw_string_used(t.type)) {
int64_t index = TokenDeserializer__read_uint(&deserializer, ',');
c11_string* p = c11__getitem(c11_string*, precompiled_tokens, index);
t.start = p->data;
t.length = p->size;
} else {
t.start = NULL;
t.length = 0;
}
if(TokenDeserializer__match_char(&deserializer, ',')) {
t.line = c11_vector__back(Token, &self->nexts).line;
} else {
t.line = (int)TokenDeserializer__read_uint(&deserializer, ',');
}
if(TokenDeserializer__match_char(&deserializer, ',')) {
t.brackets_level = c11_vector__back(Token, &self->nexts).brackets_level;
} else {
t.brackets_level = (int)TokenDeserializer__read_uint(&deserializer, ',');
}
char type = (*deserializer.curr++); // read_char
switch(type) {
case 'I': {
int64_t res = TokenDeserializer__read_uint(&deserializer, '\n');
t.value = (TokenValue){TokenValue_I64, ._i64 = res};
} break;
case 'F': {
double res = TokenDeserializer__read_float(&deserializer, '\n');
t.value = (TokenValue){TokenValue_F64, ._f64 = res};
} break;
case 'S': {
c11_string* res = TokenDeserializer__read_string_from_hex(&deserializer, '\n');
t.value = (TokenValue){TokenValue_STR, ._str = res};
} break;
default: t.value = EmptyTokenValue; break;
}
c11_vector__push(Token, &self->nexts, t);
}
return NULL;
}
Error* Lexer__process(SourceData_ src, TokenArray* out_tokens) {
Lexer lexer;
Lexer__ctor(&lexer, src);
if(src->is_precompiled) {
Error* err = from_precompiled(&lexer);
// TODO: set out tokens
Lexer__dtor(&lexer);
return err;
}
// push initial tokens
Token sof =
{TK_SOF, lexer.token_start, 0, lexer.current_line, lexer.brackets_level, EmptyTokenValue};
@ -610,7 +622,7 @@ Error* Lexer__process(SourceData_ src, TokenArray* out_tokens) {
bool eof = false;
while(!eof) {
void* err = lex_one_token(&lexer, &eof);
void* err = lex_one_token(&lexer, &eof, false);
if(err) {
Lexer__dtor(&lexer);
return err;
@ -623,102 +635,6 @@ Error* Lexer__process(SourceData_ src, TokenArray* out_tokens) {
return NULL;
}
Error* Lexer__process_and_dump(SourceData_ src, c11_string** out) {
assert(!src->is_precompiled);
TokenArray nexts; // output tokens
Error* err = Lexer__process(src, &nexts);
if(err) return err;
c11_sbuf ss;
c11_sbuf__ctor(&ss);
// L1: version string
c11_sbuf__write_cstr(&ss, "pkpy:" PK_VERSION "\n");
// L2: mode
c11_sbuf__write_int(&ss, (int)src->mode);
c11_sbuf__write_char(&ss, '\n');
c11_smallmap_s2n token_indices;
c11_smallmap_s2n__ctor(&token_indices);
c11__foreach(Token, &nexts, token) {
if(is_raw_string_used(token->type)) {
c11_sv token_sv = {token->start, token->length};
if(!c11_smallmap_s2n__contains(&token_indices, token_sv)) {
c11_smallmap_s2n__set(&token_indices, token_sv, 0);
}
}
}
// L3: raw string count
c11_sbuf__write_char(&ss, '=');
c11_sbuf__write_int(&ss, token_indices.count);
c11_sbuf__write_char(&ss, '\n');
uint16_t index = 0;
for(int i = 0; i < token_indices.count; i++) {
c11_smallmap_s2n_KV* kv = c11__at(c11_smallmap_s2n_KV, &token_indices, i);
// L4: raw strings
c11_sbuf__write_cstrn(&ss, kv->key.data, kv->key.size);
kv->value = index++;
}
// L5: token count
c11_sbuf__write_char(&ss, '=');
c11_sbuf__write_int(&ss, nexts.count);
c11_sbuf__write_char(&ss, '\n');
for(int i = 0; i < nexts.count; i++) {
const Token* token = c11__at(Token, &nexts, i);
c11_sbuf__write_int(&ss, (int)token->type);
c11_sbuf__write_char(&ss, ',');
if(is_raw_string_used(token->type)) {
uint16_t* p =
c11_smallmap_s2n__try_get(&token_indices, (c11_sv){token->start, token->length});
assert(p != NULL);
c11_sbuf__write_int(&ss, (int)*p);
c11_sbuf__write_char(&ss, ',');
}
if(i > 0 && c11__getitem(Token, &nexts, i - 1).line == token->line) {
c11_sbuf__write_char(&ss, ',');
} else {
c11_sbuf__write_int(&ss, token->line);
c11_sbuf__write_char(&ss, ',');
}
if(i > 0 && c11__getitem(Token, &nexts, i - 1).brackets_level == token->brackets_level) {
c11_sbuf__write_char(&ss, ',');
} else {
c11_sbuf__write_int(&ss, token->brackets_level);
c11_sbuf__write_char(&ss, ',');
}
// visit token value
switch(token->value.index) {
case TokenValue_EMPTY: break;
case TokenValue_I64:
c11_sbuf__write_char(&ss, 'I');
c11_sbuf__write_int(&ss, token->value._i64);
break;
case TokenValue_F64:
c11_sbuf__write_char(&ss, 'F');
c11_sbuf__write_f64(&ss, token->value._f64, -1);
break;
case TokenValue_STR: {
c11_sbuf__write_char(&ss, 'S');
c11_sv sv = c11_string__sv(token->value._str);
for(int i = 0; i < sv.size; i++) {
c11_sbuf__write_hex(&ss, sv.data[i], false);
}
break;
}
}
c11_sbuf__write_char(&ss, '\n');
}
*out = c11_sbuf__submit(&ss);
c11_smallmap_s2n__dtor(&token_indices);
return NULL;
}
void TokenArray__dtor(TokenArray* self) {
Token* data = self->data;
for(int i = 0; i < self->count; i++) {
@ -734,7 +650,10 @@ const char* TokenSymbols[] = {
"@id",
"@num",
"@str",
"@fstr",
"@fstr-begin", // TK_FSTR_BEGIN
"@fstr-cpnt", // TK_FSTR_CPNT
"@fstr-spec", // TK_FSTR_SPEC
"@fstr-end", // TK_FSTR_END
"@bytes",
"@imag",
"@indent",

View File

@ -1,4 +1,5 @@
#include "pocketpy/common/config.h"
#include "pocketpy/common/str.h"
#include "pocketpy/common/utils.h"
#include "pocketpy/interpreter/frame.h"
#include "pocketpy/interpreter/vm.h"
@ -10,7 +11,7 @@
#include <stdbool.h>
static bool stack_unpack_sequence(VM* self, uint16_t arg);
static bool format_object(py_Ref obj, c11_sv spec);
static bool stack_format_object(VM* self, c11_sv spec);
#define DISPATCH() \
do { \
@ -469,8 +470,9 @@ FrameResult VM__run_top_frame(VM* self) {
}
case OP_BUILD_BYTES: {
int size;
const char* data = py_tostrn(TOP(), &size);
unsigned char* p = py_newbytes(TOP(), size);
py_Ref string = c11__at(py_TValue, &frame->co->consts, byte.arg);
const char* data = py_tostrn(string, &size);
unsigned char* p = py_newbytes(SP()++, size);
memcpy(p, data, size);
DISPATCH();
}
@ -660,11 +662,6 @@ FrameResult VM__run_top_frame(VM* self) {
DISPATCH_JUMP_ABSOLUTE(target);
}
/*****************************************/
case OP_REPR: {
if(!py_repr(TOP())) goto __ERROR;
py_assign(TOP(), py_retval());
DISPATCH();
}
case OP_CALL: {
ManagedHeap__collect_if_needed(&self->heap);
vectorcall_opcall(byte.arg & 0xFF, byte.arg >> 8);
@ -1022,21 +1019,10 @@ FrameResult VM__run_top_frame(VM* self) {
DISPATCH();
}
//////////////////
case OP_FSTRING_EVAL: {
py_TValue* code = c11__at(py_TValue, &frame->co->consts, byte.arg);
assert(py_istype(code, tp_code));
py_newglobals(SP()++);
py_newlocals(SP()++);
PUSH(code);
if(!pk_exec(py_touserdata(code), frame->module)) goto __ERROR;
PUSH(py_retval());
DISPATCH();
}
case OP_FORMAT_STRING: {
py_Ref spec = c11__at(py_TValue, &frame->co->consts, byte.arg);
bool ok = format_object(TOP(), py_tosv(spec));
bool ok = stack_format_object(self, py_tosv(spec));
if(!ok) goto __ERROR;
py_assign(TOP(), py_retval());
DISPATCH();
}
default: c11__unreachedable();
@ -1132,9 +1118,31 @@ static bool stack_unpack_sequence(VM* self, uint16_t arg) {
return true;
}
static bool format_object(py_Ref val, c11_sv spec) {
static bool stack_format_object(VM* self, c11_sv spec) {
// format TOS via `spec` inplace
// spec: '!r:.2f', '.2f'
py_StackRef val = TOP();
if(spec.size == 0) return py_str(val);
if(spec.data[0] == '!'){
if(c11_sv__startswith(spec, (c11_sv){"!r", 2})){
spec.data += 2;
spec.size -= 2;
if(!py_repr(val)) return false;
py_assign(val, py_retval());
if(spec.size == 0) return true;
}else{
return ValueError("invalid conversion specifier (only !r is supported)");
}
}
assert(spec.size > 0);
if(spec.data[0] == ':'){
spec.data++;
spec.size--;
}
char type;
switch(spec.data[spec.size - 1]) {
case 'f':
@ -1253,6 +1261,7 @@ static bool format_object(py_Ref val, c11_sv spec) {
}
c11_string__delete(body);
c11_sbuf__py_submit(&buf, py_retval());
// inplace update
c11_sbuf__py_submit(&buf, val);
return true;
}

View File

@ -1,4 +1,4 @@
#include "pocketpy/interpreter/gc.h"
#include "pocketpy/interpreter/heap.h"
#include "pocketpy/common/memorypool.h"
#include "pocketpy/objects/base.h"

View File

@ -6,10 +6,12 @@
static bool get_ns(int64_t* out) {
struct timespec tms;
#ifdef __ANDROID__
clock_gettime(CLOCK_REALTIME, &tms);
#else
/* The C11 way */
if(!timespec_get(&tms, TIME_UTC)) {
return py_exception(tp_OSError, "%s", "timespec_get() failed");
}
timespec_get(&tms, TIME_UTC);
#endif
/* seconds, multiplied with 1 billion */
int64_t nanos = tms.tv_sec * NANOS_PER_SEC;
/* Add full nanoseconds */

View File

@ -646,6 +646,10 @@ py_Type pk_bytes__register() {
}
bool py_str(py_Ref val) {
if(val->type == tp_str) {
py_assign(py_retval(), val);
return true;
}
py_Ref tmp = py_tpfindmagic(val->type, __str__);
if(!tmp) return py_repr(val);
return py_call(tmp, 1, val);

View File

@ -20,6 +20,9 @@ assert s == 'asdasd\nasds1321321321测试\\测试'
t = 4
assert f'123{t}56789' == '123456789'
assert f'{{' == '{'
assert f'}}' == '}'
b = 123
s = f'''->->{s}<-<-
{b}
@ -117,10 +120,11 @@ class A:
return 'A'
a = A()
assert f'{a!r}' == 'A()'
assert f'{a!r:10}' == 'A() '
assert f'{a!s:10}' == 'A '
assert f'{a:10}' == 'A '
assert f'{a:10}' == 'A '
assert f'{A()!r:10}' == 'A() '
assert f'{A()!s:10}' == 'A '
assert f'{A():10}' == 'A '
assert f'{A():10}' == 'A '

View File

@ -89,8 +89,6 @@ try:
exit(1)
except UnboundLocalError:
pass
except NameError:
pass
g = 1