mirror of
https://github.com/danog/parser.git
synced 2024-12-02 09:27:50 +01:00
wip
This commit is contained in:
parent
9a0bf0db19
commit
f02a8d85e2
@ -1,9 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"trunk_lexer",
|
"trunk_lexer",
|
||||||
"trunk_parser",
|
"trunk_parser"
|
||||||
"trunk_interpreter"
|
]
|
||||||
]
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
debug = true
|
|
414
flamegraph.svg
414
flamegraph.svg
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 48 KiB |
@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "trunk_interpreter"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cmder = "0.6.1"
|
|
||||||
hashbrown = "0.13.1"
|
|
||||||
trunk_lexer = { path = "../trunk_lexer" }
|
|
||||||
trunk_parser = { path = "../trunk_parser" }
|
|
@ -1,3 +0,0 @@
|
|||||||
mod run;
|
|
||||||
|
|
||||||
pub use run::run;
|
|
@ -1,22 +0,0 @@
|
|||||||
use std::{fs::canonicalize, path::PathBuf};
|
|
||||||
|
|
||||||
use cmder::ParserMatches;
|
|
||||||
use trunk_lexer::Lexer;
|
|
||||||
use trunk_parser::Parser;
|
|
||||||
|
|
||||||
use crate::engine::eval;
|
|
||||||
|
|
||||||
pub fn run(matches: ParserMatches) {
|
|
||||||
// FIXME: Better error handling needed.
|
|
||||||
let file = PathBuf::from(matches.get_arg("file").unwrap());
|
|
||||||
let contents = std::fs::read_to_string(&file).unwrap();
|
|
||||||
let abs_filename = canonicalize(&file).unwrap();
|
|
||||||
|
|
||||||
let mut lexer = Lexer::new(None);
|
|
||||||
let tokens = lexer.tokenize(&contents).unwrap();
|
|
||||||
|
|
||||||
let mut parser = Parser::new(None);
|
|
||||||
let program = parser.parse(tokens).unwrap();
|
|
||||||
|
|
||||||
eval(abs_filename, program).ok();
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
use hashbrown::HashMap;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use super::value::Value;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Environment {
|
|
||||||
entries: HashMap<String, Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Environment {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
entries: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, name: &str) -> Option<Value> {
|
|
||||||
if let Some(value) = self.entries.get(name) {
|
|
||||||
return Some(value.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&mut self, name: &str, value: Value) {
|
|
||||||
if let Some(current) = self.entries.get_mut(name) {
|
|
||||||
*current = value;
|
|
||||||
} else {
|
|
||||||
self.entries.insert(name.to_owned(), value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,302 +0,0 @@
|
|||||||
use hashbrown::HashMap;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use trunk_lexer::Lexer;
|
|
||||||
use trunk_parser::{CastKind, Expression, InfixOp, MagicConst, Param, Parser, Statement};
|
|
||||||
|
|
||||||
use self::environment::Environment;
|
|
||||||
use self::value::Value;
|
|
||||||
|
|
||||||
mod environment;
|
|
||||||
mod value;
|
|
||||||
|
|
||||||
pub struct Function {
|
|
||||||
pub(crate) params: Vec<Param>,
|
|
||||||
pub(crate) body: Vec<Statement>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Engine {
|
|
||||||
pub(crate) filename: PathBuf,
|
|
||||||
pub(crate) global_environment: Environment,
|
|
||||||
pub(crate) function_table: HashMap<String, Function>,
|
|
||||||
pub(crate) scopes: Vec<Environment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Engine {
|
|
||||||
pub fn new(filename: PathBuf) -> Self {
|
|
||||||
Self {
|
|
||||||
filename,
|
|
||||||
global_environment: Environment::new(),
|
|
||||||
function_table: HashMap::default(),
|
|
||||||
scopes: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval(filename: PathBuf, program: Vec<Statement>) -> Result<(), Escape> {
|
|
||||||
let mut engine = Engine::new(filename);
|
|
||||||
for statement in program {
|
|
||||||
eval_statement(&mut engine, statement)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Escape {
|
|
||||||
Return(Value),
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! extract {
|
|
||||||
($target:expr, $pattern:pat, $return:expr) => {
|
|
||||||
match $target {
|
|
||||||
$pattern => $return,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval_statement(engine: &mut Engine, statement: Statement) -> Result<(), Escape> {
|
|
||||||
match statement {
|
|
||||||
Statement::Function { .. } => eval_function(engine, statement)?,
|
|
||||||
Statement::Echo { .. } => eval_echo(engine, statement)?,
|
|
||||||
Statement::If { .. } => eval_if(engine, statement)?,
|
|
||||||
Statement::Return { .. } => return Err(Escape::Return(eval_return(engine, statement))),
|
|
||||||
Statement::Include { .. } => eval_include(engine, statement)?,
|
|
||||||
_ => unimplemented!("{:?}", statement),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_function(engine: &mut Engine, statement: Statement) -> Result<(), Escape> {
|
|
||||||
let (name, params, body, return_type, by_ref) = match statement {
|
|
||||||
Statement::Function {
|
|
||||||
name,
|
|
||||||
params,
|
|
||||||
body,
|
|
||||||
return_type,
|
|
||||||
by_ref,
|
|
||||||
} => (name, params, body, return_type, by_ref),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let func = Function { params, body };
|
|
||||||
|
|
||||||
engine.function_table.insert(name.name.into(), func);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_echo(engine: &mut Engine, statement: Statement) -> Result<(), Escape> {
|
|
||||||
let values = match statement {
|
|
||||||
Statement::Echo { values } => values,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for value in values {
|
|
||||||
let value = eval_expression(engine, value);
|
|
||||||
|
|
||||||
print!("{}", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_if(engine: &mut Engine, statement: Statement) -> Result<(), Escape> {
|
|
||||||
let (condition, then, ..) = match statement {
|
|
||||||
Statement::If {
|
|
||||||
condition,
|
|
||||||
then,
|
|
||||||
else_ifs,
|
|
||||||
r#else,
|
|
||||||
} => (condition, then, else_ifs, r#else),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let condition = eval_expression(engine, condition);
|
|
||||||
|
|
||||||
if condition.is_truthy() {
|
|
||||||
for statement in then {
|
|
||||||
eval_statement(engine, statement)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_return(engine: &mut Engine, statement: Statement) -> Value {
|
|
||||||
let value = extract!(statement, Statement::Return { value }, value);
|
|
||||||
|
|
||||||
if let Some(value) = value {
|
|
||||||
return eval_expression(engine, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Value::Null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_include(engine: &mut Engine, statement: Statement) -> Result<(), Escape> {
|
|
||||||
let (kind, path) = extract!(statement, Statement::Include { kind, path }, (kind, path));
|
|
||||||
|
|
||||||
let path = extract!(eval_expression(engine, path), Value::String(value), value);
|
|
||||||
let original_filename = engine.filename.clone();
|
|
||||||
let contents = std::fs::read_to_string(&path).unwrap();
|
|
||||||
|
|
||||||
engine.filename = PathBuf::from(&path);
|
|
||||||
|
|
||||||
let mut lexer = Lexer::new(None);
|
|
||||||
let tokens = lexer.tokenize(&contents).unwrap();
|
|
||||||
|
|
||||||
let mut parser = Parser::new(None);
|
|
||||||
let ast = parser.parse(tokens).unwrap();
|
|
||||||
|
|
||||||
for statement in ast {
|
|
||||||
eval_statement(engine, statement)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.filename = original_filename;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_expression(engine: &mut Engine, expression: Expression) -> Value {
|
|
||||||
match expression {
|
|
||||||
Expression::Infix { .. } => eval_infix_expression(engine, expression),
|
|
||||||
Expression::Call { .. } => eval_call_expression(engine, expression),
|
|
||||||
Expression::Variable { .. } => eval_variable_expression(engine, expression),
|
|
||||||
Expression::Cast { .. } => eval_cast_expression(engine, expression),
|
|
||||||
Expression::MagicConst { constant } => eval_magic_const(engine, constant),
|
|
||||||
Expression::Int { i } => Value::Int(i),
|
|
||||||
Expression::ConstantString { value } => Value::String(value.into()),
|
|
||||||
_ => panic!("unhandled expression: {:?}", expression),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_infix_expression(engine: &mut Engine, expression: Expression) -> Value {
|
|
||||||
let (lhs, op, rhs) = match expression {
|
|
||||||
Expression::Infix { lhs, op, rhs } => (lhs, op, rhs),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let lhs = eval_expression(engine, *lhs);
|
|
||||||
let rhs = eval_expression(engine, *rhs);
|
|
||||||
|
|
||||||
match op {
|
|
||||||
InfixOp::Add => lhs + rhs,
|
|
||||||
InfixOp::Sub => lhs - rhs,
|
|
||||||
InfixOp::LessThan => Value::Bool(lhs < rhs),
|
|
||||||
InfixOp::Concat => match (lhs, rhs) {
|
|
||||||
(Value::String(a), Value::String(b)) => {
|
|
||||||
let mut s = String::with_capacity(a.len() + b.len());
|
|
||||||
s.push_str(&a);
|
|
||||||
s.push_str(&b);
|
|
||||||
Value::String(s)
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
},
|
|
||||||
_ => todo!("infix: {:?}", op),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_call_expression(engine: &mut Engine, expression: Expression) -> Value {
|
|
||||||
let (target, args) = match expression {
|
|
||||||
Expression::Call { target, args } => (target, args),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let target: String = match *target {
|
|
||||||
Expression::Identifier { name } => name.into(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !engine.function_table.contains_key(&target) {
|
|
||||||
panic!("undefined function: {}", target);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut arg_values = Vec::new();
|
|
||||||
for arg in args {
|
|
||||||
let value = eval_expression(engine, arg.value);
|
|
||||||
arg_values.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
let func = engine.function_table.get(&target).unwrap();
|
|
||||||
let mut environment = Environment::new();
|
|
||||||
|
|
||||||
for (i, param) in func.params.clone().into_iter().enumerate() {
|
|
||||||
let name: String = match param.name {
|
|
||||||
Expression::Variable { name } => name.into(),
|
|
||||||
_ => todo!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.set(&name, arg_values.get(i).unwrap().clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.scopes.push(environment);
|
|
||||||
|
|
||||||
let mut return_value = Value::Null;
|
|
||||||
|
|
||||||
for statement in func.body.clone() {
|
|
||||||
match eval_statement(engine, statement) {
|
|
||||||
Err(Escape::Return(value)) => {
|
|
||||||
return_value = value.clone();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.scopes.pop();
|
|
||||||
|
|
||||||
return_value
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_variable_expression(engine: &mut Engine, expression: Expression) -> Value {
|
|
||||||
let name: String = match expression {
|
|
||||||
Expression::Variable { name } => name.into(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(scope) = engine.scopes.last() {
|
|
||||||
if let Some(value) = scope.get(&name) {
|
|
||||||
return value.clone();
|
|
||||||
} else {
|
|
||||||
panic!("undefined variable: {}", name);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let Some(value) = engine.global_environment.get(&name) {
|
|
||||||
return value.clone();
|
|
||||||
} else {
|
|
||||||
panic!("undefined variable: {}", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_cast_expression(engine: &mut Engine, expression: Expression) -> Value {
|
|
||||||
let (kind, value) = match expression {
|
|
||||||
Expression::Cast { kind, value } => (kind, value),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = eval_expression(engine, *value);
|
|
||||||
|
|
||||||
match (kind, &value) {
|
|
||||||
(CastKind::String, Value::Int(i)) => Value::String(i.to_string()),
|
|
||||||
_ => value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_magic_const(engine: &mut Engine, constant: MagicConst) -> Value {
|
|
||||||
match constant {
|
|
||||||
MagicConst::Dir => {
|
|
||||||
// FIXME: Sort this nasty code out.
|
|
||||||
Value::String(
|
|
||||||
engine
|
|
||||||
.filename
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
use std::{
|
|
||||||
fmt::Display,
|
|
||||||
ops::{Add, Sub},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialOrd, PartialEq)]
|
|
||||||
pub enum Value {
|
|
||||||
Null,
|
|
||||||
Int(i64),
|
|
||||||
Float(f64),
|
|
||||||
Bool(bool),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Value {
|
|
||||||
pub fn is_truthy(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Value::Null => false,
|
|
||||||
Value::Bool(false) => false,
|
|
||||||
Value::Bool(true) => true,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add<Value> for Value {
|
|
||||||
type Output = Value;
|
|
||||||
|
|
||||||
fn add(self, rhs: Value) -> Self::Output {
|
|
||||||
match (self, rhs) {
|
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Int(a + b),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sub<Value> for Value {
|
|
||||||
type Output = Value;
|
|
||||||
|
|
||||||
fn sub(self, rhs: Value) -> Self::Output {
|
|
||||||
match (self, rhs) {
|
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Int(a - b),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Value {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Value::String(s) => write!(f, "{}", s),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
use cmder::Program;
|
|
||||||
|
|
||||||
mod cmd;
|
|
||||||
mod engine;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut program = Program::new();
|
|
||||||
|
|
||||||
program
|
|
||||||
.bin_name("trunk")
|
|
||||||
.author("Ryan Chandler <support@ryangjchandler.co.uk>")
|
|
||||||
.description("An alternative interpreter and runtime for PHP.");
|
|
||||||
|
|
||||||
let run_cmd = program.subcommand("run");
|
|
||||||
|
|
||||||
run_cmd
|
|
||||||
.description("Run a PHP file through Trunk.")
|
|
||||||
.alias("r")
|
|
||||||
.argument("file", "The file you'd like to run.")
|
|
||||||
.action(cmd::run);
|
|
||||||
|
|
||||||
program.parse();
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user