conlang: Use interned strings (Sym) for all symbols
This commit is contained in:
@@ -6,6 +6,7 @@ use super::{
|
||||
temp_type_impl::ConValue,
|
||||
BuiltIn, Callable,
|
||||
};
|
||||
use cl_ast::Sym;
|
||||
use std::{
|
||||
io::{stdout, Write},
|
||||
rc::Rc,
|
||||
@@ -68,7 +69,7 @@ builtins! {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
|
||||
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + b).into(),
|
||||
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b.to_string()).into(),
|
||||
_ => Err(Error::TypeError)?
|
||||
})
|
||||
}
|
||||
@@ -230,7 +231,7 @@ macro builtins (
|
||||
$(let [$($arg),*] = to_args(args)?;)?
|
||||
$body
|
||||
}
|
||||
fn name(&self) -> &str { stringify!($name) }
|
||||
fn name(&self) -> Sym { stringify!($name).into() }
|
||||
}
|
||||
)*
|
||||
}
|
||||
@@ -242,7 +243,7 @@ macro cmp ($a:expr, $b:expr, $empty:literal, $op:tt) {
|
||||
(ConValue::Int(a), ConValue::Int(b)) => Ok(ConValue::Bool(a $op b)),
|
||||
(ConValue::Bool(a), ConValue::Bool(b)) => Ok(ConValue::Bool(a $op b)),
|
||||
(ConValue::Char(a), ConValue::Char(b)) => Ok(ConValue::Bool(a $op b)),
|
||||
(ConValue::String(a), ConValue::String(b)) => Ok(ConValue::Bool(a $op b)),
|
||||
(ConValue::String(a), ConValue::String(b)) => Ok(ConValue::Bool(a.get() $op b.get())),
|
||||
_ => Err(Error::TypeError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Interpret for Let {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Let { mutable: _, name: Identifier(name), ty: _, init } = self;
|
||||
let init = init.as_ref().map(|i| i.interpret(env)).transpose()?;
|
||||
env.insert(name, init);
|
||||
env.insert(*name, init);
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@ impl Interpret for Assign {
|
||||
};
|
||||
// Get the initializer and the tail
|
||||
let init = tail.interpret(env)?;
|
||||
let target = env.get_mut(head)?;
|
||||
let target = env.get_mut(*head)?;
|
||||
|
||||
if let AssignKind::Plain = op {
|
||||
use std::mem::discriminant as variant;
|
||||
@@ -187,7 +187,7 @@ impl Interpret for Assign {
|
||||
return Ok(ConValue::Empty);
|
||||
}
|
||||
let Some(target) = target else {
|
||||
return Err(Error::NotInitialized(head.into()));
|
||||
return Err(Error::NotInitialized(*head));
|
||||
};
|
||||
|
||||
match op {
|
||||
@@ -302,9 +302,9 @@ impl Interpret for Unary {
|
||||
let Unary { kind, tail } = self;
|
||||
let operand = tail.interpret(env)?;
|
||||
match kind {
|
||||
UnaryKind::Deref => env.call("deref", &[operand]),
|
||||
UnaryKind::Neg => env.call("neg", &[operand]),
|
||||
UnaryKind::Not => env.call("not", &[operand]),
|
||||
UnaryKind::Deref => env.call("deref".into(), &[operand]),
|
||||
UnaryKind::Neg => env.call("neg".into(), &[operand]),
|
||||
UnaryKind::Not => env.call("not".into(), &[operand]),
|
||||
UnaryKind::At => {
|
||||
println!("{operand}");
|
||||
Ok(operand)
|
||||
@@ -335,7 +335,7 @@ impl Interpret for Path {
|
||||
if parts.len() == 1 {
|
||||
match parts.last().expect("parts should not be empty") {
|
||||
PathPart::SuperKw | PathPart::SelfKw => todo!("Path navigation"),
|
||||
PathPart::Ident(Identifier(s)) => env.get(s),
|
||||
PathPart::Ident(Identifier(name)) => env.get(*name),
|
||||
}
|
||||
} else {
|
||||
todo!("Path navigation!")
|
||||
@@ -469,7 +469,7 @@ impl Interpret for For {
|
||||
loop {
|
||||
let mut env = env.frame("loop variable");
|
||||
if let Some(loop_var) = bounds.next() {
|
||||
env.insert(name, Some(loop_var.into()));
|
||||
env.insert(*name, Some(loop_var.into()));
|
||||
match pass.interpret(&mut env) {
|
||||
Err(Error::Break(value)) => return Ok(value),
|
||||
Err(Error::Continue) => continue,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#![warn(clippy::all)]
|
||||
#![feature(decl_macro)]
|
||||
|
||||
use cl_ast::Sym;
|
||||
use env::Environment;
|
||||
use error::{Error, IResult};
|
||||
use interpret::Interpret;
|
||||
@@ -13,7 +14,7 @@ pub trait Callable: std::fmt::Debug {
|
||||
/// The Callable is responsible for checking the argument count and validating types
|
||||
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue>;
|
||||
/// Returns the common name of this identifier.
|
||||
fn name(&self) -> &str;
|
||||
fn name(&self) -> Sym;
|
||||
}
|
||||
|
||||
/// [BuiltIn]s are [Callable]s with bespoke definitions
|
||||
@@ -25,6 +26,8 @@ pub mod temp_type_impl {
|
||||
//! Temporary implementations of Conlang values
|
||||
//!
|
||||
//! The most permanent fix is a temporary one.
|
||||
use cl_ast::Sym;
|
||||
|
||||
use super::{
|
||||
error::{Error, IResult},
|
||||
function::Function,
|
||||
@@ -50,7 +53,7 @@ pub mod temp_type_impl {
|
||||
/// A unicode character
|
||||
Char(char),
|
||||
/// A string
|
||||
String(Rc<str>),
|
||||
String(Sym),
|
||||
/// A reference
|
||||
Ref(Rc<ConValue>),
|
||||
/// An Array
|
||||
@@ -120,11 +123,11 @@ pub mod temp_type_impl {
|
||||
}
|
||||
|
||||
impl Callable for ConValue {
|
||||
fn name(&self) -> &str {
|
||||
fn name(&self) -> Sym {
|
||||
match self {
|
||||
ConValue::Function(func) => func.name(),
|
||||
ConValue::BuiltIn(func) => func.name(),
|
||||
_ => "",
|
||||
_ => "".into(),
|
||||
}
|
||||
}
|
||||
fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||
@@ -145,7 +148,7 @@ pub mod temp_type_impl {
|
||||
(Self::Int(a), Self::Int(b)) => Ok(Self::Bool(a $op b)),
|
||||
(Self::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)),
|
||||
(Self::Char(a), Self::Char(b)) => Ok(Self::Bool(a $op b)),
|
||||
(Self::String(a), Self::String(b)) => Ok(Self::Bool(a $op b)),
|
||||
(Self::String(a), Self::String(b)) => Ok(Self::Bool(a.get() $op b.get())),
|
||||
_ => Err(Error::TypeError)
|
||||
}
|
||||
}
|
||||
@@ -162,10 +165,16 @@ pub mod temp_type_impl {
|
||||
fn from(value: $T) -> Self { $v(value.into()) }
|
||||
})*
|
||||
}
|
||||
impl From<&Sym> for ConValue {
|
||||
fn from(value: &Sym) -> Self {
|
||||
ConValue::String(*value)
|
||||
}
|
||||
}
|
||||
from! {
|
||||
Integer => ConValue::Int,
|
||||
bool => ConValue::Bool,
|
||||
char => ConValue::Char,
|
||||
Sym => ConValue::String,
|
||||
&str => ConValue::String,
|
||||
String => ConValue::String,
|
||||
Rc<str> => ConValue::String,
|
||||
@@ -202,7 +211,7 @@ pub mod temp_type_impl {
|
||||
Add: add = [
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
|
||||
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b).into(),
|
||||
(ConValue::String(a), ConValue::String(b)) => (a.to_string() + &b.to_string()).into(),
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
BitAnd: bitand = [
|
||||
@@ -302,7 +311,7 @@ pub mod function {
|
||||
//! Represents a block of code which lives inside the Interpreter
|
||||
|
||||
use super::{Callable, ConValue, Environment, Error, IResult, Interpret};
|
||||
use cl_ast::{Function as FnDecl, Identifier, Param};
|
||||
use cl_ast::{Function as FnDecl, Identifier, Param, Sym};
|
||||
use std::rc::Rc;
|
||||
/// Represents a block of code which persists inside the Interpreter
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -323,8 +332,8 @@ pub mod function {
|
||||
}
|
||||
|
||||
impl Callable for Function {
|
||||
fn name(&self) -> &str {
|
||||
let FnDecl { name: Identifier(ref name), .. } = *self.decl;
|
||||
fn name(&self) -> Sym {
|
||||
let FnDecl { name: Identifier(name), .. } = *self.decl;
|
||||
name
|
||||
}
|
||||
fn call(&self, env: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||
@@ -334,12 +343,12 @@ pub mod function {
|
||||
return Err(Error::ArgNumber { want: bind.len(), got: args.len() });
|
||||
}
|
||||
let Some(body) = body else {
|
||||
return Err(Error::NotDefined(name.into()));
|
||||
return Err(Error::NotDefined(*name));
|
||||
};
|
||||
// TODO: completely refactor data storage
|
||||
let mut frame = env.frame("fn args");
|
||||
for (Param { mutability: _, name: Identifier(name) }, value) in bind.iter().zip(args) {
|
||||
frame.insert(name, Some(value.clone()));
|
||||
frame.insert(*name, Some(value.clone()));
|
||||
}
|
||||
match body.interpret(&mut frame) {
|
||||
Err(Error::Return(value)) => Ok(value),
|
||||
@@ -361,14 +370,14 @@ pub mod env {
|
||||
temp_type_impl::ConValue,
|
||||
BuiltIn, Callable, Interpret,
|
||||
};
|
||||
use cl_ast::{Function as FnDecl, Identifier};
|
||||
use cl_ast::{Function as FnDecl, Identifier, Sym};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
type StackFrame = HashMap<String, Option<ConValue>>;
|
||||
type StackFrame = HashMap<Sym, Option<ConValue>>;
|
||||
|
||||
/// Implements a nested lexical scope
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -404,10 +413,8 @@ pub mod env {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<String, Option<ConValue>> {
|
||||
from.iter()
|
||||
.map(|&v| (v.name().into(), Some(v.into())))
|
||||
.collect()
|
||||
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<Sym, Option<ConValue>> {
|
||||
from.iter().map(|&v| (v.name(), Some(v.into()))).collect()
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
@@ -425,7 +432,7 @@ pub mod env {
|
||||
|
||||
/// Calls a function inside the interpreter's scope,
|
||||
/// and returns the result
|
||||
pub fn call(&mut self, name: &str, args: &[ConValue]) -> IResult<ConValue> {
|
||||
pub fn call(&mut self, name: Sym, args: &[ConValue]) -> IResult<ConValue> {
|
||||
// FIXME: Clone to satisfy the borrow checker
|
||||
let function = self.get(name)?.clone();
|
||||
function.call(self, args)
|
||||
@@ -439,39 +446,39 @@ pub mod env {
|
||||
/// Resolves a variable mutably.
|
||||
///
|
||||
/// Returns a mutable reference to the variable's record, if it exists.
|
||||
pub fn get_mut(&mut self, id: &str) -> IResult<&mut Option<ConValue>> {
|
||||
pub fn get_mut(&mut self, id: Sym) -> IResult<&mut Option<ConValue>> {
|
||||
for (frame, _) in self.frames.iter_mut().rev() {
|
||||
if let Some(var) = frame.get_mut(id) {
|
||||
if let Some(var) = frame.get_mut(&id) {
|
||||
return Ok(var);
|
||||
}
|
||||
}
|
||||
Err(Error::NotDefined(id.into()))
|
||||
Err(Error::NotDefined(id))
|
||||
}
|
||||
/// Resolves a variable immutably.
|
||||
///
|
||||
/// Returns a reference to the variable's contents, if it is defined and initialized.
|
||||
pub fn get(&self, id: &str) -> IResult<ConValue> {
|
||||
pub fn get(&self, id: Sym) -> IResult<ConValue> {
|
||||
for (frame, _) in self.frames.iter().rev() {
|
||||
match frame.get(id) {
|
||||
match frame.get(&id) {
|
||||
Some(Some(var)) => return Ok(var.clone()),
|
||||
Some(None) => return Err(Error::NotInitialized(id.into())),
|
||||
Some(None) => return Err(Error::NotInitialized(id)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Err(Error::NotDefined(id.into()))
|
||||
Err(Error::NotDefined(id))
|
||||
}
|
||||
/// Inserts a new [ConValue] into this [Environment]
|
||||
pub fn insert(&mut self, id: &str, value: Option<ConValue>) {
|
||||
pub fn insert(&mut self, id: Sym, value: Option<ConValue>) {
|
||||
if let Some((frame, _)) = self.frames.last_mut() {
|
||||
frame.insert(id.into(), value);
|
||||
frame.insert(id, value);
|
||||
}
|
||||
}
|
||||
/// A convenience function for registering a [FnDecl] as a [Function]
|
||||
pub fn insert_fn(&mut self, decl: &FnDecl) {
|
||||
let FnDecl { name: Identifier(name), .. } = decl;
|
||||
let (name, function) = (name.clone(), Some(Function::new(decl).into()));
|
||||
let (name, function) = (name, Some(Function::new(decl).into()));
|
||||
if let Some((frame, _)) = self.frames.last_mut() {
|
||||
frame.insert(name, function);
|
||||
frame.insert(*name, function);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,6 +532,8 @@ pub mod env {
|
||||
pub mod error {
|
||||
//! The [Error] type represents any error thrown by the [Environment](super::Environment)
|
||||
|
||||
use cl_ast::Sym;
|
||||
|
||||
use super::temp_type_impl::ConValue;
|
||||
|
||||
pub type IResult<T> = Result<T, Error>;
|
||||
@@ -556,9 +565,9 @@ pub mod error {
|
||||
/// An expression is not assignable
|
||||
NotAssignable,
|
||||
/// A name was not defined in scope before being used
|
||||
NotDefined(String),
|
||||
NotDefined(Sym),
|
||||
/// A name was defined but not initialized
|
||||
NotInitialized(String),
|
||||
NotInitialized(Sym),
|
||||
/// A value was called, but is not callable
|
||||
NotCallable(ConValue),
|
||||
/// A function was called with the wrong number of arguments
|
||||
|
||||
@@ -127,7 +127,7 @@ mod macros {
|
||||
}
|
||||
|
||||
pub macro env_ne($env:ident.$var:ident, $expr:expr) {{
|
||||
let evaluated = $env.get(stringify!($var))
|
||||
let evaluated = $env.get(stringify!($var).into())
|
||||
.expect(stringify!($var should be defined and initialized));
|
||||
if !conv_cmp!(neq, evaluated, $expr) {
|
||||
panic!("assertion {} ({evaluated}) != {} failed.", stringify!($var), stringify!($expr))
|
||||
@@ -135,7 +135,7 @@ mod macros {
|
||||
}}
|
||||
|
||||
pub macro env_eq($env:ident.$var:ident, $expr:expr) {{
|
||||
let evaluated = $env.get(stringify!($var))
|
||||
let evaluated = $env.get(stringify!($var).into())
|
||||
.expect(stringify!($var should be defined and initialized));
|
||||
if !conv_cmp!(eq, evaluated, $expr) {
|
||||
panic!("assertion {} ({evaluated}) == {} failed.", stringify!($var), stringify!($expr))
|
||||
@@ -190,7 +190,7 @@ mod fn_declarations {
|
||||
"fn empty_fn () {\n \n}",
|
||||
format!(
|
||||
"{}",
|
||||
env.get("empty_fn")
|
||||
env.get("empty_fn".into())
|
||||
.expect(stringify!(empty_fn should be defined and initialized))
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user