Skip to content

Commit ceda834

Browse files
Feat: Add missing Builtin's methods.
1 parent 89cb384 commit ceda834

5 files changed

Lines changed: 314 additions & 4 deletions

File tree

compiler/src/modules/parser/types.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ pub enum OpCode {
2222
BitNot, Shl, Shr, In, NotIn, Is, IsNot, UnpackSequence, BuildTuple, SetupWith, ExitWith, Yield,
2323
Del, Assert, Global, Nonlocal, UnpackArgs, ListAppend, SetAdd, MapAdd, BuildSet, RaiseFrom,
2424
UnpackEx, LoadEllipsis, Await, MakeCoroutine, YieldFrom, TypeAlias, StoreItem, Dup2,
25-
JumpIfFalseOrPop, JumpIfTrueOrPop, Dup, CallMethod, CallMethodArgs,
25+
JumpIfFalseOrPop, JumpIfTrueOrPop, Dup, CallMethod, CallMethodArgs, CallAll, CallAny, CallBin,
26+
CallOct, CallHex, CallDivmod, CallPow, CallRepr, CallReversed, CallCallable, CallId, CallHash,
2627
}
2728

2829
/* O(1) lookup table mapping Python builtin names to their corresponding OpCodes. */
@@ -51,6 +52,18 @@ pub(super) fn builtin(name: &str) -> Option<(OpCode, bool)> {
5152
"isinstance" => Some((OpCode::CallIsInstance, true)),
5253
"chr" => Some((OpCode::CallChr, true)),
5354
"ord" => Some((OpCode::CallOrd, true)),
55+
"all" => Some((OpCode::CallAll, true)),
56+
"any" => Some((OpCode::CallAny, true)),
57+
"bin" => Some((OpCode::CallBin, true)),
58+
"oct" => Some((OpCode::CallOct, true)),
59+
"hex" => Some((OpCode::CallHex, true)),
60+
"divmod" => Some((OpCode::CallDivmod, true)),
61+
"pow" => Some((OpCode::CallPow, true)),
62+
"repr" => Some((OpCode::CallRepr, true)),
63+
"reversed" => Some((OpCode::CallReversed, true)),
64+
"callable" => Some((OpCode::CallCallable, true)),
65+
"id" => Some((OpCode::CallId, true)),
66+
"hash" => Some((OpCode::CallHash, true)),
5467
_ => None,
5568
}
5669
}
@@ -262,7 +275,7 @@ impl OpCode {
262275
BuildList | BuildTuple | BuildDict | BuildSet | BuildSlice | BuildString => OpCategory::Build,
263276
GetItem | StoreItem | UnpackSequence | UnpackEx | FormatValue => OpCategory::Container,
264277
ListAppend | SetAdd | MapAdd => OpCategory::Comprehension,
265-
Call | MakeFunction | MakeCoroutine | CallPrint | CallLen | CallAbs | CallStr | CallInt | CallRange | CallChr | CallType | CallFloat | CallBool | CallRound | CallMin | CallMax | CallSum | CallSorted | CallEnumerate | CallZip | CallList | CallTuple | CallDict | CallIsInstance | CallSet | CallInput | CallOrd | CallMethod | CallMethodArgs => OpCategory::Function,
278+
Call | MakeFunction | MakeCoroutine | CallPrint | CallLen | CallAbs | CallStr | CallInt | CallRange | CallChr | CallType | CallFloat | CallBool | CallRound | CallMin | CallMax | CallSum | CallSorted | CallEnumerate | CallZip | CallList | CallTuple | CallDict | CallIsInstance | CallSet | CallInput | CallOrd | CallMethod | CallMethodArgs | CallAll | CallAny | CallBin | CallOct | CallHex | CallDivmod | CallPow | CallRepr | CallReversed | CallCallable | CallId | CallHash => OpCategory::Function,
266279
Phi => OpCategory::Ssa,
267280
Yield => OpCategory::Yield,
268281
Assert | Del | Global | Nonlocal | TypeAlias | Import | ImportFrom | SetupExcept | PopExcept | Raise | RaiseFrom | Await | YieldFrom => OpCategory::Side,

compiler/src/modules/vm/builtins.rs

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,48 @@ use core::cell::RefCell;
99
use alloc::{string::{String, ToString}, vec::Vec, vec, rc::Rc};
1010
use crate::modules::fx::FxHashSet as HashSet;
1111

12+
fn i64_to_radix(n: i64, radix: u32, prefix: &str) -> String {
13+
if n == 0 {
14+
let mut s = String::with_capacity(prefix.len() + 1);
15+
s.push_str(prefix); s.push('0'); return s;
16+
}
17+
let neg = n < 0;
18+
let mut abs = (n as i128).unsigned_abs();
19+
let mut buf = [0u8; 64];
20+
let mut i = buf.len();
21+
while abs > 0 {
22+
i -= 1;
23+
let d = (abs % radix as u128) as u8;
24+
buf[i] = if d < 10 { b'0' + d } else { b'a' + d - 10 };
25+
abs /= radix as u128;
26+
}
27+
let mut s = String::with_capacity(prefix.len() + buf.len() - i + 1);
28+
if neg { s.push('-'); }
29+
s.push_str(prefix);
30+
s.push_str(unsafe { core::str::from_utf8_unchecked(&buf[i..]) });
31+
s
32+
}
33+
34+
fn bigint_to_radix(b: &BigInt, radix: u32, prefix: &str) -> String {
35+
let mut out = String::new();
36+
if b.neg { out.push('-'); }
37+
out.push_str(prefix);
38+
if b.is_zero() { out.push('0'); return out; }
39+
40+
let radix_big = BigInt::from_i64(radix as i64);
41+
let mut work = b.abs();
42+
let mut digits = Vec::<u8>::new();
43+
while !work.is_zero() {
44+
let (q, r) = work.divmod(&radix_big).unwrap();
45+
let d = r.to_i64_checked().unwrap() as u8;
46+
digits.push(if d < 10 { b'0' + d } else { b'a' + d - 10 });
47+
work = q;
48+
}
49+
digits.reverse();
50+
out.push_str(unsafe { core::str::from_utf8_unchecked(&digits) });
51+
out
52+
}
53+
1254
fn normalize_index(i: i64, len: usize) -> usize {
1355
(if i < 0 { len as i64 + i } else { i }) as usize
1456
}
@@ -596,4 +638,207 @@ impl<'a> VM<'a> {
596638
}
597639
Ok(())
598640
}
641+
642+
// ─── Logical reductions ──────────────────────────────────────────
643+
644+
pub fn call_all(&mut self, op: u16) -> Result<(), VmErr> {
645+
if op != 1 { return Err(cold_type("all() takes exactly 1 argument")); }
646+
let o = self.pop()?;
647+
let items = self.extract_iter(o, true)?;
648+
for v in items {
649+
if !self.truthy(v) {
650+
self.push(Val::bool(false));
651+
return Ok(());
652+
}
653+
}
654+
self.push(Val::bool(true));
655+
Ok(())
656+
}
657+
658+
pub fn call_any(&mut self, op: u16) -> Result<(), VmErr> {
659+
if op != 1 { return Err(cold_type("any() takes exactly 1 argument")); }
660+
let o = self.pop()?;
661+
let items = self.extract_iter(o, true)?;
662+
for v in items {
663+
if self.truthy(v) {
664+
self.push(Val::bool(true));
665+
return Ok(());
666+
}
667+
}
668+
self.push(Val::bool(false));
669+
Ok(())
670+
}
671+
672+
// ─── Number formatting ───────────────────────────────────────────
673+
674+
pub fn call_bin(&mut self) -> Result<(), VmErr> {
675+
let o = self.pop()?;
676+
let s = self.int_to_radix_string(o, 2, "0b")?;
677+
let v = self.heap.alloc(HeapObj::Str(s))?;
678+
self.push(v); Ok(())
679+
}
680+
pub fn call_oct(&mut self) -> Result<(), VmErr> {
681+
let o = self.pop()?;
682+
let s = self.int_to_radix_string(o, 8, "0o")?;
683+
let v = self.heap.alloc(HeapObj::Str(s))?;
684+
self.push(v); Ok(())
685+
}
686+
pub fn call_hex(&mut self) -> Result<(), VmErr> {
687+
let o = self.pop()?;
688+
let s = self.int_to_radix_string(o, 16, "0x")?;
689+
let v = self.heap.alloc(HeapObj::Str(s))?;
690+
self.push(v); Ok(())
691+
}
692+
693+
/// Converts int/BigInt to "<prefix><digits>" with optional sign.
694+
fn int_to_radix_string(&self, v: Val, radix: u32, prefix: &str) -> Result<String, VmErr> {
695+
if v.is_int() {
696+
return Ok(i64_to_radix(v.as_int(), radix, prefix));
697+
}
698+
if v.is_heap()
699+
&& let HeapObj::BigInt(b) = self.heap.get(v) {
700+
return Ok(bigint_to_radix(b, radix, prefix));
701+
}
702+
Err(cold_type("integer required"))
703+
}
704+
705+
// ─── Arithmetic ──────────────────────────────────────────────────
706+
707+
pub fn call_divmod(&mut self) -> Result<(), VmErr> {
708+
let b = self.pop()?;
709+
let a = self.pop()?;
710+
let (Some(ba), Some(bb)) = (self.to_bigint(a), self.to_bigint(b))
711+
else { return Err(cold_type("divmod() requires integer operands")); };
712+
let (q, r) = ba.divmod(&bb).ok_or(VmErr::ZeroDiv)?;
713+
let qv = self.bigint_to_val(q)?;
714+
let rv = self.bigint_to_val(r)?;
715+
let val = self.heap.alloc(HeapObj::Tuple(vec![qv, rv]))?;
716+
self.push(val); Ok(())
717+
}
718+
719+
pub fn call_pow(&mut self, op: u16) -> Result<(), VmErr> {
720+
let args = self.pop_n(op as usize)?;
721+
match args.len() {
722+
2 => {
723+
let r = self.pow_2arg(args[0], args[1])?;
724+
self.push(r);
725+
Ok(())
726+
}
727+
3 => {
728+
// Modular exponentiation: (a ** b) % c
729+
let (Some(base), Some(modulus)) =
730+
(self.to_bigint(args[0]), self.to_bigint(args[2]))
731+
else { return Err(cold_type("pow() with 3 args requires integers")); };
732+
if !args[1].is_int() {
733+
return Err(cold_type("pow() with 3 args requires integer exponent"));
734+
}
735+
let mut e = args[1].as_int();
736+
if e < 0 { return Err(cold_value("pow() exponent must be non-negative")); }
737+
if modulus.is_zero() { return Err(VmErr::ZeroDiv); }
738+
739+
let mut result = BigInt::from_i64(1);
740+
let (_, mut base) = base.divmod(&modulus).unwrap();
741+
while e > 0 {
742+
if e & 1 == 1 {
743+
let (_, r) = result.mul(&base).divmod(&modulus).unwrap();
744+
result = r;
745+
}
746+
let (_, b2) = base.mul(&base).divmod(&modulus).unwrap();
747+
base = b2;
748+
e >>= 1;
749+
}
750+
let r = self.bigint_to_val(result)?;
751+
self.push(r);
752+
Ok(())
753+
}
754+
_ => Err(cold_type("pow() takes 2 or 3 arguments")),
755+
}
756+
}
757+
758+
fn pow_2arg(&mut self, a: Val, b: Val) -> Result<Val, VmErr> {
759+
if let Some(ba) = self.to_bigint(a) && b.is_int() {
760+
let exp = b.as_int();
761+
if exp >= 0 {
762+
if exp > u32::MAX as i64 {
763+
return Err(cold_value("pow() exponent too large"));
764+
}
765+
return self.bigint_to_val(ba.pow_u32(exp as u32));
766+
}
767+
return Ok(Val::float(fpowi(ba.to_f64(), exp as i32)));
768+
}
769+
let to_f = |v: Val| -> Result<f64, VmErr> {
770+
if v.is_int() { Ok(v.as_int() as f64) }
771+
else if v.is_float() { Ok(v.as_float()) }
772+
else { Err(cold_type("pow() requires numeric operands")) }
773+
};
774+
Ok(Val::float(fpowf(to_f(a)?, to_f(b)?)))
775+
}
776+
777+
// ─── Introspection ───────────────────────────────────────────────
778+
779+
pub fn call_repr(&mut self) -> Result<(), VmErr> {
780+
let o = self.pop()?;
781+
let s = self.repr(o);
782+
let v = self.heap.alloc(HeapObj::Str(s))?;
783+
self.push(v); Ok(())
784+
}
785+
786+
pub fn call_callable(&mut self) -> Result<(), VmErr> {
787+
let o = self.pop()?;
788+
let result = if o.is_heap() {
789+
matches!(self.heap.get(o),
790+
HeapObj::Func(..) | HeapObj::BoundMethod(..) | HeapObj::Type(_))
791+
} else { false };
792+
self.push(Val::bool(result));
793+
Ok(())
794+
}
795+
796+
pub fn call_id(&mut self) -> Result<(), VmErr> {
797+
let o = self.pop()?;
798+
// Use the NaN-boxed bit pattern as identity. Truncate to fit INT_MAX.
799+
let id = ((o.0 as i64).abs()) & Val::INT_MAX;
800+
self.push(Val::int(id));
801+
Ok(())
802+
}
803+
804+
pub fn call_hash(&mut self) -> Result<(), VmErr> {
805+
use core::hash::{Hash, Hasher};
806+
let o = self.pop()?;
807+
let mut h = crate::modules::fx::FxHasher::default();
808+
if o.is_int() { o.as_int().hash(&mut h); }
809+
else if o.is_float() { o.as_float().to_bits().hash(&mut h); }
810+
else if o.is_bool() { o.as_bool().hash(&mut h); }
811+
else if o.is_none() { 0u64.hash(&mut h); }
812+
else if o.is_heap() {
813+
match self.heap.get(o) {
814+
HeapObj::Str(s) => s.hash(&mut h),
815+
HeapObj::BigInt(b) => { b.neg.hash(&mut h); b.limbs.hash(&mut h); }
816+
HeapObj::Tuple(items) => {
817+
for v in items { v.0.hash(&mut h); }
818+
}
819+
HeapObj::List(_) | HeapObj::Dict(_) | HeapObj::Set(_) => {
820+
return Err(cold_type("unhashable type"));
821+
}
822+
_ => o.0.hash(&mut h),
823+
}
824+
}
825+
self.push(Val::int(h.finish() as i64 & Val::INT_MAX));
826+
Ok(())
827+
}
828+
829+
// ─── Sequence ops ────────────────────────────────────────────────
830+
831+
pub fn call_reversed(&mut self) -> Result<(), VmErr> {
832+
let o = self.pop()?;
833+
if !o.is_heap() { return Err(cold_type("reversed() requires a sequence")); }
834+
let mut items = if let HeapObj::Str(s) = self.heap.get(o) {
835+
let s = s.clone();
836+
self.str_to_char_vals(&s)?
837+
} else {
838+
self.extract_iter(o, true)?
839+
};
840+
items.reverse();
841+
let v = self.heap.alloc(HeapObj::List(Rc::new(RefCell::new(items))))?;
842+
self.push(v); Ok(())
843+
}
599844
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ impl<'a> VM<'a> {
3535
OpCode::CallSet => self.call_set(operand),
3636
OpCode::CallPrint => { self.mark_impure(); self.call_print(operand) }
3737
OpCode::CallInput => { self.mark_impure(); self.call_input() }
38+
OpCode::CallAll => self.call_all(operand),
39+
OpCode::CallAny => self.call_any(operand),
40+
OpCode::CallBin => self.call_bin(),
41+
OpCode::CallOct => self.call_oct(),
42+
OpCode::CallHex => self.call_hex(),
43+
OpCode::CallDivmod => self.call_divmod(),
44+
OpCode::CallPow => self.call_pow(operand),
45+
OpCode::CallRepr => self.call_repr(),
46+
OpCode::CallReversed => self.call_reversed(),
47+
OpCode::CallCallable => self.call_callable(),
48+
OpCode::CallId => self.call_id(),
49+
OpCode::CallHash => self.call_hash(),
3850
_ => unreachable!("non-function opcode in handle_function"),
3951
}
4052
}

