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::Ast;
use crate::parser::StringPart;
use crate::spans::Spanned; use crate::spans::Spanned;
use crate::value::*; use crate::value::*;
use chumsky::prelude::SimpleSpan; use chumsky::prelude::SimpleSpan;
@ -6,6 +7,7 @@ use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use std::cell::OnceCell; use std::cell::OnceCell;
use std::rc::Rc; use std::rc::Rc;
use std::string;
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
pub enum Op { pub enum Op {
@ -53,6 +55,10 @@ pub enum Op {
Decrement, Decrement,
Truncate, Truncate,
MatchDepth, MatchDepth,
Panic,
EmptyString,
ConcatStrings,
Stringify,
Call, Call,
@ -156,6 +162,10 @@ impl std::fmt::Display for Op {
Truncate => "truncate", Truncate => "truncate",
Duplicate => "duplicate", Duplicate => "duplicate",
MatchDepth => "match_depth", MatchDepth => "match_depth",
Panic => "panic",
EmptyString => "empty_string",
ConcatStrings => "concat_strings",
Stringify => "stringify",
Eq => "eq", Eq => "eq",
Add => "add", Add => "add",
@ -199,7 +209,8 @@ impl Chunk {
Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse
| PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch
| TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq | 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}") println!("{i:04}: {op}")
} }
Constant | MatchConstant => { Constant | MatchConstant => {
@ -1094,10 +1105,40 @@ impl Compiler {
self.emit_op(Op::JumpBack); self.emit_op(Op::JumpBack);
self.emit_byte(self.len() - self.loop_idx()); self.emit_byte(self.len() - self.loop_idx());
} }
Interpolated(..) Panic(msg) => {
| Arguments(..) 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 | Placeholder
| Panic(..)
| Do(..) | Do(..)
| Splat(..) | Splat(..)
| InterpolatedPattern(..) | InterpolatedPattern(..)
@ -1119,7 +1160,7 @@ impl Compiler {
| MatchFalse | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen | MatchFalse | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen
| PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple | PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple
| LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At | LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At
| Not => { | Not | Panic | EmptyString | ConcatStrings | Stringify => {
println!("{i:04}: {op}") println!("{i:04}: {op}")
} }
Constant | MatchConstant => { Constant | MatchConstant => {

View File

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

View File

@ -51,6 +51,7 @@ impl std::fmt::Display for Value {
False => write!(f, "false"), False => write!(f, "false"),
Keyword(idx) => write!(f, ":{idx}"), Keyword(idx) => write!(f, ":{idx}"),
Interned(idx) => write!(f, "\"@{idx}\""), Interned(idx) => write!(f, "\"@{idx}\""),
String(str) => write!(f, "\"{str}\""),
Number(n) => write!(f, "{n}"), Number(n) => write!(f, "{n}"),
Tuple(members) => write!( Tuple(members) => write!(
f, f,
@ -98,6 +99,10 @@ impl Value {
let str_str = ctx.strings[*i]; let str_str = ctx.strings[*i];
format!("\"{str_str}\"") format!("\"{str_str}\"")
} }
String(str) => {
let str_str = str.to_string();
format!("\"{str_str}\"")
}
Keyword(i) => { Keyword(i) => {
let kw_str = ctx.keywords[*i]; let kw_str = ctx.keywords[*i];
format!(":{kw_str}") format!(":{kw_str}")
@ -122,13 +127,62 @@ impl Value {
.join(", "); .join(", ");
format!("#{{{members}}}") format!("#{{{members}}}")
} }
String(s) => s.as_ref().clone(),
Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show(ctx)), Box(x) => format!("box {{ {} }}", x.as_ref().borrow().show(ctx)),
Fn(lfn) => format!("fn {}", lfn.get().unwrap().name), Fn(lfn) => format!("fn {}", lfn.get().unwrap().name),
_ => todo!(), _ => 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 { pub fn type_of(&self) -> &'static str {
use Value::*; use Value::*;
match self { match self {

View File

@ -6,6 +6,7 @@ use chumsky::prelude::SimpleSpan;
use imbl::{HashMap, Vector}; use imbl::{HashMap, Vector};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt;
use std::mem::swap; use std::mem::swap;
use std::rc::Rc; use std::rc::Rc;
@ -18,7 +19,19 @@ use std::rc::Rc;
// pub trace: Vec<Trace>, // pub trace: Vec<Trace>,
// pub extra: String, // 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)] #[derive(Debug, Clone, PartialEq)]
pub struct Trace { pub struct Trace {
@ -102,7 +115,11 @@ impl<'a> Vm<'a> {
} }
pub fn panic(&mut self, msg: &'static str) { 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) { pub fn interpret(&mut self) {
@ -554,6 +571,35 @@ impl<'a> Vm<'a> {
self.push(negated); self.push(negated);
self.ip += 1; 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!(), Call => todo!(),
} }
} }