Compare commits

...

9 Commits

Author SHA1 Message Date
Kanika Kapoor
223e469668
Merge c048ec9faf4bfe02da004dce69471897933c3617 into 0c965d8c5b0077ca74cae55c194c51f08a9e2a87 2026-01-07 19:49:57 +05:30
blueloveTH
0c965d8c5b Update guide.md 2026-01-07 19:59:41 +08:00
blueloveTH
ef2481b8a5 add gsoc 2026 page
Update guide.md
2026-01-07 19:55:29 +08:00
blueloveTH
9e4636b619 improve math tests 2026-01-07 16:59:10 +08:00
blueloveTH
e82b2b98cf Update math.c 2026-01-07 16:47:21 +08:00
blueloveTH
08f39b8444 minor fix 2026-01-07 16:16:30 +08:00
blueloveTH
2d1d61d9ca minor fix 2026-01-07 16:12:23 +08:00
blueloveTH
c32a97084b update dmath submodule 2026-01-07 16:03:22 +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
9 changed files with 407 additions and 60 deletions

@ -1 +1 @@
Subproject commit 2d9e04701ddac77eb33eb7f9dcb24cab5bb10412
Subproject commit cc7cdccae657b0f81a1e234eb9d4ed4c458bda0f

76
docs/gsoc2026/guide.md Normal file
View File