compiler/src/modules/vm/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,10 @@ impl<'a> VM<'a> {
538538
| OpCode::CallList | OpCode::CallTuple | OpCode::CallEnumerate | OpCode::CallIsInstance
539539
| OpCode::CallRange | OpCode::CallRound | OpCode::CallMin | OpCode::CallMax
540540
| OpCode::CallSum | OpCode::CallZip | OpCode::CallDict | OpCode::CallSet
541-
| OpCode::CallInput | OpCode::MakeFunction | OpCode::MakeCoroutine => {
541+
| OpCode::CallInput | OpCode::MakeFunction | OpCode::MakeCoroutine
542+
| OpCode::CallAll | OpCode::CallAny | OpCode::CallBin | OpCode::CallOct
543+
| OpCode::CallHex | OpCode::CallDivmod | OpCode::CallPow | OpCode::CallRepr
544+
| OpCode::CallReversed | OpCode::CallCallable | OpCode::CallId | OpCode::CallHash => {
542545
self.handle_function(ins.opcode, op, chunk, slots)?;
543546
}
544547

compiler/tests/cases/vm.json

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,5 +425,42 @@
425425
{"src": "def f(a, b):\n return a + b\nprint(f(**{'a': 1, 'b': 2}))", "output": ["3"], "result": "None"},
426426
{"src": "def f(a, b, c):\n return a + b + c\nprint(f(1, **{'b': 2, 'c': 3}))", "output": ["6"], "result": "None"},
427427
{"src": "def f(a, b, c, d):\n return a + b + c + d\nprint(f(*[1, 2], **{'c': 3, 'd': 4}))", "output": ["10"], "result": "None"},
428-
{"src": "def f(*args):\n return args\nprint(f(*[1, 2, 3]))", "output": ["[1, 2, 3]"], "result": "None"}
428+
{"src": "def f(*args):\n return args\nprint(f(*[1, 2, 3]))", "output": ["[1, 2, 3]"], "result": "None"},
429+
{"src": "print(all([1, 2, 3]))", "output": ["True"], "result": "None"},
430+
{"src": "print(all([1, 0, 3]))", "output": ["False"], "result": "None"},
431+
{"src": "print(all([]))", "output": ["True"], "result": "None"},
432+
{"src": "print(any([0, 0, 1]))", "output": ["True"], "result": "None"},
433+
{"src": "print(any([0, 0, 0]))", "output": ["False"], "result": "None"},
434+
{"src": "print(any([]))", "output": ["False"], "result": "None"},
435+
{"src": "print(bin(5))", "output": ["0b101"], "result": "None"},
436+
{"src": "print(bin(0))", "output": ["0b0"], "result": "None"},
437+
{"src": "print(bin(-10))", "output": ["-0b1010"], "result": "None"},
438+
{"src": "print(oct(8))", "output": ["0o10"], "result": "None"},
439+
{"src": "print(oct(0))", "output": ["0o0"], "result": "None"},
440+
{"src": "print(hex(255))", "output": ["0xff"], "result": "None"},
441+
{"src": "print(hex(16))", "output": ["0x10"], "result": "None"},
442+
{"src": "print(hex(-256))", "output": ["-0x100"], "result": "None"},
443+
{"src": "print(divmod(7, 3))", "output": ["(2, 1)"], "result": "None"},
444+
{"src": "print(divmod(10, 4))", "output": ["(2, 2)"], "result": "None"},
445+
{"src": "print(divmod(-7, 3))", "output": ["(-3, 2)"], "result": "None"},
446+
{"src": "print(pow(2, 10))", "output": ["1024"], "result": "None"},
447+
{"src": "print(pow(3, 5))", "output": ["243"], "result": "None"},
448+
{"src": "print(pow(2, 10, 1000))", "output": ["24"], "result": "None"},
449+
{"src": "print(pow(7, 13, 19))", "output": ["7"], "result": "None"},
450+
{"src": "print(repr('hello'))", "output": ["'hello'"], "result": "None"},
451+
{"src": "print(repr(42))", "output": ["42"], "result": "None"},
452+
{"src": "print(repr([1, 2, 3]))", "output": ["[1, 2, 3]"], "result": "None"},
453+
{"src": "print(reversed([1, 2, 3]))", "output": ["[3, 2, 1]"], "result": "None"},
454+
{"src": "print(reversed('abc'))", "output": ["['c', 'b', 'a']"], "result": "None"},
455+
{"src": "print(reversed((1, 2, 3)))", "output": ["[3, 2, 1]"], "result": "None"},
456+
{"src": "print(callable(str))", "output": ["True"], "result": "None"},
457+
{"src": "def f(): pass\nprint(callable(f))", "output": ["True"], "result": "None"},
458+
{"src": "print(callable(42))", "output": ["False"], "result": "None"},
459+
{"src": "print(callable('hello'))", "output": ["False"], "result": "None"},
460+
{"src": "print(callable(int))", "output": ["True"], "result": "None"},
461+
{"src": "x = 42\nprint(id(x) == id(x))", "output": ["True"], "result": "None"},
462+
{"src": "print(hash(42) == hash(42))", "output": ["True"], "result": "None"},
463+
{"src": "print(hash('hello') == hash('hello'))", "output": ["True"], "result": "None"},
464+
{"src": "print(hash((1, 2, 3)) == hash((1, 2, 3)))", "output": ["True"], "result": "None"},
465+
{"src": "hash([1, 2, 3])", "output": [], "result": "None", "error": "unhashable type"}
429466
]

0 commit comments

Comments
 (0)