1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
// #todo find a better name, e.g. `lang`, `sys`, `runtime`, tbh api is a good name though.
use std::path::Path;
use crate::{
context::Context,
error::Error,
eval::eval,
expr::Expr,
lexer::{token::Token, Lexer},
macro_expand::macro_expand,
optimize::optimize,
parser::Parser,
prune::prune,
resolver::Resolver,
};
pub const TAN_FILE_EXTENSION: &str = "tan";
pub const TAN_FILE_EMOJI_EXTENSION: &str = "👅";
pub fn has_tan_extension(path: impl AsRef<Path>) -> bool {
let path = path.as_ref();
if let Some(extension) = path.extension() {
extension == TAN_FILE_EXTENSION || extension == TAN_FILE_EMOJI_EXTENSION
} else {
false
}
}
// #todo argh! optimize this!!
pub fn strip_tan_extension(path: impl Into<String>) -> String {
let path = path.into();
if let Some(path) = path.strip_suffix(&format!(".{TAN_FILE_EXTENSION}")) {
return path.to_owned();
} else if let Some(path) = path.strip_suffix(&format!(".{TAN_FILE_EMOJI_EXTENSION}")) {
return path.to_owned();
} else {
return path;
}
}
/// Lexes a Tan expression encoded as a text string.
pub fn lex_string(input: impl AsRef<str>) -> Result<Vec<Token>, Vec<Error>> {
let input = input.as_ref();
let mut lexer = Lexer::new(input);
lexer.lex()
}
// #todo temp solution for compatibility.
// #todo remove this!
/// Parses a Tan expression encoded as a text string, returns first expression.
pub fn parse_string(input: impl AsRef<str>) -> Result<Expr, Vec<Error>> {
let input = input.as_ref();
let mut lexer = Lexer::new(input);
let tokens = lexer.lex()?;
let mut parser = Parser::new(&tokens);
let mut expr = parser.parse()?;
// #todo temp solution
let expr = expr.swap_remove(0);
Ok(expr)
}
/// Parses a Tan expression encoded as a text string, returns all expressions parsed.
pub fn parse_string_all(input: impl AsRef<str>) -> Result<Vec<Expr>, Vec<Error>> {
let input = input.as_ref();
let mut lexer = Lexer::new(input);
let tokens = lexer.lex()?;
let mut parser = Parser::new(&tokens);
let exprs = parser.parse()?;
Ok(exprs)
}
// #todo what is a good name?
/// Reads and resolves a Tan expression encoded as a text string.
/// Updates the environment with definitions.
pub fn resolve_string(
input: impl AsRef<str>,
context: &mut Context,
) -> Result<Vec<Expr>, Vec<Error>> {
let exprs = parse_string_all(input)?;
// #todo also resolve static-use (normal use) here!
// #insight
//
// AST -> Executable pipeline:
//
// - macro-expand
// - resolve
// - optimize
// // Nice debugging tool!
// for ex in &exprs {
// for e in ex.iter() {
// println!("-- {e:?}");
// }
// }
let mut resolved_exprs = Vec::new();
for expr in exprs {
// #Insight
// Macro expansion should be performed before resolving.
// Prune pass
// #insight first prune pass needed before macro_expand.
let Some(expr) = prune(expr) else {
// The expression is pruned (elided)
continue;
};
// Expand macros.
// #todo pass a dummy scope here? no need to polute the dyn-time environment with macro stuff.
let expr = macro_expand(expr, context);
// #todo temp hack until macro_expand returns multiple errors.
let Ok(expr) = expr else {
return Err(vec![expr.unwrap_err()]);
};
// #todo maybe a second `prune` pass is needed?
let Some(expr) = expr else {
// The expression is pruned (elided)
// #insight elision can happen also in macro_expand!
continue;
};
// Optimization pass
// #todo should run after resolve?
let expr = optimize(expr);
// Resolve pass (typechecking, definitions, etc)
// #todo should we push a new env?
let mut resolver = Resolver::new();
let expr = resolver.resolve(expr, context)?;
resolved_exprs.push(expr);
}
Ok(resolved_exprs)
}
// #todo this implements in essence a do block. Maybe no value should be returned?
/// Evaluates a Tan expression encoded as a text string.
pub fn eval_string(input: impl AsRef<str>, context: &mut Context) -> Result<Expr, Vec<Error>> {
let exprs = resolve_string(input, context)?;
let mut last_value = Expr::One.into();
for expr in exprs {
let value = eval(&expr, context);
let Ok(value) = value else {
return Err(vec![value.unwrap_err()]);
};
last_value = value;
}
Ok(last_value)
}