add string interpolation

This commit is contained in:
Scott Richmond 2025-05-30 11:44:32 -04:00
parent 82ac6744ca
commit cda217f6ef
4 changed files with 154 additions and 10 deletions

View File

@ -1,4 +1,5 @@
use crate::parser::Ast;
use crate::parser::StringPart;
use crate::spans::Spanned;
use crate::value::*;
use chumsky::prelude::SimpleSpan;
@ -6,6 +7,7 @@ use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::cell::OnceCell;
use std::rc::Rc;
use std::string;
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum Op {
@ -53,6 +55,10 @@ pub enum Op {
Decrement,
Truncate,
MatchDepth,
Panic,
EmptyString,
ConcatStrings,
Stringify,
Call,
@ -156,6 +162,10 @@ impl std::fmt::Display for Op {
Truncate => "truncate",
Duplicate => "duplicate",
MatchDepth => "match_depth",
Panic => "panic",
EmptyString => "empty_string",
ConcatStrings => "concat_strings",
Stringify => "stringify",
Eq => "eq",
Add => "add",
@ -199,7 +209,8 @@ impl Chunk {
Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch
| TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq
| Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not => {
| Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic
| EmptyString | ConcatStrings | Stringify => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
@ -1094,10 +1105,40 @@ impl Compiler {
self.emit_op(Op::JumpBack);
self.emit_byte(self.len() - self.loop_idx());
}
Interpolated(..)
| Arguments(..)
Panic(msg) => {
self.visit(msg);
self.emit_op(Op::Panic);
}
Interpolated(parts) => {
self.emit_op(Op::EmptyString);
self.stack_depth += 1;
for part in parts {
let str = &part.0;
match str {
StringPart::Inline(_) => unreachable!(),
StringPart::Data(str) => {
let allocated = Value::String(Rc::new(str.clone()));
self.emit_constant(allocated);
self.emit_op(Op::ConcatStrings);
self.stack_depth -= 1;
}
StringPart::Word(word) => {
self.emit_op(Op::PushBinding);
let biter = self.bindings.iter().rev();
for binding in biter {
if binding.name == word.as_str() {
self.emit_byte(binding.stack_pos);
break;
}
}
self.emit_op(Op::Stringify);
self.emit_op(Op::ConcatStrings);
}
}
}
}
Arguments(..)
| Placeholder
| Panic(..)
| Do(..)
| Splat(..)
| InterpolatedPattern(..)
@ -1119,7 +1160,7 @@ impl Compiler {
| MatchFalse | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen
| PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple
| LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At
| Not => {
| Not | Panic | EmptyString | ConcatStrings | Stringify => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {

View File

@ -65,7 +65,7 @@ pub fn run(src: &'static str) {
let result = vm.run();
let output = match result {
Ok(val) => val.show(&compiler.chunk),
Err(panic) => format!("{:?}", panic),
Err(panic) => format!("Ludus panicked! {panic}"),
};
vm.print_stack();
println!("{output}");
@ -74,7 +74,10 @@ pub fn run(src: &'static str) {
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src = "
or (true, true, 42)
let x = 42
let y = :foo
let z = \"thing\"
\"{x} {y} {z}\"
";
run(src);
}

View File

@ -51,6 +51,7 @@ impl std::fmt::Display for Value {
False => write!(f, "false"),
Keyword(idx) => write!(f, ":{idx}"),
Interned(idx) => write!(f, "\"@{idx}\""),
String(str) => write!(f, "\"{str}\""),
Number(n) => write!(f, "{n}"),
Tuple(members) => write!(
f,
@ -98,6 +99,10 @@ impl Value {
let str_str = ctx.strings[*i];
format!("\"{str_str}\"")
}
String(str) => {
let str_str = str.to_string();
format!("\"{str_str}\"")
}
Keyword(i) => {
let kw_str = ctx.keywords[*i];
format!(":{kw_str}")
@ -122,13 +127,62 @@ impl Value {
.join(", ");
format!("#{{{members}}}")
}
String(s) => s.as_ref().clone(),
Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show(ctx)),
Fn(lfn) => format!("fn {}", lfn.get().unwrap().name),
_ => todo!(),
}
}
pub fn stringify(&self, ctx: &Chunk) -> String {
use Value::*;
match &self {
Nil => "nil".to_string(),
True => "true".to_string(),
False => "false".to_string(),
Number(n) => format!("{n}"),
Interned(i) => {
let str_str = ctx.strings[*i];
str_str.to_string()
}
Keyword(i) => {
let kw_str = ctx.keywords[*i];
format!(":{kw_str}")
}
Tuple(t) => {
let members = t
.iter()
.map(|e| e.stringify(ctx))
.collect::<Vec<_>>()
.join(", ");
members.to_string()
}
List(l) => {
let members = l
.iter()
.map(|e| e.stringify(ctx))
.collect::<Vec<_>>()
.join(", ");
members.to_string()
}
Dict(d) => {
let members = d
.iter()
.map(|(k, v)| {
let key_show = Value::Keyword(*k).show(ctx);
let value_show = v.show(ctx);
format!("{key_show} {value_show}")
})
.collect::<Vec<_>>()
.join(", ");
members.to_string()
}
String(s) => s.as_ref().clone(),
Box(x) => x.as_ref().borrow().stringify(ctx),
Fn(lfn) => lfn.get().unwrap().name.to_string(),
_ => todo!(),
}
}
pub fn type_of(&self) -> &'static str {
use Value::*;
match self {

View File

@ -6,6 +6,7 @@ use chumsky::prelude::SimpleSpan;
use imbl::{HashMap, Vector};
use num_traits::FromPrimitive;
use std::cell::RefCell;
use std::fmt;
use std::mem::swap;
use std::rc::Rc;
@ -18,7 +19,19 @@ use std::rc::Rc;
// pub trace: Vec<Trace>,
// pub extra: String,
// }
pub struct Panic(&'static str);
pub enum Panic {
Str(&'static str),
String(String),
}
impl fmt::Display for Panic {
fn fmt(self: &Panic, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Panic::Str(msg) => write!(f, "{msg}"),
Panic::String(msg) => write!(f, "{msg}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Trace {
@ -102,7 +115,11 @@ impl<'a> Vm<'a> {
}
pub fn panic(&mut self, msg: &'static str) {
self.result = Some(Err(Panic(msg)));
self.result = Some(Err(Panic::Str(msg)));
}
pub fn panic_with(&mut self, msg: String) {
self.result = Some(Err(Panic::String(msg)));
}
pub fn interpret(&mut self) {
@ -554,6 +571,35 @@ impl<'a> Vm<'a> {
self.push(negated);
self.ip += 1;
}
Panic => {
let msg = self.pop().show(self.chunk);
self.panic_with(msg);
}
EmptyString => {
self.push(Value::String(Rc::new("".to_string())));
self.ip += 1;
}
ConcatStrings => {
let second = self.pop();
let first = self.pop();
let combined = match (first, second) {
(Value::String(first), Value::String(second)) => {
let mut newstr = first.as_ref().clone();
newstr.push_str(second.as_str());
Value::String(Rc::new(newstr))
}
_ => unreachable!(),
};
self.push(combined);
self.ip += 1;
}
Stringify => {
let to_stringify = self.pop();
let the_string = to_stringify.stringify(self.chunk);
let stringified = Value::String(Rc::new(the_string));
self.push(stringified);
self.ip += 1;
}
Call => todo!(),
}
}