mod binding;
mod block;
mod comprehension;
mod error;
mod expression;
mod import;
mod literal;
mod operation;
mod pattern;
mod template_string;
mod types;
mod value;
use indexmap::IndexMap;
use pest::Parser as _;
use pest_derive::Parser;
use std::fmt::Display;
use std::rc::Rc;
use std::str;
use thiserror::Error;
use crate::environment::Environment;
use crate::rc_world;
pub use self::binding::Binding;
pub use self::block::Block;
pub use self::comprehension::ListComprehension;
pub use self::error::{ErrorEntry, ErrorLogger, ParseError};
pub use self::expression::{Dict, Expression, KeyValue};
pub use self::import::{Format, Import};
pub use self::literal::Literal;
pub use self::operation::{
BinaryOperation, BinaryOperator, PostfixOperation, PostfixOperator, PrefixOperation,
PrefixOperator,
};
pub use self::pattern::{MatchDictItem, Pattern};
pub use self::types::{Type, TypeExpression};
pub use self::value::{NotIterable, NotRepresentable, PatternMatch, Value};
#[allow(missing_docs)]
#[derive(Parser)]
#[grammar = "ryan.pest"] struct Parser;
impl Rule {
fn name(&self) -> &'static str {
match self {
Rule::EOI => "end of input",
Rule::WHITESPACE => "whitespace",
Rule::COMMENT => "a comment",
Rule::root => "a Ryan program",
Rule::main => "a Ryan program",
Rule::literal => "a literal value",
Rule::unsigned => "an unsigned number",
Rule::null => "null",
Rule::sign => "`+` or `-`",
Rule::number => "a number",
Rule::bool => "a boolean",
Rule::escaped => "the interior of escaped text",
Rule::controlCode => "a control code in escaped text",
Rule::text => "text",
Rule::identifier => "a variable name",
Rule::identifierStr => "a variable name",
Rule::reserved => "a reserved keyword",
Rule::templateString => "a template string",
Rule::templateEscaped => "the interior of a template string",
Rule::interpolation => "a string interpolation in a template",
Rule::templateControlCode => "a control code in a template string",
Rule::expression => "an expression",
Rule::binaryOp => "a binary operation",
Rule::orOp => "`or`",
Rule::andOp => "`and`",
Rule::equalsOp => "`==`",
Rule::notEqualsOp => "`!=`",
Rule::typeMatchesOp => "`:`",
Rule::greaterOp => "`>`",
Rule::greaterEqualOp => "`>=`",
Rule::lesserOp => "`<`",
Rule::lesserEqualOp => "`<=`",
Rule::isContainedOp => "`in`",
Rule::plusOp => "`+`",
Rule::minusOp => "`-`",
Rule::timesOp => "`*`",
Rule::dividedOp => "`/`",
Rule::remainderOp => "`%`",
Rule::defaultOp => "`?`",
Rule::juxtapositionOp => "a juxtaposition",
Rule::prefixOp => "a prefix operator",
Rule::notOp => "`not`",
Rule::postfixOp => "a postfix operator",
Rule::castInt => "a type cast to integer",
Rule::castFloat => "a type cast to float",
Rule::castText => "a type cast to text",
Rule::accessOp => "list or map access",
Rule::pathOp => "list or map access",
Rule::term => "an expression term",
Rule::list => "a list",
Rule::listItem => "an item of a list",
Rule::flatExpression => "a flatten expression",
Rule::dictItem => "a dictionary item",
Rule::keyValue => "a key-value dictionary entry",
Rule::dict => "a dictionary",
Rule::conditional => "`if ... then ... else ...`",
Rule::listComprehension => "a list comprehension",
Rule::dictComprehension => "a dictionary comprehension",
Rule::forClause => "a `for` clause",
Rule::ifGuard => "an `if` guard",
Rule::keyValueClause => "a key-value clause",
Rule::pattern => "a pattern match",
Rule::wildcard => "a wildcard pattern patch",
Rule::matchIdentifier => "an identifier pattern match",
Rule::matchList => "a full list pattern match",
Rule::matchHead => "a list head pattern match",
Rule::matchTail => "a list tail pattern match",
Rule::matchDict => "a non-strict dictionary pattern match",
Rule::matchDictStrict => "a strict dictionary pattern match",
Rule::matchDictItem => "a dictionary item pattern match",
Rule::binding => "a variable binding",
Rule::patternMatchBinding => "a pattern match binding",
Rule::destructuringBiding => "a destructuring binding",
Rule::typeDefinition => "a type definition",
Rule::block => "a code block",
Rule::import => "an import statement",
Rule::importFormat => "an import format",
Rule::importFormatText => "import as text",
Rule::primitive => "a primitive type value",
Rule::typeExpression => "a type expression",
Rule::typeTerm => "a term in a type expression",
Rule::optionalType => "an optional type",
Rule::listType => "a list type",
Rule::dictionaryType => "a dictionary type",
Rule::tupleType => "a tuple type",
Rule::recordType => "a non-strict record type",
Rule::strictRecordType => "a strict record type",
Rule::typeItem => "a dictionary type key-value item",
}
}
}
pub fn parse(s: &str) -> Result<Block, ParseError> {
let mut parsed = Parser::parse(Rule::root, s).map_err(|e| ParseError {
errors: vec![ErrorEntry::from(e).to_string_with(s)],
})?;
let mut error_logger = ErrorLogger::new(s);
let main = parsed.next().expect("there is always a matching token");
let block = if !main.as_str().is_empty() {
Block::parse(&mut error_logger, main.into_inner())
} else {
Block::null()
};
if error_logger.errors.is_empty() {
Ok(block)
} else {
Err(error_logger.into())
}
}
#[derive(Debug)]
enum Context {
RunningFile(Rc<str>),
EvaluatingBinding(Rc<str>),
DefiningType(Rc<str>),
SubstitutingPattern(Option<Rc<str>>),
LoadingImport(Rc<str>),
}
impl Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RunningFile(filename) => write!(f, "Running {filename}"),
Self::EvaluatingBinding(name) => write!(f, "Evaluating binding {name}"),
Self::DefiningType(typ) => write!(f, "Defining type {typ}"),
Self::SubstitutingPattern(Some(name)) => write!(f, "Substituting pattern {name}"),
Self::SubstitutingPattern(None) => write!(f, "Substituting anonymous pattern"),
Self::LoadingImport(import) => write!(f, "Loading import {import:?}"),
}
}
}
#[derive(Debug)]
struct State<'a> {
inherited: Option<&'a State<'a>>,
bindings: IndexMap<Rc<str>, Value>,
error: Option<String>,
contexts: Vec<Context>,
environment: Environment,
}
impl<'a> State<'a> {
fn new(environment: Environment) -> State<'a> {
State {
inherited: None,
bindings: IndexMap::new(),
error: None,
contexts: vec![Context::RunningFile(rc_world::str_to_rc(
environment.current_module.as_deref().unwrap_or("<main>"),
))],
environment,
}
}
fn absorb<T, E>(&mut self, r: Result<T, E>) -> Option<T>
where
E: ToString,
{
match r {
Ok(t) => Some(t),
Err(e) => {
self.error = Some(e.to_string());
None
}
}
}
fn raise<E>(&mut self, msg: E) -> Option<()>
where
E: ToString,
{
self.error = Some(msg.to_string());
None
}
fn push_ctx(&mut self, ctx: Context) {
self.contexts.push(ctx);
}
fn pop_ctx(&mut self) {
self.contexts.pop();
}
fn try_get(&self, id: &str) -> Result<Value, String> {
match self.bindings.get(id) {
Some(bound) => Ok(bound.clone()),
_ => {
if let Some(inherited) = self.inherited.as_ref() {
inherited.try_get(id)
} else if let Some(builtin) = self.environment.builtin(id) {
Ok(builtin)
} else {
Err(format!("Variable `{id}` is undefined"))
}
}
}
}
fn get(&mut self, id: &str) -> Option<Value> {
self.absorb(self.try_get(id))
}
}
#[derive(Debug, Error)]
pub struct EvalError {
error: String,
context: Vec<String>,
}
impl Display for EvalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.error)?;
if !self.context.is_empty() {
writeln!(f)?;
writeln!(f, "Context:")?;
for line in &self.context {
writeln!(f, " - {line}")?;
}
}
Ok(())
}
}
pub fn eval(environment: Environment, block: &Block) -> Result<Value, EvalError> {
let mut state = State::new(environment);
if let Some(value) = block.eval(&mut state) {
Ok(value)
} else {
Err(EvalError {
error: state.error.expect("on backtracking, an error must be set"),
context: state.contexts.iter().map(ToString::to_string).collect(),
})
}
}