From c5c1717e57f6007e7103ea8b79d567f1ff242b70 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 4 Dec 2024 15:03:09 -0500 Subject: [PATCH] add ref-counted string type --- src/base.rs | 21 +++++++++++++++++++++ src/main.rs | 2 +- src/parser.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/value.rs | 17 ++++++++++++----- src/vm.rs | 8 +++++--- 5 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/base.rs b/src/base.rs index 9d89a34..a594f15 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,6 +1,7 @@ // Solution to the #[derive(Clone)] problem: https://stackoverflow.com/questions/27883509/can-you-clone-a-closure use crate::value::*; use crate::vm::*; +use std::rc::Rc; // use imbl::*; // use std::fmt; @@ -68,6 +69,24 @@ pub fn store<'src>(b: &Value<'src>, val: &Value<'src>) -> LResult<'src> { } } +// TODO: do better than returning just the docstr +// name, patterns, AND docstring +pub fn doc<'src>(f: &Value<'src>) -> LResult<'src> { + match f { + Value::Fn(f) => { + let Fn { name, doc, .. } = **f; + if let Some(docstr) = doc { + Ok(Value::AllocatedString(Rc::new(format!("{name}: {docstr}")))) + } else { + Ok(Value::InternedString( + doc.unwrap_or("no documentation found"), + )) + } + } + _ => Ok(Value::InternedString("no documentation found")), + } +} + pub fn base<'src>() -> Vec<(&'src str, Value<'src>)> { let mut base = vec![]; @@ -83,6 +102,8 @@ pub fn base<'src>() -> Vec<(&'src str, Value<'src>)> { base.push(("store", Value::Base(Base::Binary("store", store)))); + base.push(("doc", Value::Base(Base::Unary("doc", doc)))); + base } diff --git a/src/main.rs b/src/main.rs index 5a48bae..0316b42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,7 +63,7 @@ fn foo { () -> :foo (_) -> :bar } -foo () +doc (foo) "; let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { diff --git a/src/parser.rs b/src/parser.rs index 6d1cd8a..97fb05c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -33,6 +33,22 @@ impl<'src> fmt::Display for MatchClause<'src> { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum StringPart<'src> { + Data(&'src str), + Word(&'src str), +} + +impl<'src> fmt::Display for StringPart<'src> { + fn fmt(self: &StringPart<'src>, f: &mut fmt::Formatter) -> fmt::Result { + let rep = match self { + StringPart::Data(s) => format!("{{{s}}}"), + StringPart::Word(s) => s.to_string(), + }; + write!(f, "{}", rep) + } +} + #[derive(Clone, Debug, PartialEq, Dissectible)] pub enum Ast<'src> { Error, @@ -43,6 +59,7 @@ pub enum Ast<'src> { Keyword(&'src str), Word(&'src str), String(&'src str), + Interpolated(Vec>>), Block(Vec>), If(Box>, Box>, Box>), Tuple(Vec>), @@ -71,6 +88,16 @@ impl fmt::Display for Ast<'_> { Ast::Error => write!(f, "Error"), Ast::Nil => write!(f, "nil"), Ast::String(s) => write!(f, "String: \"{}\"", s), + Ast::Interpolated(strs) => { + write!( + f, + "String: \"{}\"", + strs.iter() + .map(|(s, _)| s.to_string()) + .collect::>() + .join("") + ) + } Ast::Boolean(b) => write!(f, "Boolean: {}", b), Ast::Number(n) => write!(f, "Number: {}", n), Ast::Keyword(k) => write!(f, "Keyword: :{}", k), @@ -277,6 +304,17 @@ impl fmt::Display for Pattern<'_> { } } +// TODO: write this +// 1. we need an enum for a return type +// either a string part or a word part +// 2. our string types, both patterns and values, now contains a vec of nodes +// 3. this should loop through the string and allow for escaping braces +// consider using Rust-style escapes: {{}}, rather than \{\} +// {{{foo}}} +pub fn parse_string<'src>(s: &'src str) -> Vec> { + vec![] +} + pub fn parser<'src, I>( ) -> impl Parser<'src, I, Spanned>, extra::Err, Span>>> + Clone where diff --git a/src/value.rs b/src/value.rs index f9914cf..f0ab649 100644 --- a/src/value.rs +++ b/src/value.rs @@ -10,6 +10,7 @@ use struct_scalpel::Dissectible; pub struct Fn<'src> { pub name: &'src str, pub body: &'src Vec>, + pub doc: &'src Option<&'src str>, } #[derive(Debug, Dissectible)] @@ -19,7 +20,10 @@ pub enum Value<'src> { Boolean(bool), Number(f64), Keyword(&'src str), - String(&'src str), + // TODO: add a "runtime-generated" string type that wraps a Rust String + // this is necessary for nice documentation and string interpolation + InternedString(&'src str), + AllocatedString(Rc), // on the heap for now Tuple(Rc>), Args(Rc>), @@ -49,7 +53,8 @@ impl<'src> Clone for Value<'src> { match self { Value::Nil => Value::Nil, Value::Boolean(b) => Value::Boolean(*b), - Value::String(s) => Value::String(s), + Value::InternedString(s) => Value::InternedString(s), + Value::AllocatedString(s) => Value::AllocatedString(s.clone()), Value::Keyword(s) => Value::Keyword(s), Value::Number(n) => Value::Number(*n), Value::Tuple(t) => Value::Tuple(t.clone()), @@ -72,7 +77,8 @@ impl fmt::Display for Value<'_> { Value::Boolean(b) => write!(f, "{}", b), Value::Number(n) => write!(f, "{}", n), Value::Keyword(k) => write!(f, ":{}", k), - Value::String(s) => write!(f, "\"{}\"", s), + Value::InternedString(s) => write!(f, "\"{}\"", s), + Value::AllocatedString(s) => write!(f, "\"{}\"", s), Value::Fn(fun) => write!(f, "fn {}", fun.name), Value::Tuple(t) | Value::Args(t) => write!( f, @@ -132,7 +138,8 @@ impl Value<'_> { Value::Boolean(_) => Value::Keyword("boolean"), Value::Keyword(_) => Value::Keyword("keyword"), Value::Tuple(_) => Value::Keyword("tuple"), - Value::String(_) => Value::Keyword("string"), + Value::InternedString(_) => Value::Keyword("string"), + Value::AllocatedString(_) => Value::Keyword("string"), Value::List(_) => Value::Keyword("list"), Value::Dict(_) => Value::Keyword("dict"), Value::Fn(_) => Value::Keyword("fn"), @@ -152,7 +159,7 @@ impl<'src> PartialEq for Value<'src> { (Value::Nil, Value::Nil) => true, (Value::Boolean(x), Value::Boolean(y)) => x == y, (Value::Number(x), Value::Number(y)) => x == y, - (Value::String(x), Value::String(y)) => x == y, + (Value::InternedString(x), Value::InternedString(y)) => x == y, (Value::Keyword(x), Value::Keyword(y)) => x == y, (Value::Tuple(x), Value::Tuple(y)) => x == y, (Value::List(x), Value::List(y)) => x == y, diff --git a/src/vm.rs b/src/vm.rs index df08f28..14f36a8 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -62,7 +62,7 @@ pub fn match_pattern<'src, 'a>( (Pattern::Number(x), Value::Number(y)) => match_eq(x, y, ctx), (Pattern::Boolean(x), Value::Boolean(y)) => match_eq(x, y, ctx), (Pattern::Keyword(x), Value::Keyword(y)) => match_eq(x, y, ctx), - (Pattern::String(x), Value::String(y)) => match_eq(x, y, ctx), + (Pattern::String(x), Value::InternedString(y)) => match_eq(x, y, ctx), (Pattern::Word(w), val) => { ctx.push((w, val.clone())); Some(ctx) @@ -281,7 +281,8 @@ pub fn eval<'src, 'a>( Ast::Boolean(b) => Ok(Value::Boolean(*b)), Ast::Number(n) => Ok(Value::Number(*n)), Ast::Keyword(k) => Ok(Value::Keyword(k)), - Ast::String(s) => Ok(Value::String(s)), + Ast::String(s) => Ok(Value::InternedString(s)), + Ast::Interpolated(s) => todo!(), Ast::Block(exprs) => { let to = ctx.len(); let mut result = Value::Nil; @@ -414,10 +415,11 @@ pub fn eval<'src, 'a>( let value = eval(&value.0, ctx)?; match_clauses(&value, clauses, ctx) } - Ast::Fn(name, clauses, ..) => { + Ast::Fn(name, clauses, doc) => { let the_fn = Value::Fn::<'src>(Rc::new(Fn::<'src> { name, body: clauses, + doc, })); ctx.push((name, the_fn.clone())); Ok(the_fn)