Skip to content

Commit 8c8cf63

Browse files
Feat: Add string OpCodes and IT.
1 parent 80c678d commit 8c8cf63

5 files changed

Lines changed: 169 additions & 9 deletions

File tree

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,20 @@ impl<'a> VM<'a> {
1212
let obj = self.pop()?;
1313

1414
let method_id = match (self.type_name(obj), name.as_str()) {
15-
("list", "append") => BuiltinMethodId::ListAppend,
16-
("dict", "keys") => BuiltinMethodId::DictKeys,
17-
("dict", "values") => BuiltinMethodId::DictValues,
18-
("dict", "items") => BuiltinMethodId::DictItems,
15+
("list", "append") => BuiltinMethodId::ListAppend,
16+
("dict", "keys") => BuiltinMethodId::DictKeys,
17+
("dict", "values") => BuiltinMethodId::DictValues,
18+
("dict", "items") => BuiltinMethodId::DictItems,
19+
("str", "upper") => BuiltinMethodId::StrUpper,
20+
("str", "lower") => BuiltinMethodId::StrLower,
21+
("str", "strip") => BuiltinMethodId::StrStrip,
22+
("str", "split") => BuiltinMethodId::StrSplit,
23+
("str", "join") => BuiltinMethodId::StrJoin,
24+
("str", "replace") => BuiltinMethodId::StrReplace,
25+
("str", "startswith") => BuiltinMethodId::StrStartswith,
26+
("str", "endswith") => BuiltinMethodId::StrEndswith,
27+
("str", "find") => BuiltinMethodId::StrFind,
28+
("str", "count") => BuiltinMethodId::StrCount,
1929
(ty, attr) => {
2030
return Err(attr_not_found(ty, attr));
2131
}

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

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,126 @@ impl<'a> VM<'a> {
275275
self.push(val);
276276
Ok(())
277277
}
278+
StrUpper => {
279+
let s = self.recv_str(recv)?;
280+
let val = self.heap.alloc(HeapObj::Str(s.to_uppercase()))?;
281+
self.push(val);
282+
Ok(())
283+
}
284+
StrLower => {
285+
let s = self.recv_str(recv)?;
286+
let val = self.heap.alloc(HeapObj::Str(s.to_lowercase()))?;
287+
self.push(val);
288+
Ok(())
289+
}
290+
StrStrip => {
291+
let s = self.recv_str(recv)?;
292+
let val = self.heap.alloc(HeapObj::Str(s.trim().to_string()))?;
293+
self.push(val);
294+
Ok(())
295+
}
296+
StrSplit => {
297+
let s = self.recv_str(recv)?;
298+
let parts: Vec<Val> = if positional.is_empty() {
299+
s.split_whitespace()
300+
.map(|p| self.heap.alloc(HeapObj::Str(p.to_string())))
301+
.collect::<Result<_, _>>()?
302+
} else {
303+
let sep = self.val_to_str(positional[0])?;
304+
s.split(sep.as_str())
305+
.map(|p| self.heap.alloc(HeapObj::Str(p.to_string())))
306+
.collect::<Result<_, _>>()?
307+
};
308+
let val = self.heap.alloc(HeapObj::List(Rc::new(RefCell::new(parts))))?;
309+
self.push(val);
310+
Ok(())
311+
}
312+
StrJoin => {
313+
let sep = self.recv_str(recv)?;
314+
if positional.len() != 1 {
315+
return Err(VmErr::Type("join() takes exactly one argument"));
316+
}
317+
let items = match self.heap.get(positional[0]) {
318+
HeapObj::List(rc) => rc.borrow().clone(),
319+
HeapObj::Tuple(v) => v.clone(),
320+
_ => return Err(VmErr::Type("join() argument must be iterable")),
321+
};
322+
let mut parts: Vec<String> = Vec::with_capacity(items.len());
323+
for v in items {
324+
parts.push(self.val_to_str(v)?);
325+
}
326+
let val = self.heap.alloc(HeapObj::Str(parts.join(sep.as_str())))?;
327+
self.push(val);
328+
Ok(())
329+
}
330+
StrReplace => {
331+
if positional.len() != 2 {
332+
return Err(VmErr::Type("replace() takes exactly 2 arguments"));
333+
}
334+
let s = self.recv_str(recv)?;
335+
let old = self.val_to_str(positional[0])?;
336+
let new = self.val_to_str(positional[1])?;
337+
let val = self.heap.alloc(HeapObj::Str(s.replace(old.as_str(), new.as_str())))?;
338+
self.push(val);
339+
Ok(())
340+
}
341+
StrStartswith => {
342+
if positional.len() != 1 {
343+
return Err(VmErr::Type("startswith() takes exactly one argument"));
344+
}
345+
let s = self.recv_str(recv)?;
346+
let prefix = self.val_to_str(positional[0])?;
347+
self.push(Val::bool(s.starts_with(prefix.as_str())));
348+
Ok(())
349+
}
350+
StrEndswith => {
351+
if positional.len() != 1 {
352+
return Err(VmErr::Type("endswith() takes exactly one argument"));
353+
}
354+
let s = self.recv_str(recv)?;
355+
let suffix = self.val_to_str(positional[0])?;
356+
self.push(Val::bool(s.ends_with(suffix.as_str())));
357+
Ok(())
358+
}
359+
StrFind => {
360+
if positional.len() != 1 {
361+
return Err(VmErr::Type("find() takes exactly one argument"));
362+
}
363+
let s = self.recv_str(recv)?;
364+
let sub = self.val_to_str(positional[0])?;
365+
let idx = s.find(sub.as_str())
366+
.map(|i| s[..i].chars().count() as i64)
367+
.unwrap_or(-1);
368+
self.push(Val::int(idx));
369+
Ok(())
370+
}
371+
StrCount => {
372+
if positional.len() != 1 {
373+
return Err(VmErr::Type("count() takes exactly one argument"));
374+
}
375+
let s = self.recv_str(recv)?;
376+
let sub = self.val_to_str(positional[0])?;
377+
let n = s.matches(sub.as_str()).count() as i64;
378+
self.push(Val::int(n));
379+
Ok(())
380+
}
381+
}
382+
}
383+
384+
// Extracts the string content from a Str receiver.
385+
fn recv_str(&self, recv: Val) -> Result<String, VmErr> {
386+
match self.heap.get(recv) {
387+
HeapObj::Str(s) => Ok(s.clone()),
388+
_ => Err(VmErr::Type("method requires a string receiver")),
389+
}
390+
}
391+
392+
// Converts a Val to String for use as a method argument.
393+
fn val_to_str(&self, v: Val) -> Result<String, VmErr> {
394+
match self.heap.get(v) {
395+
HeapObj::Str(s) => Ok(s.clone()),
396+
_ => Err(VmErr::Type("argument must be a string")),
278397
}
279398
}
399+
280400
}

