Skip to content

Commit 80c678d

Browse files
Chore: Expand IT, fix lambda and implement dict help methods (BuiltIn).
1 parent b3486b4 commit 80c678d

9 files changed

Lines changed: 107 additions & 22 deletions

File tree

compiler/src/modules/parser/control.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,14 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
285285
self.eat(TokenType::Colon);
286286
self.compile_block();
287287

288-
// Only need a jump if there are more `except` arms after this one
289-
// (i.e. we might fall through to another arm). Peek ahead.
290-
if matches!(self.peek(), Some(TokenType::Except | TokenType::Else | TokenType::Finally)) {
288+
// Always emit Jump after handler body — needed to skip remaining arms,
289+
// the re-raise, and any else/finally. Exception: bare except with nothing
290+
// after it (no else, no finally, no more except arms).
291+
let more = matches!(
292+
self.peek(),
293+
Some(TokenType::Except | TokenType::Else | TokenType::Finally)
294+
);
295+
if !had_bare || more {
291296
self.chunk.emit(OpCode::Jump, 0);
292297
end_jumps.push(self.chunk.instructions.len() - 1);
293298
}

compiler/src/modules/parser/expr.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,10 +394,15 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
394394

395395
pub(super) fn parse_lambda(&mut self) {
396396
let mut params = Vec::new();
397+
let mut defaults = 0u16;
397398
if !matches!(self.peek(), Some(TokenType::Colon)) {
398399
loop {
399400
let p = self.advance();
400401
params.push(self.lexeme(&p).to_string());
402+
if self.eat_if(TokenType::Equal) {
403+
self.expr();
404+
defaults += 1;
405+
}
401406
if !self.eat_if(TokenType::Comma) {
402407
break;
403408
}
@@ -411,7 +416,7 @@ impl<'src, I: Iterator<Item = Token>> Parser<'src, I> {
411416
s.chunk.emit(OpCode::ReturnValue, 0);
412417
});
413418
let fi = self.chunk.functions.len() as u16;
414-
self.chunk.functions.push((params, body, 0, u16::MAX));
419+
self.chunk.functions.push((params, body, defaults, u16::MAX));
415420
self.chunk.emit(OpCode::MakeFunction, fi);
416421
}
417422
}

compiler/src/modules/parser/types.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,15 @@ fn unescape(s: &str) -> String {
239239

240240
// Array containing the default data types for the language.
241241
pub const BUILTIN_TYPES: &[&str] = &[
242-
"int", "float", "str", "bool", "list",
243-
"tuple", "dict", "set", "range", "type", "NoneType"
242+
"int", "float", "str", "bool", "list",
243+
"tuple", "dict", "set", "range", "type", "NoneType",
244+
"Exception", "BaseException",
245+
"ValueError", "TypeError", "NameError", "KeyError",
246+
"IndexError", "AttributeError", "RuntimeError",
247+
"ZeroDivisionError", "OverflowError", "MemoryError",
248+
"RecursionError", "StopIteration", "NotImplementedError",
249+
"OSError", "IOError", "ImportError", "ModuleNotFoundError",
250+
"AssertionError", "ArithmeticError", "LookupError",
244251
];
245252

246253
// Map each opcode to its functional group for streamlined execution logic.

compiler/src/modules/vm/builtins.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,11 +337,22 @@ impl<'a> VM<'a> {
337337
let (arg2, obj) = (self.pop()?, self.pop()?);
338338
let obj_ty = self.type_name(obj);
339339

340+
// Exception handler pushes the exception as a Str (e.g. "ZeroDivisionError").
341+
// Allow isinstance("ZeroDivisionError", ZeroDivisionError) to return true
342+
// by also comparing the string content against the type name.
343+
let obj_as_str: Option<String> = if obj.is_heap() {
344+
match self.heap.get(obj) {
345+
HeapObj::Str(s) => Some(s.clone()),
346+
_ => None,
347+
}
348+
} else { None };
349+
340350
let check = |t: Val, heap: &HeapPool| -> Result<bool, VmErr> {
341351
match heap.get(t) {
342352
HeapObj::Type(name) => Ok(
343353
name == obj_ty
344354
|| (obj_ty == "bool" && name == "int")
355+
|| obj_as_str.as_deref() == Some(name.as_str())
345356
),
346357
_ => Err(VmErr::Type("isinstance() arg 2 must be a type or tuple of types")),
347358
}

compiler/src/modules/vm/handlers/attr.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ impl<'a> VM<'a> {
1313

1414
let method_id = match (self.type_name(obj), name.as_str()) {
1515
("list", "append") => BuiltinMethodId::ListAppend,
16+
("dict", "keys") => BuiltinMethodId::DictKeys,
17+
("dict", "values") => BuiltinMethodId::DictValues,
18+
("dict", "items") => BuiltinMethodId::DictItems,
1619
(ty, attr) => {
17-
// Out-of-line so the hot path stays small.
1820
return Err(attr_not_found(ty, attr));
1921
}
2022
};

compiler/src/modules/vm/handlers/function.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,47 @@ impl<'a> VM<'a> {
234234
self.push(Val::none());
235235
Ok(())
236236
}
237+
DictKeys => {
238+
if !positional.is_empty() {
239+
return Err(VmErr::Type("keys() takes no arguments"));
240+
}
241+
let keys: Vec<Val> = match self.heap.get(recv) {
242+
HeapObj::Dict(rc) => rc.borrow().keys().collect(),
243+
_ => return Err(VmErr::Type("keys: receiver is not a dict")),
244+
};
245+
let val = self.heap.alloc(HeapObj::List(Rc::new(RefCell::new(keys))))?;
246+
self.push(val);
247+
Ok(())
248+
}
249+
DictValues => {
250+
if !positional.is_empty() {
251+
return Err(VmErr::Type("values() takes no arguments"));
252+
}
253+
let values: Vec<Val> = match self.heap.get(recv) {
254+
HeapObj::Dict(rc) => rc.borrow().entries.iter().map(|&(_, v)| v).collect(),
255+
_ => return Err(VmErr::Type("values: receiver is not a dict")),
256+
};
257+
let val = self.heap.alloc(HeapObj::List(Rc::new(RefCell::new(values))))?;
258+
self.push(val);
259+
Ok(())
260+
}
261+
DictItems => {
262+
if !positional.is_empty() {
263+
return Err(VmErr::Type("items() takes no arguments"));
264+
}
265+
let pairs: Vec<(Val, Val)> = match self.heap.get(recv) {
266+
HeapObj::Dict(rc) => rc.borrow().entries.clone(),
267+
_ => return Err(VmErr::Type("items: receiver is not a dict")),
268+
};
269+
let mut items: Vec<Val> = Vec::with_capacity(pairs.len());
270+
for (k, v) in pairs {
271+
let t = self.heap.alloc(HeapObj::Tuple(vec![k, v]))?;
272+
items.push(t);
273+
}
274+
let val = self.heap.alloc(HeapObj::List(Rc::new(RefCell::new(items))))?;
275+
self.push(val);
276+
Ok(())
277+
}
237278
}
238279
}
239280
}

compiler/src/modules/vm/ops.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ impl<'a> VM<'a> {
123123
HeapObj::Func(i, _) => format!("<function {}>", i),
124124
HeapObj::BoundMethod(_, id) => match id {
125125
BuiltinMethodId::ListAppend => "<built-in method append>".into(),
126+
BuiltinMethodId::DictKeys => "<built-in method keys>".into(),
127+
BuiltinMethodId::DictValues => "<built-in method values>".into(),
128+
BuiltinMethodId::DictItems => "<built-in method items>".into(),
126129
},
127130
HeapObj::Slice(s, e, st) => format!("slice({}, {}, {})",
128131
self.display(*s), self.display(*e), self.display(*st)),

compiler/src/modules/vm/types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,9 @@ pub enum HeapObj {
466466
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
467467
pub enum BuiltinMethodId {
468468
ListAppend,
469+
DictKeys,
470+
DictValues,
471+
DictItems,
469472
}
470473

471474
/*

compiler/tests/cases/vm.json

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -285,54 +285,40 @@
285285
{"src": "counter: int = 0\nfor _ in range(100):\n counter += 1\n counter += 2\nprint(counter)", "output": ["300"], "result": "None"},
286286
{"src": "counter: float = 0.0\nfor _ in range(50): counter += 1\nprint(counter)", "output": ["50.0"], "result": "None"},
287287
{"src": "total: int = 0\nfor i in range(5):\n for j in range(5):\n total += 1\nprint(total)", "output": ["25"], "result": "None"},
288-
289288
{"src": "if (n := 5) > 3:\n print(n)", "output": ["5"], "result": "None"},
290289
{"src": "data = [1, 2, 3]\nif (length := len(data)) > 0:\n print(length)", "output": ["3"], "result": "None"},
291290
{"src": "x = 0\nwhile (x := x + 1) < 5:\n print(x)", "output": ["1", "2", "3", "4"], "result": "None"},
292-
293291
{"src": "name = 'world'\nage = 42\nprint(f'{name} is {age} years old')", "output": ["world is 42 years old"], "result": "None"},
294292
{"src": "print(f'{1 + 2 + 3}')", "output": ["6"], "result": "None"},
295293
{"src": "x = [1, 2, 3]\nprint(f'len={len(x)}')", "output": ["len=3"], "result": "None"},
296-
297294
{"src": "a = [1,2,3,4,5]\nprint(a[1:4:2])", "output": ["[2, 4]"], "result": "None"},
298295
{"src": "a = [1,2,3,4,5]\nprint(a[-3:])", "output": ["[3, 4, 5]"], "result": "None"},
299296
{"src": "a = [1,2,3,4,5]\nprint(a[:-2])", "output": ["[1, 2, 3]"], "result": "None"},
300297
{"src": "s = 'abcdef'\nprint(s[::-1])", "output": ["fedcba"], "result": "None"},
301298
{"src": "t = (1,2,3,4,5)\nprint(t[1:-1])", "output": ["(2, 3, 4)"], "result": "None"},
302-
303299
{"src": "a = 2**100\nb = 2**100\nprint(a == b)", "output": ["True"], "result": "None"},
304300
{"src": "a = 2**100\nb = 2**99\nprint(a > b)", "output": ["True"], "result": "None"},
305301
{"src": "print((2**100) // 7 * 7 + (2**100) % 7 == 2**100)", "output": ["True"], "result": "None"},
306-
307302
{"src": "s = {1, 2, 3}\nprint(2 in s)", "output": ["True"], "result": "None"},
308303
{"src": "s = {1, 2, 3}\nprint(5 in s)", "output": ["False"], "result": "None"},
309304
{"src": "print(set([1,1,2,2,3]) == {1,2,3})", "output": ["True"], "result": "None"},
310-
311-
{"src": "try:\n x = 1/0\nexcept:\n x = -1\nprint(x)", "output": ["-1"], "result": "None"},
312-
{"src": "try:\n x = int('abc')\nexcept:\n x = 0\nprint(x)", "output": ["0"], "result": "None"},
313-
314305
{"src": "def f():\n yield 1\n yield 2\n yield 3\nresult = list(f())\nprint(result)", "output": ["[1, 2, 3]"], "result": "None"},
315306
{"src": "def squares(n):\n for i in range(n):\n yield i*i\nprint(list(squares(5)))", "output": ["[0, 1, 4, 9, 16]"], "result": "None"},
316-
317307
{"src": "def make_counter():\n count = 0\n def inc():\n return count + 1\n return inc()\nprint(make_counter())", "output": ["1"], "result": "None"},
318-
319308
{"src": "x = -2**100\nprint(x)", "output": ["-1267650600228229401496703205376"], "result": "None"},
320309
{"src": "print(abs(-2**60 - 2**60))", "output": ["2305843009213693952"], "result": "None"},
321-
322310
{"src": "for i in range(3):\n for j in range(3):\n print(i, j)", "output": ["0 0", "0 1", "0 2", "1 0", "1 1", "1 2", "2 0", "2 1", "2 2"], "result": "None"},
323311
{"src": "try:\n x = 1/0\nexcept:\n x = -1\nprint(x)", "output": ["-1"], "result": "None"},
324312
{"src": "try:\n x = int('abc')\nexcept:\n x = 0\nprint(x)", "output": ["0"], "result": "None"},
325313
{"src": "try:\n x = 1\nexcept:\n x = -1\nprint(x)", "output": ["1"], "result": "None"},
326314
{"src": "try:\n a = [1,2,3]\n b = a[10]\nexcept:\n b = -1\nprint(b)", "output": ["-1"], "result": "None"},
327315
{"src": "result = 0\nfor i in range(5):\n try:\n result += 10 // (i - 2)\n except:\n result += 100\nprint(result)", "output": ["100"], "result": "None"},
328-
329316
{"src": "calls = []\ndef side(name, val):\n calls.append(name)\n return val\nresult = side('a', False) and side('b', True)\nprint(calls)\nprint(result)", "output": ["['a']", "False"], "result": "None"},
330317
{"src": "calls = []\ndef side(name, val):\n calls.append(name)\n return val\nresult = side('a', True) or side('b', False)\nprint(calls)\nprint(result)", "output": ["['a']", "True"], "result": "None"},
331318
{"src": "x = 0 or 'fallback'\nprint(x)", "output": ["fallback"], "result": "None"},
332319
{"src": "x = 'first' and 'second'\nprint(x)", "output": ["second"], "result": "None"},
333320
{"src": "x = None or 0 or [] or 'default'\nprint(x)", "output": ["default"], "result": "None"},
334321
{"src": "x = 1 and 2 and 3\nprint(x)", "output": ["3"], "result": "None"},
335-
336322
{"src": "print(1 < 2 < 3)", "output": ["True"], "result": "None"},
337323
{"src": "print(1 < 3 < 2)", "output": ["False"], "result": "None"},
338324
{"src": "print(3 < 2 < 1)", "output": ["False"], "result": "None"},
@@ -341,5 +327,27 @@
341327
{"src": "x = 5\nprint(0 < x < 10)", "output": ["True"], "result": "None"},
342328
{"src": "x = 15\nprint(0 < x < 10)", "output": ["False"], "result": "None"},
343329
{"src": "print(1 == 1 == 1)", "output": ["True"], "result": "None"},
344-
{"src": "print(1 == 1 == 2)", "output": ["False"], "result": "None"}
330+
{"src": "print(1 == 1 == 2)", "output": ["False"], "result": "None"},
331+
{"src": "x = []\nx.append(1)\nx.append(2)\nprint(x)", "output": ["[1, 2]"], "result": "None"},
332+
{"src": "print([y := x + 1 for x in range(3)])", "output": ["[1, 2, 3]"], "result": "None"},
333+
{"src": "try:\n x = 1/0\nexcept ValueError:\n x = -1\nexcept:\n x = -2\nprint(x)", "output": ["-2"], "result": "None"},
334+
{"src": "try:\n raise 'boom'\nexcept:\n x = 'caught'\nprint(x)", "output": ["caught"], "result": "None"},
335+
{"src": "def outer():\n items = []\n def inner(x):\n items.append(x)\n inner(1)\n inner(2)\n return items\nprint(outer())", "output": ["[1, 2]"], "result": "None"},
336+
{"src": "def f(x, acc=[]):\n acc.append(x)\n return acc\nprint(f(1))\nprint(f(2))", "output": ["[1]", "[1, 2]"], "result": "None"},
337+
{"src": "a, b, c = 1, 2, 3\nprint(a < b < c)", "output": ["True"], "result": "None"},
338+
{"src": "a, b, c = 1, 3, 2\nprint(a < b < c)", "output": ["False"], "result": "None"},
339+
{"src": "for i in range(3):\n pass\nelse:\n print('done')", "output": ["done"], "result": "None"},
340+
{"src": "for i in range(3):\n if i == 1:\n break\nelse:\n print('done')\nprint('end')", "output": ["end"], "result": "None"},
341+
{"src": "x = 0\nwhile x < 3:\n x += 1\nelse:\n print('done')", "output": ["done"], "result": "None"},
342+
{"src": "d = {}\nfor i in [3,1,2]:\n d[i] = i\nprint(list(d.keys()))", "output": ["[3, 1, 2]"], "result": "None"},
343+
{"src": "d = {'a': 1, 'b': 2}\nprint(list(d.values()))", "output": ["[1, 2]"], "result": "None"},
344+
{"src": "d = {}\nfor i in [3,1,2]:\n d[i] = i\nprint(list(d.keys()))", "output": ["[3, 1, 2]"], "result": "None"},
345+
{"src": "print(2**100 > 2**99 > 2**98)", "output": ["True"], "result": "None"},
346+
{"src": "print({x % 3 for x in range(9)})", "output": ["{0, 1, 2}"], "result": "None"},
347+
{"src": "x = 0\nfor i in range(1000):\n x = {'v': i}\nprint(x['v'])", "output": ["999"], "result": "None"},
348+
{"src": "def fib(n):\n if n < 2: return n\n return fib(n-1) + fib(n-2)\nprint(fib(15))", "output": ["610"], "result": "None"},
349+
{"src": "print('abcde'[3:0:-1])", "output": ["dcb"], "result": "None"},
350+
{"src": "for a, *b in [[1,2,3],[4,5,6]]:\n print(a, b)", "output": ["1 [2, 3]", "4 [5, 6]"], "result": "None"},
351+
{"src": "try:\n x = 1/0\nexcept ZeroDivisionError as e:\n print('caught')", "output": ["caught"], "result": "None"},
352+
{"src": "f = lambda x, y=10: x + y\nprint(f(5))\nprint(f(5, 1))", "output": ["15", "6"], "result": "None"}
345353
]

0 commit comments

Comments
 (0)