mod ast;
mod check;
mod eval;
mod functions;
mod getters;
mod lexer;
mod ops;
pub(crate) mod overload;
mod parser;
mod strings;
#[cfg(test)]
mod tests;
pub(crate) mod time;
mod types;
mod value;
pub use eval::BUDGET;
pub use types::Type;
pub use value::{Key, Value};
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum Error {
#[error("column {}: {message}", at + 1)]
Syntax {
at: usize,
message: String,
},
#[error("column {}: {message}", at + 1)]
Type {
at: usize,
message: String,
},
#[error("column {}: {message}", at + 1)]
Evaluation {
at: usize,
message: String,
},
#[error("column {}: the expression takes too much work", at + 1)]
Budget {
at: usize,
},
}
impl Error {
pub(crate) fn syntax(at: usize, message: impl Into<String>) -> Self {
Self::Syntax {
at,
message: message.into(),
}
}
pub(crate) fn type_error(at: usize, message: impl Into<String>) -> Self {
Self::Type {
at,
message: message.into(),
}
}
pub(crate) fn evaluation(at: usize, message: impl Into<String>) -> Self {
Self::Evaluation {
at,
message: message.into(),
}
}
#[must_use]
pub fn at(&self) -> usize {
match self {
Self::Syntax { at, .. }
| Self::Type { at, .. }
| Self::Evaluation { at, .. }
| Self::Budget { at } => *at,
}
}
#[must_use]
pub fn message(&self) -> &str {
match self {
Self::Syntax { message, .. }
| Self::Type { message, .. }
| Self::Evaluation { message, .. } => message,
Self::Budget { .. } => "the expression takes too much work",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Described {
pub(crate) signatures: Vec<String>,
pub(crate) doc: &'static str,
}
pub(crate) fn described(name: &str) -> Option<Described> {
if name == "has" {
return Some(Described {
signatures: vec!["has(x.field) โ bool".to_string()],
doc: "true when the map or record has the field",
});
}
if let Some(found) = ast::Macro::from_name(name) {
return Some(Described {
signatures: vec![found.signature().to_string()],
doc: found.doc(),
});
}
let function = functions::Function::from_name(name)?;
Some(Described {
signatures: overload::signatures(function, name),
doc: overload::Library::doc(function),
})
}
pub(crate) fn function_names(method: bool) -> Vec<&'static str> {
let functions = functions::Function::NAMES.iter().copied().filter(|name| {
functions::Function::from_name(name).is_some_and(|function| {
overload::Library::overloads(function)
.iter()
.any(|overload| overload.receiver.is_some() == method)
})
});
let macros = ast::Macro::NAMES.iter().copied().filter(|_| method);
let has = (!method).then_some("has");
functions.chain(macros).chain(has).collect()
}
pub trait FunctionTypes {
fn result_type(&self, name: &str, args: &[Type]) -> Result<Type, String>;
}
pub trait Functions {
fn names(&self) -> &[&'static str];
fn call(&self, name: &str, args: &[Value]) -> Result<Value, String>;
fn offset(&self, zone: Option<&str>, at: i64) -> Result<i32, String>;
}
#[derive(Clone, Debug, Default)]
pub struct Scope(BTreeMap<String, Type>);
impl Scope {
#[must_use]
pub fn with(mut self, name: impl Into<String>, ty: Type) -> Self {
self.0.insert(name.into(), ty);
self
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&Type> {
self.0.get(name)
}
pub(crate) fn names(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(String::as_str)
}
}
#[derive(Clone, Debug, Default)]
pub struct Values(BTreeMap<String, Value>);
impl Values {
#[must_use]
pub fn with(mut self, name: impl Into<String>, value: Value) -> Self {
self.0.insert(name.into(), value);
self
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&Value> {
self.0.get(name)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Program {
expr: ast::Expr,
}
impl Program {
pub fn parse(source: &str) -> Result<Self, Error> {
Ok(Self {
expr: parser::parse(source)?,
})
}
pub fn check(&self, scope: &Scope, functions: &dyn FunctionTypes) -> Result<Type, Error> {
check::check(&self.expr, scope, functions)
}
pub fn evaluate(&self, values: &Values, functions: &dyn Functions) -> Result<Value, Error> {
eval::evaluate(&self.expr, values, functions, BUDGET)
}
}