compiler/src/modules/vm/ops.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,20 @@ impl<'a> VM<'a> {
122122
HeapObj::Type(name) => format!("<class '{}'>", name),
123123
HeapObj::Func(i, _) => format!("<function {}>", i),
124124
HeapObj::BoundMethod(_, id) => match id {
125-
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(),
125+
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(),
129+
BuiltinMethodId::StrUpper => "<built-in method upper>".into(),
130+
BuiltinMethodId::StrLower => "<built-in method lower>".into(),
131+
BuiltinMethodId::StrStrip => "<built-in method strip>".into(),
132+
BuiltinMethodId::StrSplit => "<built-in method split>".into(),
133+
BuiltinMethodId::StrJoin => "<built-in method join>".into(),
134+
BuiltinMethodId::StrReplace => "<built-in method replace>".into(),
135+
BuiltinMethodId::StrStartswith => "<built-in method startswith>".into(),
136+
BuiltinMethodId::StrEndswith => "<built-in method endswith>".into(),
137+
BuiltinMethodId::StrFind => "<built-in method find>".into(),
138+
BuiltinMethodId::StrCount => "<built-in method count>".into(),
129139
},
130140
HeapObj::Slice(s, e, st) => format!("slice({}, {}, {})",
131141
self.display(*s), self.display(*e), self.display(*st)),

compiler/src/modules/vm/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,16 @@ pub enum BuiltinMethodId {
469469
DictKeys,
470470
DictValues,
471471
DictItems,
472+
StrUpper,
473+
StrLower,
474+
StrStrip,
475+
StrSplit,
476+
StrJoin,
477+
StrReplace,
478+
StrStartswith,
479+
StrEndswith,
480+
StrFind,
481+
StrCount,
472482
}
473483

474484
/*

compiler/tests/cases/vm.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,5 +349,15 @@
349349
{"src": "print('abcde'[3:0:-1])", "output": ["dcb"], "result": "None"},
350350
{"src": "for a, *b in [[1,2,3],[4,5,6]]:\n print(a, b)", "output": ["1 [2, 3]", "4 [5, 6]"], "result": "None"},
351351
{"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"}
352+
{"src": "f = lambda x, y=10: x + y\nprint(f(5))\nprint(f(5, 1))", "output": ["15", "6"], "result": "None"},
353+
{"src": "print('hello'.upper())", "output": ["HELLO"], "result": "None"},
354+
{"src": "print('HELLO'.lower())", "output": ["hello"], "result": "None"},
355+
{"src": "print(' hi '.strip())", "output": ["hi"], "result": "None"},
356+
{"src": "print('a,b,c'.split(','))", "output": ["['a', 'b', 'c']"], "result": "None"},
357+
{"src": "print(','.join(['a','b','c']))", "output": ["a,b,c"], "result": "None"},
358+
{"src": "print('hello'.replace('l','r'))", "output": ["herro"], "result": "None"},
359+
{"src": "print('hello'.startswith('he'))", "output": ["True"], "result": "None"},
360+
{"src": "print('hello'.endswith('lo'))", "output": ["True"], "result": "None"},
361+
{"src": "print('hello'.find('ll'))", "output": ["2"], "result": "None"},
362+
{"src": "print('hello'.count('l'))", "output": ["2"], "result": "None"}
353363
]

0 commit comments

Comments
 (0)