@ -0,0 +1,76 @@
---
icon: rocket
order: 10
label: "Application Guide"
---
Welcome to the Google Summer of Code 2026 application guide for pocketpy.
We are recruiting a student who is passionate about vibe coding and mobile game development.
See [Project Ideas (TBA)](./ideas.md) for more details about the project.
## Prerequisites
To apply for this project, you need to satisfy the following prerequisites:
+ You are a student enrolled in an accredited institution (university, college, etc.) pursuing a degree in computer science or a related field. And this is your first time participating in Google Summer of Code.
+ You have interest in vibe coding and mobile game development.
+ You are experienced in [Python](https://www.python.org/) and backend technologies, such as [FastAPI](https://fastapi.tiangolo.com/) or [Flask](https://flask.palletsprojects.com/).
+ You are glad to learn mobile app development using frameworks like [Flutter](https://flutter.dev/).
## Application steps
### Step 1
If you think you meet the prerequisites,
send an email to `blueloveth@foxmail.com` with the following information.
1. A brief introduction about yourself, including the most related open sourced project you have worked on before. It is highly recommended to attach your Github profile link.
2. Your understanding of this project and why you are capable of completing it.
3. Your free time during the whole GSoC period (From 2026-03-01 to 2026-08-31).
### Step 2
After you get a positive reply from us,
you need to complete 1~2 pull requests to pocketpy's repository on GitHub.
This is mandatory as it demonstrates your coding skills and commitment to the project.
We will provide you a few good first issues to work on.
### Step 3
Once your pull requests are merged,
we will guide you to write a full proposal
for the project you are going to work on during GSoC 2026.
This proposal will be submitted to Google for review.
## Build guide for pocketpy
First, you need to install these tools:
1. Python(>= 3.8), I am sure you already have it.
2. A C11 compiler, such as GCC, Clang or MSVC. If you are on Linux, `gcc` is already installed. If you are on Windows, you can install Visual Studio with C/C++ development tools.
3. CMake(>= 3.10), a cross-platform build tool. You can use `pip install cmake` to install it.
Then, clone pocketpy sources from github and try to build:
```bash
git clone https://github.com/pocketpy/pocketpy
cd pocketpy
python cmake_build.py
```
If everything goes well, you will get a `main` executable (`main.exe` on Windows) in the root directory of pocketpy.
Simply run it and you will enter pocketpy's REPL.
```txt
pocketpy 2.1.7 (Jan 7 2026, 16:42:45) [64 bit] on darwin
https://github.com/pocketpy/pocketpy
Type "exit()" to exit.
>>>
>>> "Hello, world"
'Hello, world'
```
## Coding style guide
See [Coding Style Guide](../coding-style-guide.md).

2
docs/gsoc2026/index.yml Normal file
View File

@ -0,0 +1,2 @@
order: 1
label: "GSoC 2026"

View File

@ -2758,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)) {
@ -2766,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

@ -1122,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

@ -18,6 +18,15 @@
return true; \
}
#define ONE_ARG_BOOL_FUNC(name, func) \
static bool math_##name(int argc, py_Ref argv) { \
PY_CHECK_ARGC(1); \
double x; \
if(!py_castfloat(py_arg(0), &x)) return false; \
py_newbool(py_retval(), func(x)); \
return true; \
}
#define TWO_ARG_FUNC(name, func) \
static bool math_##name(int argc, py_Ref argv) { \
PY_CHECK_ARGC(2); \
@ -69,9 +78,9 @@ static bool math_gcd(int argc, py_Ref argv) {
return true;
}
ONE_ARG_FUNC(isfinite, isfinite)
ONE_ARG_FUNC(isinf, isinf)
ONE_ARG_FUNC(isnan, isnan)
ONE_ARG_BOOL_FUNC(isfinite, isfinite)
ONE_ARG_BOOL_FUNC(isinf, isinf)
ONE_ARG_BOOL_FUNC(isnan, isnan)
static bool math_isclose(int argc, py_Ref argv) {
PY_CHECK_ARGC(2);
@ -206,4 +215,5 @@ void pk__add_module_math() {
}
#undef ONE_ARG_FUNC
#undef TWO_ARG_FUNC
#undef ONE_ARG_BOOL_FUNC
#undef TWO_ARG_FUNC

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,64 +1,259 @@
from math import log, log10, log2, sin, cos, tan, e, pi, isnan, isinf, fabs, floor, ceil, sqrt
import math
# ============================================================
# Helper function for floating point comparison
# ============================================================
def isclose(a, b):
return abs(a-b) < 0.000001
assert isclose(e, 2.718281828459045), e
assert isclose(pi, 3.141592653589793), pi
assert isclose(log(10), 2.302585092994046), log(10)
assert isclose(log10(10), 1.0), log10(10)
assert isclose(log2(10), 3.321928094887362), log2(10)
assert isclose(sin(0), 0.0), sin(0)
assert isclose(cos(0), 1.0), cos(0)
assert isclose(tan(0), 0.0), tan(0)
# ============================================================
# Constants: pi, e, inf, nan
# ============================================================
a = -0.1
a = a**a
assert isnan(a)
assert not isinf(a)
assert isinf(float("inf"))
assert isclose(math.pi, 3.141592653589793)
assert isclose(math.e, 2.718281828459045)
assert math.isinf(math.inf)
assert math.isnan(math.nan)
assert isclose(fabs(-1.2), 1.2)
# ============================================================
# Rounding functions: ceil, floor, trunc, fabs
# ============================================================
assert floor(1.2) == 1
assert floor(-1.2) == -2
assert ceil(1.2) == 2
assert ceil(-1.2) == -1
# ceil - round up to nearest integer
assert math.ceil(1.2) == 2
assert math.ceil(-1.2) == -1
assert math.ceil(2.0) == 2
assert math.ceil(0) == 0
assert isclose(sqrt(4), 2.0)
# floor - round down to nearest integer
assert math.floor(1.2) == 1
assert math.floor(-1.2) == -2
assert math.floor(2.0) == 2
assert math.floor(0) == 0
import math
# trunc - truncate towards zero
assert math.trunc(1.7) == 1
assert math.trunc(-1.7) == -1
assert math.trunc(2.0) == 2
assert math.trunc(0) == 0
# test fsum
# fabs - absolute value (returns float)
assert isclose(math.fabs(-1.2), 1.2)
assert isclose(math.fabs(1.2), 1.2)
assert isclose(math.fabs(0), 0.0)
assert isclose(math.fabs(-0.0), 0.0)
# ============================================================
# Classification functions: isnan, isinf, isfinite, isclose
# ============================================================
# isnan - check if value is NaN
assert math.isnan(float('nan'))
assert math.isnan(math.nan)
assert not math.isnan(1.0)
assert not math.isnan(math.inf)
# isinf - check if value is infinity
assert math.isinf(float('inf'))
assert math.isinf(float('-inf'))
assert math.isinf(math.inf)
assert not math.isinf(1.0)
assert not math.isinf(math.nan)
# isfinite - check if value is finite (not inf or nan)
assert math.isfinite(1.0)
assert math.isfinite(0.0)
assert math.isfinite(-1.0)
assert not math.isfinite(math.inf)
assert not math.isfinite(-math.inf)
assert not math.isfinite(math.nan)
# isclose - check if two values are close
assert math.isclose(1.0, 1.0)
assert math.isclose(1.0, 1.0 + 1e-10)
assert not math.isclose(1.0, 1.1)
# ============================================================
# Power and logarithmic functions: exp, log, log2, log10, pow, sqrt
# ============================================================
# exp - e raised to the power x
assert isclose(math.exp(0), 1.0)
assert isclose(math.exp(1), math.e)
assert isclose(math.exp(2), math.e * math.e)
# log - natural logarithm (base e) or logarithm with custom base
assert isclose(math.log(1), 0.0)
assert isclose(math.log(math.e), 1.0)
assert isclose(math.log(10), 2.302585092994046)
assert isclose(math.log(100, 10), 2.0) # log base 10
assert isclose(math.log(8, 2), 3.0) # log base 2
# log2 - logarithm base 2
assert isclose(math.log2(1), 0.0)
assert isclose(math.log2(2), 1.0)
assert isclose(math.log2(8), 3.0)
assert isclose(math.log2(10), 3.321928094887362)
# log10 - logarithm base 10
assert isclose(math.log10(1), 0.0)
assert isclose(math.log10(10), 1.0)
assert isclose(math.log10(100), 2.0)
assert isclose(math.log10(1000), 3.0)
# pow - x raised to the power y
assert isclose(math.pow(2, 3), 8.0)
assert isclose(math.pow(2, 0), 1.0)
assert isclose(math.pow(2, -1), 0.5)
assert isclose(math.pow(4, 0.5), 2.0)
assert isclose(math.pow(0, 0), 1.0)
# sqrt - square root
assert isclose(math.sqrt(4), 2.0)
assert isclose(math.sqrt(2), 1.4142135623730951)
assert isclose(math.sqrt(0), 0.0)
assert isclose(math.sqrt(1), 1.0)
# ============================================================
# Trigonometric functions: sin, cos, tan, asin, acos, atan, atan2
# ============================================================
# sin - sine
assert isclose(math.sin(0), 0.0)
assert isclose(math.sin(math.pi / 2), 1.0)
assert isclose(math.sin(math.pi), 0.0)
assert isclose(math.sin(-math.pi / 2), -1.0)
# cos - cosine
assert isclose(math.cos(0), 1.0)
assert isclose(math.cos(math.pi / 2), 0.0)
assert isclose(math.cos(math.pi), -1.0)
assert isclose(math.cos(-math.pi), -1.0)
# tan - tangent
assert isclose(math.tan(0), 0.0)
assert isclose(math.tan(math.pi / 4), 1.0)
assert isclose(math.tan(-math.pi / 4), -1.0)
# asin - arc sine (inverse sine)
assert isclose(math.asin(0), 0.0)
assert isclose(math.asin(1), math.pi / 2)
assert isclose(math.asin(-1), -math.pi / 2)
assert isclose(math.asin(0.5), math.pi / 6)
# acos - arc cosine (inverse cosine)
assert isclose(math.acos(1), 0.0)
assert isclose(math.acos(0), math.pi / 2)
assert isclose(math.acos(-1), math.pi)
assert isclose(math.acos(0.5), math.pi / 3)
# atan - arc tangent (inverse tangent)
assert isclose(math.atan(0), 0.0)
assert isclose(math.atan(1), math.pi / 4)
assert isclose(math.atan(-1), -math.pi / 4)
# atan2 - arc tangent of y/x (handles quadrants correctly)
assert isclose(math.atan2(0, 1), 0.0)
assert isclose(math.atan2(1, 0), math.pi / 2)
assert isclose(math.atan2(0, -1), math.pi)
assert isclose(math.atan2(-1, 0), -math.pi / 2)
assert isclose(math.atan2(1, 1), math.pi / 4)
assert isclose(math.atan2(-1, -1), -3 * math.pi / 4)
# ============================================================
# Angle conversion functions: degrees, radians
# ============================================================
# degrees - convert radians to degrees
assert isclose(math.degrees(0), 0.0)
assert isclose(math.degrees(math.pi), 180.0)
assert isclose(math.degrees(math.pi / 2), 90.0)
assert isclose(math.degrees(2 * math.pi), 360.0)
# radians - convert degrees to radians
assert isclose(math.radians(0), 0.0)
assert isclose(math.radians(180), math.pi)
assert isclose(math.radians(90), math.pi / 2)
assert isclose(math.radians(360), 2 * math.pi)
# ============================================================
# Arithmetic functions: fmod, modf, copysign, fsum, gcd, factorial
# ============================================================
# fmod - floating point modulo
assert isclose(math.fmod(5.0, 3.0), 2.0)
assert isclose(math.fmod(-5.0, 3.0), -2.0)
assert isclose(math.fmod(5.0, -3.0), 2.0)
assert isclose(math.fmod(-5.0, -3.0), -2.0)
assert isclose(math.fmod(4.0, 3.0), 1.0)
assert isclose(math.fmod(-4.0, 3.0), -1.0)
assert isclose(math.fmod(-2.0, 3.0), -2.0)
assert isclose(math.fmod(2.0, 3.0), 2.0)
# modf - split into fractional and integer parts
frac, integer = math.modf(1.5)
assert isclose(frac, 0.5)
assert isclose(integer, 1.0)
frac, integer = math.modf(-1.5)
assert isclose(frac, -0.5)
assert isclose(integer, -1.0)
frac, integer = math.modf(2.0)
assert isclose(frac, 0.0)
assert isclose(integer, 2.0)
frac, integer = math.modf(0.0)
assert isclose(frac, 0.0)
assert isclose(integer, 0.0)
# copysign - return x with the sign of y
assert isclose(math.copysign(1.0, -1.0), -1.0)
assert isclose(math.copysign(-1.0, 1.0), 1.0)
assert isclose(math.copysign(1.0, 1.0), 1.0)
assert isclose(math.copysign(-1.0, -1.0), -1.0)
assert isclose(math.copysign(5.0, -0.0), -5.0)
assert isclose(math.copysign(0.0, -1.0), 0.0) # sign of zero may vary
# fsum - accurate floating point sum
assert math.fsum([0.1] * 10) == 1.0
assert math.fsum([]) == 0.0
assert math.fsum([1.0, 2.0, 3.0]) == 6.0
assert math.fsum([0.1, 0.2, 0.3]) == 0.6
# test gcd
# gcd - greatest common divisor
assert math.gcd(10, 5) == 5
assert math.gcd(10, 6) == 2
assert math.gcd(10, 7) == 1
assert math.gcd(10, 10) == 10
assert math.gcd(-10, 10) == 10
assert math.gcd(10, -10) == 10
assert math.gcd(-10, -10) == 10
assert math.gcd(0, 5) == 5
assert math.gcd(5, 0) == 5
assert math.gcd(0, 0) == 0
# test fmod
assert math.fmod(-2.0, 3.0) == -2.0
assert math.fmod(2.0, 3.0) == 2.0
assert math.fmod(4.0, 3.0) == 1.0
assert math.fmod(-4.0, 3.0) == -1.0
# test modf
x, y = math.modf(1.5)
assert isclose(x, 0.5)
assert isclose(y, 1.0)
x, y = math.modf(-1.5)
assert isclose(x, -0.5)
assert isclose(y, -1.0)
# test factorial
# factorial - n!
assert math.factorial(0) == 1
assert math.factorial(1) == 1
assert math.factorial(2) == 2
assert math.factorial(3) == 6
assert math.factorial(4) == 24
assert math.factorial(5) == 120
assert math.factorial(10) == 3628800
# ============================================================
# Special value tests
# ============================================================
# Test NaN generation from invalid operations
a = -0.1
a = a ** a
assert math.isnan(a)
assert not math.isinf(a)
# Test infinity
assert math.isinf(float("inf"))
assert math.isinf(float("-inf"))
assert not math.isnan(float("inf"))

View File

@ -41,15 +41,15 @@ assertEqual(math.gcd(10, 10), 10)
assertEqual(math.gcd(-10, 10), 10)
# test isfinite, isinf, isnan
assertEqual(math.isfinite(math.pi), 1)
assertEqual(math.isfinite(math.inf), 0)
assertEqual(math.isfinite(math.nan), 0)
assertEqual(math.isinf(math.pi), 0)
assertEqual(math.isinf(math.inf), 1)
assertEqual(math.isinf(math.nan), 0)
assertEqual(math.isnan(math.pi), 0)
assertEqual(math.isnan(math.inf), 0)
assertEqual(math.isnan(math.nan), 1)
assertEqual(math.isfinite(math.pi), True)
assertEqual(math.isfinite(math.inf), False)
assertEqual(math.isfinite(math.nan), False)
assertEqual(math.isinf(math.pi), False)
assertEqual(math.isinf(math.inf), True)
assertEqual(math.isinf(math.nan), False)
assertEqual(math.isnan(math.pi), False)
assertEqual(math.isnan(math.inf), False)
assertEqual(math.isnan(math.nan), True)
# test exp
assertEqual(math.exp(0), 1.0)