diff --git a/examples/site_1.0/component/title.ds b/examples/site_1.0/component/title.ds index e27dc2d..a724479 100644 --- a/examples/site_1.0/component/title.ds +++ b/examples/site_1.0/component/title.ds @@ -1,7 +1,7 @@ func holder = use("component/title_holder") -return (str title) element { +return (str title) html { return : h1.imported { @{holder(title)} } diff --git a/examples/site_1.0/component/title_holder.ds b/examples/site_1.0/component/title_holder.ds index a428f18..2f390aa 100644 --- a/examples/site_1.0/component/title_holder.ds +++ b/examples/site_1.0/component/title_holder.ds @@ -2,5 +2,5 @@ str url = meta.url return (str title) str { - return std.format("{} - {}", title, url) + return format("{} - {}", title, url) } diff --git a/examples/site_1.0/src/index.ds b/examples/site_1.0/src/index.ds index d015dfc..9bd3a2d 100644 --- a/examples/site_1.0/src/index.ds +++ b/examples/site_1.0/src/index.ds @@ -8,7 +8,7 @@ meta { func title = () str { - return std.format("{} - {}", "Hello world!", meta.url) + return format("{} - {}", "Hello world!", meta.url) } func h1 = use("component/title") diff --git a/examples/site_1.0/src/test.ds b/examples/site_1.0/src/test.ds index d681534..2bf699c 100644 --- a/examples/site_1.0/src/test.ds +++ b/examples/site_1.0/src/test.ds @@ -8,9 +8,9 @@ use("assets/main.scss") str line_content = "This is a list item" str css = use("assets/main.scss") -element line = : li > @{line_content} +html line = : li > @{line_content} -element lines = : ul { +html lines = : ul { @{line} @{line} @{line} @@ -22,10 +22,17 @@ map titles = { str C = "Title C" } -std.println("Titles map: ") +println("Titles map: ") +list title_keys = keys(titles) +num title_count = len(title_keys) +for num i = 0; i < title_count; i = i + 1 { + println(i) + println(titles[title_keys[i]]) +} + for key in titles { - std.println(key) - std.println(titles[key]) + println(key) + println(titles[key]) } list x = [ @@ -33,7 +40,7 @@ list x = [ "world" ] for _, value in x { - std.println(value) + println(value) } return : html { diff --git a/src/ast/environment/environment.rs b/src/ast/environment/environment.rs new file mode 100644 index 0000000..f32d6b1 --- /dev/null +++ b/src/ast/environment/environment.rs @@ -0,0 +1,160 @@ +use super::{Type, Value}; +use crate::ast::statement::Statement; +use crate::context::Context; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +pub type StackValue = Rc>; +pub type Scope = Rc>>; +pub type SharedEnvironment = Rc>; + +pub trait CallableSharedEnvironment { + fn within_scope(&mut self, lambda: F) -> T + where + F: FnOnce(&mut SharedEnvironment) -> T; +} + +impl CallableSharedEnvironment for SharedEnvironment { + fn within_scope(&mut self, lambda: F) -> T + where + F: FnOnce(&mut SharedEnvironment) -> T, + { + let self_clone = self.clone(); + let mut env = self_clone.borrow_mut(); + env.increase_scope(); + let result: T = lambda(self); + env.decrease_scope(); + return result; + } +} + +#[derive(Clone)] +pub struct Environment { + stack: Vec, + scopes: Vec, + current_scope: usize, +} + +impl Environment { + pub fn new() -> SharedEnvironment { + Rc::new(RefCell::new(Environment { + stack: Vec::new(), + scopes: vec![Rc::new(RefCell::new(HashMap::new()))], + current_scope: 0, + })) + } + + pub fn current_scope(&self) -> Rc>> { + self.scopes[self.current_scope].clone() + } + + pub fn get_stack_value(&self, index: usize) -> Option>> { + self.stack.get(index).map(|s| s.clone()).or_else(|| None) + } + + pub fn as_map(&mut self) -> HashMap { + let mut map = HashMap::new(); + for scope in &self.scopes { + for (name, index) in scope.borrow().iter() { + if let Some(value) = self.get_stack_value(*index) { + map.insert(name.clone(), value); + } + } + } + map + } + + pub fn as_vec(&mut self) -> Vec { + self.as_map().into_iter().map(|(_, value)| value).collect() + } + + pub fn set(&mut self, type_: Type, name: String, value: Value) { + if !Type::matches(&type_, &value) { + panic!("Type mismatch: expected {}, got {}", type_, value); + } + self.current_scope() + .borrow_mut() + .insert(name, self.stack.len()); + } + + pub fn exists(&mut self, name: &str) -> Option { + for (_, scope) in self.scopes.iter().enumerate().rev() { + if let Some(index) = scope.borrow().get(name) { + return Some(*index); + } + } + None + } + + pub fn define(&mut self, type_: Type, name: String, value: Value) { + if self.current_scope().borrow().contains_key(&name) { + panic!("Value {} already defined in this scope", name); + } + self.set(type_, name, value); + } + + pub fn get(&mut self, name: &str) -> Option { + if let Some(index) = self.exists(name) { + return self.get_stack_value(index); + } + None + } + + pub fn assign(&mut self, name: String, value: Value) { + if let Some(stackvalue) = self.get(&name) { + let (type_, mut var) = stackvalue.borrow_mut().clone(); + if !Type::matches(&type_, &value) { + panic!("Type mismatch: expected {}, got {}", type_, value); + } + + var.set_value(value); + } else { + panic!("Value {} not found in any scope", name); + } + } + + pub fn increase_scope(&mut self) { + self.current_scope += 1; + for _ in self.scopes.len()..=self.current_scope { + self.scopes.push(Rc::new(RefCell::new(HashMap::new()))); + } + } + + pub fn decrease_scope(&mut self) { + if self.current_scope > 0 { + self.current_scope -= 1; + self.collect_garbage(); + } else { + panic!("Cannot decrease scope below 0"); + } + } + + pub fn collect_garbage(&mut self) { + let length = self.scopes.len(); + + if self.current_scope < length - 1 { + self.scopes.truncate(self.current_scope + 1); + } + + let in_use: Vec = self.current_scope().borrow().values().cloned().collect(); + for i in (0..self.stack.len()).rev() { + if !in_use.contains(&i) { + self.stack.remove(i); + } + } + } + + pub fn define_builtin_function( + &mut self, + name: String, + func: fn(&mut Context, &Vec, &Vec, &mut Scope) -> Value, + return_type: Type, + ) { + self.define( + Type::Function, + name, + Value::Function(func, vec![].into(), return_type, vec![].into()), + ); + } +} diff --git a/src/ast/environment/value.rs b/src/ast/environment/value.rs index feed5b9..cd4eede 100644 --- a/src/ast/environment/value.rs +++ b/src/ast/environment/value.rs @@ -19,7 +19,6 @@ macro_rules! impl_try_into { } } -#[derive(Clone)] pub enum Value { String(String), Number(i64), diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 02d46f6..961c152 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -5,4 +5,3 @@ pub mod function; pub mod html; pub mod node; pub mod statement; -pub mod strings; diff --git a/src/ast2/builtin/mod.rs b/src/ast2/builtin/mod.rs new file mode 100644 index 0000000..289e529 --- /dev/null +++ b/src/ast2/builtin/mod.rs @@ -0,0 +1,13 @@ +use super::{environment::Value, Environment}; +use crate::prelude::*; +use ::std::sync::Arc; + +mod std; + +pub fn populate(env: &mut Environment) { + define_function!(env, "use", std::Use); + define_function!(env, "format", std::Format); + define_function!(env, "println", std::PrintLn); + define_function!(env, "len", std::Len); + define_function!(env, "keys", std::Keys); +} diff --git a/src/ast2/builtin/std/format.rs b/src/ast2/builtin/std/format.rs new file mode 100644 index 0000000..e983d41 --- /dev/null +++ b/src/ast2/builtin/std/format.rs @@ -0,0 +1,41 @@ +use crate::{ + ast2::{environment::Value, functions::FunctionRunner, Build, Environment, Expression}, + context::Context, +}; + +pub struct Format; + +impl FunctionRunner for Format { + #[allow(unused_variables)] + fn call(&self, ctx: &Context, env: &Environment, inputs: &Vec) -> Value { + if inputs.len() == 0 { + panic!("format() requires at least one argument"); + } + + if let Value::String(src) = &inputs[0](ctx, env) { + let mut result = src.clone(); + + for (i, input) in inputs.iter().enumerate().skip(1) { + let value = input(ctx, env); + match value { + Value::Map(map) => { + let map = map.as_map(); + for (key, val) in map { + result = result.replace( + &format!("{{{}}}", key), + val.borrow().1.build(ctx, env).as_str(), + ); + } + } + _ => { + result = result.replacen("{}", value.build(ctx, env).as_str(), 1); + } + } + } + + return Value::String(result); + } else { + panic!("format() first argument must be a string"); + } + } +} diff --git a/src/ast2/builtin/std/keys.rs b/src/ast2/builtin/std/keys.rs new file mode 100644 index 0000000..7b05d79 --- /dev/null +++ b/src/ast2/builtin/std/keys.rs @@ -0,0 +1,36 @@ +use crate::{ + ast2::{ + environment::{Type, Value}, + functions::FunctionRunner, + statement::Result, + Environment, Expression, + }, + context::{ + resource::{Resource, ResourceListExt}, + Context, + }, +}; + +pub struct Keys; + +impl FunctionRunner for Keys { + #[allow(unused_variables)] + fn call(&self, ctx: &Context, env: &Environment, inputs: &Vec) -> Value { + if inputs.len() != 1 { + panic!("keys() expects exactly one argument"); + } + + if let Value::Map(map) = &inputs[0](ctx, env) { + let mut list = Environment::new(); + for (i, key) in map.as_map().keys().enumerate() { + list.set( + i.to_string().as_str(), + Type::String.with_value(Value::String(key.to_string())), + ); + } + Value::List(list) + } else { + panic!("keys() expects a list as argument"); + } + } +} diff --git a/src/ast2/builtin/std/len.rs b/src/ast2/builtin/std/len.rs new file mode 100644 index 0000000..a6f4b4c --- /dev/null +++ b/src/ast2/builtin/std/len.rs @@ -0,0 +1,26 @@ +use crate::{ + ast2::{ + environment::Value, functions::FunctionRunner, statement::Result, Environment, Expression, + }, + context::{ + resource::{Resource, ResourceListExt}, + Context, + }, +}; + +pub struct Len; + +impl FunctionRunner for Len { + #[allow(unused_variables)] + fn call(&self, ctx: &Context, env: &Environment, inputs: &Vec) -> Value { + if inputs.len() != 1 { + panic!("len() expects exactly one argument"); + } + + if let Value::List(list) = &inputs[0](ctx, env) { + Value::Number(list.as_vec().len() as i64) + } else { + panic!("len() expects a list as argument"); + } + } +} diff --git a/src/ast2/builtin/std/mod.rs b/src/ast2/builtin/std/mod.rs new file mode 100644 index 0000000..3e6f4cd --- /dev/null +++ b/src/ast2/builtin/std/mod.rs @@ -0,0 +1,7 @@ +use crate::prelude::*; + +inherits!(r#use, [Use]); +inherits!(format, [Format]); +inherits!(println, [PrintLn]); +inherits!(len, [Len]); +inherits!(keys, [Keys]); diff --git a/src/ast2/builtin/std/println.rs b/src/ast2/builtin/std/println.rs new file mode 100644 index 0000000..171404f --- /dev/null +++ b/src/ast2/builtin/std/println.rs @@ -0,0 +1,22 @@ +use crate::{ + ast2::{environment::Value, functions::FunctionRunner, Build, Environment, Expression}, + context::Context, +}; + +pub struct PrintLn; + +impl FunctionRunner for PrintLn { + #[allow(unused_variables)] + fn call(&self, ctx: &Context, env: &Environment, inputs: &Vec) -> Value { + if inputs.len() == 0 { + panic!("println() requires at least one argument"); + } + + for input in inputs { + let value = input(ctx, env); + print!("{}", value.build(ctx, env)); + } + println!(); + Value::Nil + } +} diff --git a/src/ast2/builtin/std/use.rs b/src/ast2/builtin/std/use.rs new file mode 100644 index 0000000..5981728 --- /dev/null +++ b/src/ast2/builtin/std/use.rs @@ -0,0 +1,57 @@ +use crate::{ + ast2::{ + environment::Value, functions::FunctionRunner, statement::Result, Environment, Expression, + }, + context::{ + resource::{Resource, ResourceListExt}, + Context, + }, +}; + +pub struct Use; + +impl FunctionRunner for Use { + #[allow(unused_variables)] + fn call(&self, ctx: &Context, env: &Environment, inputs: &Vec) -> Value { + if inputs.len() != 1 { + panic!("use() expects exactly one argument"); + } + + if let Value::String(import) = &inputs[0](ctx, env) { + let resource = ctx + .clone() + .resources + .load(ctx.clone(), ctx.config.paths.get_workdir().join(import)); + + let resource = &*resource.borrow(); + if let Resource::File(file) = resource { + if !file.is_page { + env.clone().subscope(|| { + for ast in &file.ast { + let result = ast(ctx, env); + match result { + Result::Return(result) => { + return result; + } + _ => {} + } + } + return Value::Nil; + }) + } else { + todo!("allow importing pages to import the output url") + } + } else { + let value = match resource { + Resource::Styling(_, output_path, _) => output_path.clone(), + Resource::Other(_, output_path) => output_path.clone(), + _ => panic!("Non implemented type"), + }; + + Value::String(format!("/{}", value)) + } + } else { + panic!("use() expects a string as argument"); + } + } +} diff --git a/src/ast2/environment/mod.rs b/src/ast2/environment/mod.rs new file mode 100644 index 0000000..73fa615 --- /dev/null +++ b/src/ast2/environment/mod.rs @@ -0,0 +1,141 @@ +use super::Build; +use crate::context::Context; +use crate::prelude::*; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +inherits!(r#type, [Type]); +inherits!(value, [Value, TypeValue, CheckTypeValue]); + +pub mod scope; +use scope::QueryScopes; +use scope::Scope; +use scope::ScopeList; +use scope::UseScope; + +mod stack; +use stack::Stack; +use stack::UseStack; + +#[derive(Clone)] +pub struct Environment { + stack: Stack, + scopes: ScopeList, +} + +impl Environment { + pub fn new() -> Self { + Environment { + stack: Rc::new(RefCell::new(Vec::new())), + scopes: Rc::new(RefCell::new(vec![Rc::new(RefCell::new(HashMap::new()))])), + } + } + + pub fn from_scopes(&self, scopes: ScopeList) -> Self { + Environment { + stack: self.stack.clone(), + scopes, + } + } + + pub fn clone_scopes(&self) -> ScopeList { + Rc::new(RefCell::new(self.scopes.borrow().clone())) + } + + pub fn current_scope(&self) -> Scope { + self.scopes.borrow().last().unwrap().clone() + } + + pub fn increase_scope(&mut self) { + self.scopes + .borrow_mut() + .push(Rc::new(RefCell::new(HashMap::new()))); + } + + pub fn decrease_scope(&mut self) { + if self.scopes.borrow().len() > 0 { + let _scope = self.scopes.borrow_mut().pop(); + // scope.unwrap().clean(self.stack.clone()); + } else { + panic!("Cannot decrease scope, already at the global scope."); + } + } + + pub fn subscope(&mut self, lambda: F) -> T + where + F: FnOnce() -> T, + { + self.increase_scope(); + let result: T = lambda(); + self.decrease_scope(); + result + } + + pub fn define(&mut self, name: &str, value: TypeValue) { + let index = self.stack.push(value); + self.current_scope().define(name, index); + } + + pub fn set(&mut self, name: &str, value: TypeValue) { + let index = self.scopes.exists(name); + if let Some(index) = index { + self.stack.set(index, value); + } else { + self.define(name, value); + } + } + + pub fn assign(&mut self, name: &str, value: TypeValue) { + let index = self.scopes.exists(name).expect(&format!( + "Variable '{}' not defined in current scope.", + name + )); + self.stack.set(index, value); + } + + pub fn get(&self, name: &str) -> Option { + if let Some(index) = self.scopes.exists(name) { + return self.stack.get(index); + } + None + } + + pub fn as_map(&self) -> HashMap { + let mut map = HashMap::new(); + for (name, index) in self.current_scope().borrow().iter() { + if let Some(value) = self.stack.get(*index) { + map.insert(name.to_string(), value); + } + } + map + } + + pub fn exists(&self, name: &str) -> bool { + self.scopes.exists(name).is_some() + } + + pub fn as_vec(&self) -> Vec { + self.as_map().into_values().collect() + } + + #[allow(dead_code)] + pub fn debug(&self) { + println!("Environment:"); + self.current_scope().debug(); + self.stack.debug(); + } +} + +impl Build for Environment { + fn build(&self, ctx: &Context, env: &Environment) -> String { + self.as_vec() + .iter() + .map(|typevalue| { + let typevalue = typevalue.borrow(); + typevalue.1.build(ctx, env) + }) + .collect::>() + .join("") + } +} diff --git a/src/ast2/environment/scope.rs b/src/ast2/environment/scope.rs new file mode 100644 index 0000000..136b8dc --- /dev/null +++ b/src/ast2/environment/scope.rs @@ -0,0 +1,51 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use super::stack::Stack; + +pub type Scope = Rc, usize>>>; +pub type ScopeList = Rc>>; + +pub trait UseScope { + fn define(&mut self, name: &str, index: usize); + fn clean(&self, stack: Stack); + + #[allow(dead_code)] + fn debug(&self); +} + +impl UseScope for Scope { + fn define(&mut self, name: &str, index: usize) { + if self.borrow().contains_key(name) { + panic!("Scope: Name '{}' already defined in this scope.", name); + } + self.borrow_mut().insert(name.into(), index); + } + + fn clean(&self, stack: Stack) { + for (_, value) in self.borrow().iter() { + stack.borrow_mut().remove(*value); + } + } + + fn debug(&self) { + println!("Scope Debug:"); + for (key, value) in self.borrow().iter() { + println!(" {} => {}", key, value); + } + } +} + +pub trait QueryScopes { + fn exists(&self, name: &str) -> Option; +} + +impl QueryScopes for ScopeList { + fn exists(&self, name: &str) -> Option { + for scope in self.borrow().iter().rev() { + if let Some(index) = scope.borrow().get(name) { + return Some(*index); + } + } + None + } +} diff --git a/src/ast2/environment/stack.rs b/src/ast2/environment/stack.rs new file mode 100644 index 0000000..1bf9434 --- /dev/null +++ b/src/ast2/environment/stack.rs @@ -0,0 +1,71 @@ +use std::{cell::RefCell, rc::Rc}; + +use super::CheckTypeValue; +use super::TypeValue; + +pub type Stack = Rc>>; + +pub trait UseStack { + fn set(&mut self, index: usize, value: TypeValue); + fn get(&self, index: usize) -> Option; + fn push(&mut self, value: TypeValue) -> usize; + fn remove(&mut self, index: usize) -> Option; + fn clean(&mut self, in_use: Vec); + + #[allow(dead_code)] + fn debug(&self); +} + +impl UseStack for Stack { + fn set(&mut self, index: usize, value: TypeValue) { + value.check(); + if index < self.borrow().len() { + self.borrow_mut()[index] = value; + } else { + panic!( + "Stack: Index out of bounds {} > {}", + index, + self.borrow().len() + ); + } + } + + fn get(&self, index: usize) -> Option { + if index < self.borrow().len() { + Some(self.borrow()[index].clone()) + } else { + None + } + } + + fn push(&mut self, value: TypeValue) -> usize { + value.check(); + self.borrow_mut().push(value); + self.borrow().len() - 1 + } + + fn remove(&mut self, index: usize) -> Option { + if index < self.borrow().len() { + Some(self.borrow_mut().remove(index)) + } else { + None + } + } + + fn clean(&mut self, in_use: Vec) { + for i in (0..self.borrow().len()).rev() { + if !in_use.contains(&i) { + self.borrow_mut().remove(i); + } + } + } + + fn debug(&self) { + println!("Stack:"); + for (i, v) in self.borrow().iter().enumerate() { + let t = &v.borrow().0; + let value = &v.borrow().1; + println!(" [{}] {} = {}", i, t, value); + } + } +} diff --git a/src/ast2/environment/type.rs b/src/ast2/environment/type.rs new file mode 100644 index 0000000..323afe3 --- /dev/null +++ b/src/ast2/environment/type.rs @@ -0,0 +1,61 @@ +use super::{TypeValue, Value}; +use std::{ + cell::RefCell, + fmt::{Display, Formatter, Result}, + rc::Rc, +}; + +#[allow(dead_code)] +#[derive(Clone)] +pub enum Type { + String, + Number, + Float, + Boolean, + Html, + Function, + Map, + List, + Nil, + Any, // not usable in the language, but needed to return any type using "use" +} + +impl Type { + pub fn with_value(&self, value: Value) -> TypeValue { + Rc::new(RefCell::new((self.clone(), value))) + } + + pub fn matches(&self, value: &Value) -> bool { + let t = value.type_of(); + match (self, t) { + (Type::String, Type::String) => true, + (Type::Number, Type::Number) => true, + (Type::Float, Type::Float) => true, + (Type::Boolean, Type::Boolean) => true, + (Type::Html, Type::Html) => true, + (Type::Function, Type::Function) => true, + (Type::Map, Type::Map) => true, + (Type::List, Type::List) => true, + (_, Type::Nil) => true, + (Type::Any, _) => true, + _ => false, + } + } +} + +impl Display for Type { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + Type::String => write!(f, "String"), + Type::Number => write!(f, "Number"), + Type::Float => write!(f, "Float"), + Type::Boolean => write!(f, "Boolean"), + Type::Html => write!(f, "Html"), + Type::Function => write!(f, "Function"), + Type::Map => write!(f, "Map"), + Type::List => write!(f, "List"), + Type::Nil => write!(f, "Nil"), + Type::Any => write!(f, "Any"), + } + } +} diff --git a/src/ast2/environment/value.rs b/src/ast2/environment/value.rs new file mode 100644 index 0000000..663aa2d --- /dev/null +++ b/src/ast2/environment/value.rs @@ -0,0 +1,106 @@ +use std::fmt::{Display, Formatter, Result}; +use std::sync::Arc; +use std::{cell::RefCell, rc::Rc}; + +use crate::ast2::functions::FunctionRunner; +use crate::ast2::Node; +use crate::{ast2::Build, context::Context}; + +use super::scope::ScopeList; +use super::{Environment, Type}; + +pub type TypeValue = Rc>; + +pub trait CheckTypeValue { + fn check(&self); +} + +impl CheckTypeValue for TypeValue { + fn check(&self) { + let typevalue = self.borrow(); + let t = &typevalue.0; + let value = &typevalue.1; + if !t.matches(value) { + panic!("Type mismatch: expected {}, found {}", t, value); + } + } +} + +#[derive(Clone)] +pub enum Value { + String(String), + Number(i64), + Float(f64), + Boolean(bool), + Html(Node, Option), + Function(Arc, Option), + Map(Environment), + List(Environment), + Nil, +} + +impl Value { + pub fn type_of(&self) -> Type { + match self { + Value::String(_) => Type::String, + Value::Number(_) => Type::Number, + Value::Float(_) => Type::Float, + Value::Boolean(_) => Type::Boolean, + Value::Html(..) => Type::Html, + Value::Function(..) => Type::Function, + Value::Map(_) => Type::Map, + Value::List(_) => Type::List, + Value::Nil => Type::Nil, + } + } + + pub fn as_typevalue(self) -> TypeValue { + let t = self.type_of(); + Rc::new(RefCell::new((t, self))) + } +} + +impl Build for Value { + fn build(&self, ctx: &Context, env: &Environment) -> String { + match self { + Value::String(s) => s.clone(), + Value::Number(n) => n.to_string(), + Value::Float(n) => n.to_string(), + Value::Boolean(b) => b.to_string(), + Value::Html(node, scopes) => { + let env = if let Some(scopes) = scopes { + env.from_scopes(scopes.clone()) + } else { + env.clone() + }; + + node(ctx, &env) + } + Value::Function(..) => "function".to_string(), + Value::Map(sub_env) => sub_env.build(ctx, env), + Value::List(sub_env) => sub_env.build(ctx, env), + Value::Nil => "nil".to_string(), + } + } +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::String(l), Value::String(r)) => l == r, + (Value::Number(l), Value::Number(r)) => l == r, + (Value::Float(l), Value::Float(r)) => l == r, + (Value::Boolean(l), Value::Boolean(r)) => l == r, + (Value::Nil, Value::Nil) => true, + + _ => std::mem::discriminant(self) == std::mem::discriminant(other), + } + } +} + +impl Display for Value { + fn fmt(&self, formatter: &mut Formatter) -> Result { + let t = self.type_of(); + write!(formatter, "{}", t) + } +} diff --git a/src/ast2/expression/calculus/add.rs b/src/ast2/expression/calculus/add.rs new file mode 100644 index 0000000..7d9daf9 --- /dev/null +++ b/src/ast2/expression/calculus/add.rs @@ -0,0 +1,19 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn add(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (&left_value, &right_value) { + (Value::Number(l), Value::Number(r)) => Value::Number(l + r), + (Value::Float(l), Value::Float(r)) => Value::Float(l + r), + (Value::String(l), Value::String(r)) => Value::String(format!("{}{}", l, r)), + _ => panic!( + "Type mismatch in addition: {} + {}", + left_value, right_value + ), + } + }) +} diff --git a/src/ast2/expression/calculus/divide.rs b/src/ast2/expression/calculus/divide.rs new file mode 100644 index 0000000..9768798 --- /dev/null +++ b/src/ast2/expression/calculus/divide.rs @@ -0,0 +1,29 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn divide(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (&left_value, &right_value) { + (Value::Number(l), Value::Number(r)) => { + if *r == 0 { + panic!("Division by zero"); + } + Value::Number(l / r) + } + (Value::Float(l), Value::Float(r)) => { + if *r == 0.0 { + panic!("Division by zero"); + } + Value::Float(l / r) + } + (Value::String(l), Value::String(r)) => Value::String(format!("{}{}", l, r)), + _ => panic!( + "Type mismatch in addition: {} + {}", + left_value, right_value + ), + } + }) +} diff --git a/src/ast2/expression/calculus/mod.rs b/src/ast2/expression/calculus/mod.rs new file mode 100644 index 0000000..5357268 --- /dev/null +++ b/src/ast2/expression/calculus/mod.rs @@ -0,0 +1,6 @@ +use crate::prelude::*; + +inherit!(add); +inherit!(subtract); +inherit!(multiply); +inherit!(divide); diff --git a/src/ast2/expression/calculus/multiply.rs b/src/ast2/expression/calculus/multiply.rs new file mode 100644 index 0000000..5a4cc96 --- /dev/null +++ b/src/ast2/expression/calculus/multiply.rs @@ -0,0 +1,19 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn multiply(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (&left_value, &right_value) { + (Value::Number(l), Value::Number(r)) => Value::Number(l * r), + (Value::Float(l), Value::Float(r)) => Value::Float(l * r), + (Value::String(l), Value::String(r)) => Value::String(format!("{}{}", l, r)), + _ => panic!( + "Type mismatch in addition: {} + {}", + left_value, right_value + ), + } + }) +} diff --git a/src/ast2/expression/calculus/subtract.rs b/src/ast2/expression/calculus/subtract.rs new file mode 100644 index 0000000..43b1fac --- /dev/null +++ b/src/ast2/expression/calculus/subtract.rs @@ -0,0 +1,19 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn subtract(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (&left_value, &right_value) { + (Value::Number(l), Value::Number(r)) => Value::Number(l - r), + (Value::Float(l), Value::Float(r)) => Value::Float(l - r), + (Value::String(l), Value::String(r)) => Value::String(format!("{}{}", l, r)), + _ => panic!( + "Type mismatch in addition: {} + {}", + left_value, right_value + ), + } + }) +} diff --git a/src/ast2/expression/call.rs b/src/ast2/expression/call.rs new file mode 100644 index 0000000..d267188 --- /dev/null +++ b/src/ast2/expression/call.rs @@ -0,0 +1,22 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn call(value: Expression, arguments: Vec) -> Expression { + Box::new(move |ctx, env| { + let function = value(ctx, env); + match function { + Value::Function(function, scopes) => { + let env = if let Some(scopes) = scopes { + env.from_scopes(scopes) + } else { + env.clone() + }; + + function.call(ctx, &env, &arguments) + } + _ => { + panic!("Expected a function, got {}", function); + } + } + }) +} diff --git a/src/ast2/expression/comparisons/and.rs b/src/ast2/expression/comparisons/and.rs new file mode 100644 index 0000000..bda10c2 --- /dev/null +++ b/src/ast2/expression/comparisons/and.rs @@ -0,0 +1,19 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn and(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (&left_value, &right_value) { + (Value::Boolean(l), Value::Boolean(r)) => Value::Boolean(*l && *r), + _ => { + panic!( + "Type mismatch in logical AND: {} && {}", + left_value, right_value + ); + } + } + }) +} diff --git a/src/ast2/expression/comparisons/equal.rs b/src/ast2/expression/comparisons/equal.rs new file mode 100644 index 0000000..1c70c73 --- /dev/null +++ b/src/ast2/expression/comparisons/equal.rs @@ -0,0 +1,11 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn equal(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + Value::Boolean(left_value == right_value) + }) +} diff --git a/src/ast2/expression/comparisons/greater.rs b/src/ast2/expression/comparisons/greater.rs new file mode 100644 index 0000000..a5492a2 --- /dev/null +++ b/src/ast2/expression/comparisons/greater.rs @@ -0,0 +1,16 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn greater(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (left_value, right_value) { + (Value::Number(l), Value::Number(r)) => Value::Boolean(l > r), + (Value::String(l), Value::String(r)) => Value::Boolean(l > r), + (Value::Boolean(l), Value::Boolean(r)) => Value::Boolean(l > r), + _ => panic!("Cannot compare values of different types or unsupported types"), + } + }) +} diff --git a/src/ast2/expression/comparisons/greater_equal.rs b/src/ast2/expression/comparisons/greater_equal.rs new file mode 100644 index 0000000..14b13a6 --- /dev/null +++ b/src/ast2/expression/comparisons/greater_equal.rs @@ -0,0 +1,16 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn greater_equal(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (left_value, right_value) { + (Value::Number(l), Value::Number(r)) => Value::Boolean(l >= r), + (Value::String(l), Value::String(r)) => Value::Boolean(l >= r), + (Value::Boolean(l), Value::Boolean(r)) => Value::Boolean(l >= r), + _ => panic!("Cannot compare values of different types or unsupported types"), + } + }) +} diff --git a/src/ast2/expression/comparisons/lesser.rs b/src/ast2/expression/comparisons/lesser.rs new file mode 100644 index 0000000..edc29c5 --- /dev/null +++ b/src/ast2/expression/comparisons/lesser.rs @@ -0,0 +1,19 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn lesser(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (&left_value, &right_value) { + (Value::Number(l), Value::Number(r)) => Value::Boolean(l < r), + (Value::String(l), Value::String(r)) => Value::Boolean(l < r), + (Value::Boolean(l), Value::Boolean(r)) => Value::Boolean(l < r), + _ => panic!( + "Cannot compare values of different types or unsupported types: {} & {}", + left_value, right_value + ), + } + }) +} diff --git a/src/ast2/expression/comparisons/lesser_equal.rs b/src/ast2/expression/comparisons/lesser_equal.rs new file mode 100644 index 0000000..05b46fa --- /dev/null +++ b/src/ast2/expression/comparisons/lesser_equal.rs @@ -0,0 +1,16 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn lesser_equal(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (left_value, right_value) { + (Value::Number(l), Value::Number(r)) => Value::Boolean(l <= r), + (Value::String(l), Value::String(r)) => Value::Boolean(l <= r), + (Value::Boolean(l), Value::Boolean(r)) => Value::Boolean(l <= r), + _ => panic!("Cannot compare values of different types or unsupported types"), + } + }) +} diff --git a/src/ast2/expression/comparisons/mod.rs b/src/ast2/expression/comparisons/mod.rs new file mode 100644 index 0000000..2b451ac --- /dev/null +++ b/src/ast2/expression/comparisons/mod.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; + +inherit!(equal); +inherit!(notequal); +inherit!(or); +inherit!(and); +inherit!(lesser); +inherit!(lesser_equal); +inherit!(greater); +inherit!(greater_equal); diff --git a/src/ast2/expression/comparisons/notequal.rs b/src/ast2/expression/comparisons/notequal.rs new file mode 100644 index 0000000..1bbaaef --- /dev/null +++ b/src/ast2/expression/comparisons/notequal.rs @@ -0,0 +1,11 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn notequal(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + Value::Boolean(left_value != right_value) + }) +} diff --git a/src/ast2/expression/comparisons/or.rs b/src/ast2/expression/comparisons/or.rs new file mode 100644 index 0000000..3b64ca5 --- /dev/null +++ b/src/ast2/expression/comparisons/or.rs @@ -0,0 +1,19 @@ +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn or(left: Expression, right: Expression) -> Expression { + Box::new(move |ctx, scope| { + let left_value = left(ctx, scope); + let right_value = right(ctx, scope); + + match (&left_value, &right_value) { + (Value::Boolean(l), Value::Boolean(r)) => Value::Boolean(*l || *r), + _ => { + panic!( + "Type mismatch in logical AND: {} && {}", + left_value, right_value + ); + } + } + }) +} diff --git a/src/ast2/expression/identifier.rs b/src/ast2/expression/identifier.rs new file mode 100644 index 0000000..6152ca6 --- /dev/null +++ b/src/ast2/expression/identifier.rs @@ -0,0 +1,33 @@ +use log::warn; + +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn identifier(location: Vec) -> Expression { + Box::new(move |_ctx, env| { + let first = location + .first() + .expect("Identifier must have at least one part"); + let mut current = env.get(first); + + for part in location.iter().skip(1) { + current = if let Some(tv) = current { + let borrowed = tv.borrow(); + if let Value::Map(map) = &borrowed.1 { + map.as_map().get(part).cloned() + } else { + None + } + } else { + None + }; + } + + if let Some(tv) = current { + tv.borrow().1.clone() + } else { + warn!("Identifier not found: {}", location.join(".")); + Value::Nil + } + }) +} diff --git a/src/ast2/expression/list.rs b/src/ast2/expression/list.rs new file mode 100644 index 0000000..4ac83c2 --- /dev/null +++ b/src/ast2/expression/list.rs @@ -0,0 +1,16 @@ +use crate::ast2::environment::{Environment, Value}; + +use super::Expression; + +pub fn list(items: Vec) -> Expression { + Box::new(move |ctx, env| { + let values: Vec = items.iter().map(|item| item(ctx, env)).collect(); + let mut list = Environment::new(); + + for (index, value) in values.into_iter().enumerate() { + list.set(index.to_string().as_str(), value.as_typevalue()); + } + + Value::List(list) + }) +} diff --git a/src/ast2/expression/map.rs b/src/ast2/expression/map.rs new file mode 100644 index 0000000..a3c4d9f --- /dev/null +++ b/src/ast2/expression/map.rs @@ -0,0 +1,22 @@ +use crate::ast2::{ + environment::{Type, Value}, + Environment, +}; + +use super::Expression; + +pub fn map(entries: Vec<(Type, String, Option)>) -> Expression { + Box::new(move |ctx, env| { + let mut map = Environment::new(); + for entry in entries.iter() { + let value = if let Some(expr) = &entry.2 { + expr(ctx, env) + } else { + Value::Nil + }; + map.define(entry.1.as_str(), entry.0.with_value(value)); + } + + Value::Map(map) + }) +} diff --git a/src/ast2/expression/mod.rs b/src/ast2/expression/mod.rs new file mode 100644 index 0000000..50539ef --- /dev/null +++ b/src/ast2/expression/mod.rs @@ -0,0 +1,28 @@ +use super::environment::Value; +use super::Environment; +use crate::context::Context; +use crate::prelude::*; + +inherits!( + comparisons, + [ + equal, + notequal, + or, + and, + lesser, + lesser_equal, + greater, + greater_equal + ] +); +inherits!(calculus, [add, subtract, multiply, divide]); +inherit!(call); +inherit!(identifier); +inherit!(list); +inherit!(map); +inherit!(value); +inherit!(script); +inherit!(object_entry); + +pub type Expression = Box Value + 'static>; diff --git a/src/ast2/expression/object_entry.rs b/src/ast2/expression/object_entry.rs new file mode 100644 index 0000000..40f8c61 --- /dev/null +++ b/src/ast2/expression/object_entry.rs @@ -0,0 +1,28 @@ +use crate::ast2::environment::Value; + +use super::Expression; + +pub fn object_entry(env_obj: Expression, entry: Expression) -> Expression { + Box::new(move |ctx, env| { + let env_value = env_obj(ctx, env); + let key = entry(ctx, env); + let env_ = match env_value { + Value::Map(map_value) => map_value, + Value::List(array_value) => array_value, + _ => { + panic!("Expected a map or array, got {}", env_value); + } + }; + + let key = match key { + Value::String(s) => s, + Value::Number(n) => n.to_string(), + _ => panic!("Expected a string or number as key, got {}", key), + }; + if let Some(value) = env_.get(&key) { + value.borrow().1.clone() + } else { + Value::Nil + } + }) +} diff --git a/src/ast2/expression/script.rs b/src/ast2/expression/script.rs new file mode 100644 index 0000000..82c9a12 --- /dev/null +++ b/src/ast2/expression/script.rs @@ -0,0 +1,18 @@ +use std::process::Command; + +use crate::ast2::environment::Value; + +use super::Expression; + +pub fn script(script: Box) -> Expression { + Box::new(move |_ctx, _scope| { + let result = Command::new("bash") + .arg("-c") + .arg(script.to_string()) + .output(); + match result { + Ok(output) => Value::String(String::from_utf8_lossy(&output.stdout).trim().to_string()), + Err(e) => panic!("Failed to execute script '{}': {}", script, e), + } + }) +} diff --git a/src/ast2/expression/value.rs b/src/ast2/expression/value.rs new file mode 100644 index 0000000..7c474a3 --- /dev/null +++ b/src/ast2/expression/value.rs @@ -0,0 +1,10 @@ +use super::Expression; +use crate::ast2::environment::Value; + +pub fn value(value: Value) -> Expression { + Box::new(move |_ctx, env| match &value { + Value::Function(func, _) => Value::Function(func.clone(), Some(env.clone_scopes())), + Value::Html(node, _) => Value::Html(node.clone(), Some(env.clone_scopes())), + _ => value.clone(), + }) +} diff --git a/src/ast2/functions/mod.rs b/src/ast2/functions/mod.rs new file mode 100644 index 0000000..7b45259 --- /dev/null +++ b/src/ast2/functions/mod.rs @@ -0,0 +1,72 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::context::Context; + +use super::{ + environment::{Type, Value}, + statement::Result, + Environment, Expression, Statement, +}; + +pub type FunctionParameter = Rc)>>; +pub type FunctionBody = Rc>; +pub type FunctionValue = ( + Rc)>>, + Type, + FunctionBody, +); + +pub trait FunctionRunner { + fn call(&self, ctx: &Context, env: &Environment, inputs: &Vec) -> Value; +} + +impl FunctionRunner for FunctionValue { + fn call(&self, ctx: &Context, env: &Environment, inputs: &Vec) -> Value { + let (parameters, return_type, body) = self; + + // init params and override with inputs + for (i, param) in parameters.iter().enumerate() { + let (param_type, param_name, default) = param; + let input = inputs.get(i); + let value = if let Some(input) = input { + input(ctx, env) + } else if let Some(default) = default { + default(ctx, env) + } else { + Value::Nil + }; + + env.clone().set( + param_name, + Rc::new(RefCell::new((param_type.clone(), value))), + ); + } + + env.clone().subscope(|| { + for statement in body.iter() { + let value = match statement(ctx, env) { + Result::Return(value) => Some(value), + Result::Collect(value) => { + let list = Environment::new(); + for _ in value { + todo!() + } + Some(Value::List(list)) + } + Result::NOP => None, + _ => panic!("Unexpected result from statement"), + }; + + if let Some(value) = value { + if return_type.matches(&value) { + return value; + } else { + panic!("Type mismatch: expected {}, got {}", return_type, value); + } + } + } + + Value::Nil + }) + } +} diff --git a/src/ast2/mod.rs b/src/ast2/mod.rs new file mode 100644 index 0000000..f16cd99 --- /dev/null +++ b/src/ast2/mod.rs @@ -0,0 +1,17 @@ +pub mod builtin; +pub mod environment; +pub mod expression; +pub mod functions; +pub mod node; +pub mod statement; + +pub use environment::Environment; +pub use expression::Expression; +pub use node::Node; +pub use statement::Statement; + +use crate::context::Context; + +pub trait Build { + fn build(&self, ctx: &Context, env: &Environment) -> String; +} diff --git a/src/ast2/node/html.rs b/src/ast2/node/html.rs new file mode 100644 index 0000000..991bdbd --- /dev/null +++ b/src/ast2/node/html.rs @@ -0,0 +1,83 @@ +use super::Node; +use crate::ast2::Environment; +use crate::context::Context; +use std::collections::HashMap; +use std::sync::Arc; + +pub fn html(element: Html) -> Node { + Arc::new(move |ctx, env| element.build(ctx, env)) +} + +pub struct Html { + tag: Box, + attributes: HashMap>, + body: Vec, +} + +impl Html { + pub fn new(tag: Box) -> Self { + Html { + tag, + attributes: HashMap::new(), + body: vec![], + } + } + + pub fn with_attributes(mut self, attributes: Vec<(String, Vec)>) -> Self { + for (key, value) in attributes { + match &*key { + "id" => { + self.attributes.insert(key, value); + } + _ => { + self.attributes + .entry(key) + .or_insert_with(Vec::new) + .extend(value); + } + } + } + self + } + + pub fn with_children(mut self, children: Vec) -> Self { + self.body.extend(children); + self + } + + fn build(&self, ctx: &Context, env: &Environment) -> String { + let attributes = self + .attributes + .iter() + .map(|(k, v)| { + let value = v + .iter() + .map(|node| node(ctx, env)) + .collect::>() + .join(" "); + + if value.is_empty() { + k.to_string() + } else { + format!(r#"{}="{}""#, k, value) + } + }) + .collect::>() + .join(" "); + + let body = self + .body + .iter() + .map(|node| node(ctx, env)) + .collect::>() + .join(""); + let tag = &*self.tag; + + match (attributes.as_str(), body.as_str()) { + ("", "") => format!("<{}/>", tag), + (_, "") => format!("<{} {} />", tag, attributes), + ("", _) => format!("<{}>{}", tag, body, tag), + _ => format!("<{} {}>{}", tag, attributes, body, tag), + } + } +} diff --git a/src/ast2/node/insertion.rs b/src/ast2/node/insertion.rs new file mode 100644 index 0000000..2798188 --- /dev/null +++ b/src/ast2/node/insertion.rs @@ -0,0 +1,10 @@ +use std::sync::Arc; + +use crate::ast2::Build; +use crate::ast2::Expression; + +use super::Node; + +pub fn insertion(expr: Expression) -> Node { + Arc::new(move |ctx, env| expr(ctx, env).build(ctx, env)) +} diff --git a/src/ast2/node/logic.rs b/src/ast2/node/logic.rs new file mode 100644 index 0000000..6f58285 --- /dev/null +++ b/src/ast2/node/logic.rs @@ -0,0 +1,22 @@ +use std::sync::Arc; + +use crate::ast2::statement::Result; +use crate::ast2::{Build, Expression, Statement}; + +use super::Node; + +pub fn logic_statement(logic: Statement) -> Node { + Arc::new(move |ctx, env| match logic(ctx, env) { + Result::Collect(value) => value + .iter() + .map(|v| v.build(ctx, env)) + .collect::>() + .join(""), + Result::Return(value) => value.build(ctx, env), + Result::Break | Result::Continue | Result::NOP => String::new(), + }) +} + +pub fn logic_expression(logic: Expression) -> Node { + Arc::new(move |ctx, env| logic(ctx, env).build(ctx, env)) +} diff --git a/src/ast2/node/mod.rs b/src/ast2/node/mod.rs new file mode 100644 index 0000000..74d3986 --- /dev/null +++ b/src/ast2/node/mod.rs @@ -0,0 +1,12 @@ +use std::sync::Arc; + +use super::Environment; +use crate::context::Context; +use crate::prelude::*; + +inherits!(html, [html, Html]); +inherit!(insertion); +inherits!(logic, [logic_expression, logic_statement]); +inherit!(text); + +pub type Node = Arc String + 'static>; diff --git a/src/ast2/node/text.rs b/src/ast2/node/text.rs new file mode 100644 index 0000000..b5e0028 --- /dev/null +++ b/src/ast2/node/text.rs @@ -0,0 +1,7 @@ +use std::sync::Arc; + +use super::Node; + +pub fn text(text: String) -> Node { + Arc::new(move |_ctx, _env| text.to_string()) +} diff --git a/src/ast2/statement/assign.rs b/src/ast2/statement/assign.rs new file mode 100644 index 0000000..a896eaa --- /dev/null +++ b/src/ast2/statement/assign.rs @@ -0,0 +1,10 @@ +use super::{Result, Statement}; +use crate::ast2::expression::Expression; + +pub fn assign(name: String, expression: Expression) -> Statement { + Box::new(move |ctx, env| { + let value = expression(ctx, env); + env.clone().assign(name.as_str(), value.as_typevalue()); + Result::NOP + }) +} diff --git a/src/ast2/statement/break.rs b/src/ast2/statement/break.rs new file mode 100644 index 0000000..301f21f --- /dev/null +++ b/src/ast2/statement/break.rs @@ -0,0 +1,5 @@ +use super::{Result, Statement}; + +pub fn break_s() -> Statement { + Box::new(move |_ctx, _env| Result::Break) +} diff --git a/src/ast2/statement/call.rs b/src/ast2/statement/call.rs new file mode 100644 index 0000000..8035e98 --- /dev/null +++ b/src/ast2/statement/call.rs @@ -0,0 +1,25 @@ +use log::warn; + +use super::{Result, Statement}; +use crate::ast2::environment::Value; +use crate::ast2::expression::Expression; + +pub fn call(value: Expression, arguments: Vec) -> Statement { + Box::new(move |ctx, env| { + let function = value(ctx, env); + match function { + Value::Function(function, scopes) => { + let env = if let Some(scopes) = scopes { + env.from_scopes(scopes) + } else { + env.clone() + }; + + function.call(ctx, &env, &arguments); + } + _ => warn!("Expected a function, got {}", function), + } + + Result::NOP + }) +} diff --git a/src/ast2/statement/collect.rs b/src/ast2/statement/collect.rs new file mode 100644 index 0000000..9615c13 --- /dev/null +++ b/src/ast2/statement/collect.rs @@ -0,0 +1,9 @@ +use super::{Result, Statement}; +use crate::ast2::expression::Expression; + +pub fn collect(expression: Expression) -> Statement { + Box::new(move |ctx, env| { + let value = expression(ctx, env); + Result::Collect(vec![value]) + }) +} diff --git a/src/ast2/statement/continue.rs b/src/ast2/statement/continue.rs new file mode 100644 index 0000000..5faafec --- /dev/null +++ b/src/ast2/statement/continue.rs @@ -0,0 +1,5 @@ +use super::{Result, Statement}; + +pub fn continue_s() -> Statement { + Box::new(move |_ctx, _env| Result::Continue) +} diff --git a/src/ast2/statement/define.rs b/src/ast2/statement/define.rs new file mode 100644 index 0000000..8d4368d --- /dev/null +++ b/src/ast2/statement/define.rs @@ -0,0 +1,17 @@ +use super::{Result, Statement}; +use crate::ast2::{ + environment::{Type, Value}, + expression::Expression, +}; + +pub fn define(t: Type, name: String, expression: Option) -> Statement { + Box::new(move |ctx, env| { + let value = if let Some(expr) = &expression { + expr(ctx, env) + } else { + Value::Nil + }; + env.clone().define(name.as_str(), t.with_value(value)); + Result::NOP + }) +} diff --git a/src/ast2/statement/for.rs b/src/ast2/statement/for.rs new file mode 100644 index 0000000..70785bc --- /dev/null +++ b/src/ast2/statement/for.rs @@ -0,0 +1,47 @@ +use super::{Result, Statement}; +use crate::ast2::{environment::Value, expression::Expression}; + +pub fn for_s( + init: Statement, + condition: Expression, + increment: Statement, + body: Vec, +) -> Statement { + Box::new(move |ctx, env| { + env.clone().subscope(|| { + init(ctx, env); + + let mut collected_values = vec![]; + 'mainloop: loop { + if let Value::Boolean(false) = condition(ctx, env) { + break; + } + + for stmt in body.iter() { + let result = stmt(ctx, env); + match result { + Result::Continue => { + increment(ctx, env); + continue 'mainloop; + } + Result::Collect(values) => { + collected_values.extend(values); + } + Result::NOP => {} + _ => { + return result; + } + } + } + + increment(ctx, env); + } + + if collected_values.is_empty() { + Result::NOP + } else { + Result::Collect(collected_values) + } + }) + }) +} diff --git a/src/ast2/statement/if.rs b/src/ast2/statement/if.rs new file mode 100644 index 0000000..bd9cbb3 --- /dev/null +++ b/src/ast2/statement/if.rs @@ -0,0 +1,33 @@ +use super::{Result, Statement}; +use crate::ast2::{environment::Value, expression::Expression}; + +pub fn if_s(condition: Expression, body: Vec) -> Statement { + Box::new(move |ctx, env| { + let condition_value = condition(ctx, env); + let mut collected_values = vec![]; + let mut result = Result::NOP; + if let Value::Boolean(true) = condition_value { + result = env.clone().subscope(|| { + for stmt in body.iter() { + let result = stmt(ctx, env); + match result { + Result::Collect(values) => { + collected_values.extend(values); + } + Result::NOP => {} + _ => { + return result; + } + } + } + return Result::NOP; + }); + } + + if collected_values.is_empty() { + result + } else { + Result::Collect(collected_values) + } + }) +} diff --git a/src/ast2/statement/iter.rs b/src/ast2/statement/iter.rs new file mode 100644 index 0000000..1ef63a1 --- /dev/null +++ b/src/ast2/statement/iter.rs @@ -0,0 +1,66 @@ +use super::{Result, Statement}; +use crate::ast2::expression::Expression; + +pub fn iter( + _identifiers: (String, Option), + _iterable: Expression, + _body: Vec, +) -> Statement { + Box::new(move |_ctx, env| { + env.clone().subscope(|| { + Result::NOP + // let (key_name, value_name) = identifiers.clone(); + // let var = iterable(ctx, env); + // let (_scope, indices) = match var { + // Value::List(mut list) => { + // let indices = list.get_indices(); + // (list, indices) + // } + // Value::Map(mut map) => { + // let keys = map.get_keys(); + // (map, keys) + // } + // _ => { + // panic!("Expected an array or map, got {}", var); + // } + // }; + // + // let mut collected_values = vec![]; + // env.define(Type::Any, key_name.to_string(), Value::Nil); + // if let Some(value_name) = &value_name { + // env.define(Type::Any, value_name.to_string(), Value::Nil); + // } + // 'mainloop: for index in indices { + // env.set(key_name.to_string(), Value::String(index.clone())); + // if let Some(value_name) = &value_name { + // env.set( + // value_name.to_string(), + // _scope.get(&index).unwrap_or(&Value::Nil).clone(), + // ); + // } + // + // for statement in body.iter() { + // let result = statement(ctx, env); + // match result { + // Result::Continue => { + // continue 'mainloop; + // } + // Result::Collect(values) => { + // collected_values.extend(values); + // } + // Result::NOP => {} + // _ => { + // return result; + // } + // } + // } + // } + // + // if collected_values.is_empty() { + // return Result::NOP; + // } else { + // return Result::Collect(collected_values); + // } + }) + }) +} diff --git a/src/ast2/statement/mod.rs b/src/ast2/statement/mod.rs new file mode 100644 index 0000000..cabd396 --- /dev/null +++ b/src/ast2/statement/mod.rs @@ -0,0 +1,24 @@ +use super::{environment::Value, Environment}; +use crate::context::Context; +use crate::prelude::*; + +inherit!(call); +inherit!(assign); +inherit!(define); +inherit!(collect); +inherits!(r#return, [return_s]); +inherits!(r#break, [break_s]); +inherits!(r#continue, [continue_s]); +inherits!(r#if, [if_s]); +inherits!(r#for, [for_s]); +inherit!(iter); + +pub enum Result { + Collect(Vec), + Return(Value), + Break, + Continue, + NOP, +} + +pub type Statement = Box Result + 'static>; diff --git a/src/ast2/statement/return.rs b/src/ast2/statement/return.rs new file mode 100644 index 0000000..f92c601 --- /dev/null +++ b/src/ast2/statement/return.rs @@ -0,0 +1,13 @@ +use super::{Result, Statement}; +use crate::ast2::expression::Expression; + +pub fn return_s(expression: Option) -> Statement { + if let Some(expr) = expression { + Box::new(move |ctx, env| { + let value = expr(ctx, env); + Result::Return(value) + }) + } else { + Box::new(|_ctx, _env| Result::Break) + } +} diff --git a/src/cli/build.rs b/src/cli/build.rs index 27ea54b..a7cf88f 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -1,63 +1,71 @@ -use std::path::Path; - -use crate::ast::function::default_function; -use crate::context::Context; -use crate::resolver::{self, resource::Resource}; +use crate::ast2::{builtin, Build, Environment}; +use crate::context::resource::{Resource, ResourceListExt}; +use crate::{context, context::Context}; pub fn build(ctx: &mut Context) { - resolver::load_dir(ctx); + context::load_dir(ctx); - // Process pages - resolver::get_all(ctx).iter().for_each(|resource| { + ctx.resources.get_pages().iter().for_each(|resource| { if let Resource::File(file) = &*resource.borrow() { - if !file.is_page { - return; - } - - let mut scope = file.get_scope(ctx); - - let output_path = if let Some(meta) = scope.get("meta") { - let meta = meta.clone().try_into_map().unwrap(); - let url = meta.get("url").unwrap(); - let url = url.clone().try_into_string().unwrap(); - - Resource::get_output_path(ctx, url.as_str()).unwrap() - } else { - Resource::get_output_path(ctx, &file.src.to_str().unwrap()).unwrap() - }; - - let content = default_function(ctx, &file.ast, &vec![], &mut scope.clone()); - let content = content.render(ctx, &mut scope); - let output = ctx.save_content(output_path.to_str().unwrap(), content.as_str()); - println!("[DAISY] Built {} -> {}", file.src.to_str().unwrap(), output); + println!("[DAISY] Building page {}", file.src.to_str().unwrap()); + let built = file.build(ctx); + println!("{}", built); } else { panic!("Expected a File resource for page"); } }); - // after pages have been process, new resources have been added, process these resources - resolver::get_all(ctx) - .iter() - .for_each(|resource| match &*resource.borrow() { - Resource::SCSS(src, path, content) => { - let output = ctx.save_content(path, content); - println!("[SCSS] Built SCSS {} -> {}", src, output); - } - Resource::Other(src, output) => { - std::fs::create_dir_all(Path::new(output).parent().unwrap()).unwrap_or_else( - |err| { - panic!("Failed to create directory {}: {}", output, err); - }, - ); - - std::fs::copy(src, output).unwrap_or_else(|err| { - panic!( - "Failed to copy resource from {} to {}: {}", - src, output, err - ); - }); - println!("[ASSET] Copied {} -> {}", src, output); - } - _ => {} - }); + // Process pages + // resolver::get_all(ctx).iter().for_each(|resource| { + // if let Resource::File(file) = &*resource.borrow() { + // if !file.is_page { + // return; + // } + // + // let mut scope = file.get_scope(ctx); + // + // let output_path = if let Some(meta) = scope.get("meta") { + // let meta = meta.clone().try_into_map().unwrap(); + // let url = meta.get("url").unwrap(); + // let url = url.clone().try_into_string().unwrap(); + // + // Resource::get_output_path(ctx, url.as_str()).unwrap() + // } else { + // Resource::get_output_path(ctx, &file.src.to_str().unwrap()).unwrap() + // }; + // + // let content = default_function(ctx, &file.ast, &vec![], &mut scope.clone()); + // let content = content.render(ctx, &mut scope); + // let output = ctx.save_content(output_path.to_str().unwrap(), content.as_str()); + // println!("[DAISY] Built {} -> {}", file.src.to_str().unwrap(), output); + // } else { + // panic!("Expected a File resource for page"); + // } + // }); + // + // // after pages have been process, new resources have been added, process these resources + // resolver::get_all(ctx) + // .iter() + // .for_each(|resource| match &*resource.borrow() { + // Resource::SCSS(src, path, content) => { + // let output = ctx.save_content(path, content); + // println!("[SCSS] Built SCSS {} -> {}", src, output); + // } + // Resource::Other(src, output) => { + // std::fs::create_dir_all(Path::new(output).parent().unwrap()).unwrap_or_else( + // |err| { + // panic!("Failed to create directory {}: {}", output, err); + // }, + // ); + // + // std::fs::copy(src, output).unwrap_or_else(|err| { + // panic!( + // "Failed to copy resource from {} to {}: {}", + // src, output, err + // ); + // }); + // println!("[ASSET] Copied {} -> {}", src, output); + // } + // _ => {} + // }); } diff --git a/src/context/config/assets.rs b/src/context/config/assets.rs new file mode 100644 index 0000000..e1ca6cc --- /dev/null +++ b/src/context/config/assets.rs @@ -0,0 +1,15 @@ +use serde::Deserialize; + +#[derive(Deserialize, Clone)] +#[serde(default)] +pub struct Assets { + pub folder: String, +} + +impl Default for Assets { + fn default() -> Self { + Assets { + folder: "assets".to_string(), + } + } +} diff --git a/src/context/config/mod.rs b/src/context/config/mod.rs new file mode 100644 index 0000000..ee9905d --- /dev/null +++ b/src/context/config/mod.rs @@ -0,0 +1,45 @@ +use log::warn; +use serde::Deserialize; +use std::fs; + +mod assets; +mod paths; + +use assets::Assets; +use paths::Paths; + +#[derive(Deserialize, Clone)] +#[serde(default)] +pub struct Config { + pub paths: Paths, + pub assets: Assets, + pub pretty: bool, +} + +impl Default for Config { + fn default() -> Self { + Config { + paths: Paths::default(), + assets: Assets::default(), + pretty: false, + } + } +} + +const DAISY_CONFIG: &str = "daisy.toml"; + +impl Config { + pub fn new() -> Self { + let config_raw = fs::read_to_string(DAISY_CONFIG).unwrap_or_else(|_| { + warn!("{} not found, using default config", DAISY_CONFIG); + String::new() + }); + + toml::from_str::(&config_raw).unwrap().sanitize() + } + + pub fn sanitize(self) -> Self { + // todo: sanitize paths + self + } +} diff --git a/src/context/config/paths.rs b/src/context/config/paths.rs new file mode 100644 index 0000000..e09d842 --- /dev/null +++ b/src/context/config/paths.rs @@ -0,0 +1,70 @@ +use std::path::{Path, PathBuf}; + +use serde::Deserialize; + +#[derive(Deserialize, Clone)] +#[serde(default)] +pub struct Paths { + pub workdir: String, + pub pages: String, + pub output: String, +} + +impl Default for Paths { + fn default() -> Self { + Paths { + workdir: ".".to_string(), + pages: "src".to_string(), + output: "site".to_string(), + } + } +} + +impl Paths { + pub fn get_workdir(&self) -> PathBuf { + let mut current_dir = std::env::current_dir().unwrap(); + current_dir = current_dir.join(&self.workdir); + current_dir + .to_path_buf() + .canonicalize() + .unwrap_or(current_dir.to_path_buf()) + } + + pub fn get_page_path(&self) -> PathBuf { + let path = self.get_workdir().join(&self.pages); + path.canonicalize().unwrap_or(path) + } + + pub fn get_output_path(&self) -> PathBuf { + let path = self.get_workdir().join(&self.output); + path.canonicalize().unwrap_or(path) + } + + pub fn get_absolute_filepath(&self, file: &PathBuf) -> PathBuf { + let path = self.get_workdir().join(file); + path.canonicalize().unwrap_or(path) + } + + pub fn generate_output_path(&self, file: &PathBuf) -> PathBuf { + let relative_path = file + .strip_prefix(&self.get_page_path()) + .unwrap_or(file) + .strip_prefix(&self.get_workdir()) + .unwrap_or(file); + + let relative_parent = relative_path.parent().unwrap_or(Path::new("")); + let file_stem = relative_path.file_stem().unwrap_or_default(); + + match file.extension().and_then(|ext| ext.to_str()) { + Some("ds") => { + if file_stem == "index" { + relative_parent.join("index.html") + } else { + relative_parent.join(file_stem).join("index.html") + } + } + Some(ext) => relative_parent.join(format!("{}.{}", file_stem.to_str().unwrap(), ext)), + None => relative_path.to_path_buf(), + } + } +} diff --git a/src/context/file.rs b/src/context/file.rs new file mode 100644 index 0000000..241a1fe --- /dev/null +++ b/src/context/file.rs @@ -0,0 +1,72 @@ +use std::{fs::read_to_string, path::PathBuf}; + +use crate::ast2::statement::Result; +use crate::ast2::{builtin, Build, Environment}; + +use crate::{ + ast2::{Expression, Statement}, + context::parser, +}; + +use super::Context; + +pub struct File { + pub is_page: bool, + + pub src: PathBuf, + pub meta: Option, + pub ast: Vec, +} + +impl File { + pub fn new(ctx: Context, location: &PathBuf) -> Self { + let parser = ctx.parser.borrow(); + let content = read_to_string(&location); + + let content = if let Ok(content) = content { + content + } else { + panic!("Failed to read file: {}", location.to_str().unwrap()); + }; + + let result = parser.parse(content.as_str()); + if let Ok((meta, ast)) = result { + File { + is_page: false, + + src: location.to_path_buf(), + meta, + ast, + } + } else { + parser::error_message(location, result.err().unwrap(), &content); + } + } + + pub fn build(&self, ctx: &Context) -> String { + let mut environment = if let Some(meta) = &self.meta { + let meta = meta(ctx, &Environment::new()); + let mut env = Environment::new(); + env.set("meta", meta.as_typevalue()); + env + } else { + Environment::new() + }; + + builtin::populate(&mut environment); + + for ast in &self.ast { + let result = ast(ctx, &environment); + match result { + Result::Return(result) => { + let value = result.build(ctx, &environment); + return value; + } + _ => { + continue; + } + } + } + return "".to_string(); + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index 8dc092a..9b618d0 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -1,111 +1,30 @@ -use std::{cell::RefCell, fs, path::Path, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; +mod config; +use config::Config; +pub mod resource; +use resource::ResourceList; +pub mod parser; +mod resolver; +use crate::grammar::DaisyParser; +pub use resolver::load_dir; -use crate::{grammar::DaisyParser, resolver::resource::Resource}; -use log::warn; -use serde::Deserialize; +mod file; +const EXTENSION: &str = "ds"; + +#[derive(Clone)] pub struct Context { - pub parser: DaisyParser, - pub resources: Vec>>, + pub parser: Rc>, + pub resources: ResourceList, pub config: Config, } -#[derive(Deserialize)] -#[serde(default)] -pub struct Config { - pub paths: Paths, - pub assets: Assets, - pub pretty: bool, -} - -#[derive(Deserialize)] -#[serde(default)] -pub struct Paths { - pub workdir: String, - pub pages: String, - pub output: String, -} - -#[derive(Deserialize)] -#[serde(default)] -pub struct Assets { - pub folder: String, -} - -impl Default for Config { - fn default() -> Self { - Config { - paths: Paths::default(), - assets: Assets::default(), - pretty: false, - } - } -} - -impl Default for Paths { - fn default() -> Self { - Paths { - workdir: ".".to_string(), - pages: "src".to_string(), - output: "site".to_string(), - } - } -} - -impl Default for Assets { - fn default() -> Self { - Assets { - folder: "assets".to_string(), - } - } -} - -const DAISY_CONFIG: &str = "daisy.toml"; - impl Context { - pub fn load_config() -> Self { - let config_str = std::fs::read_to_string(DAISY_CONFIG).unwrap_or_else(|_| { - warn!("{} not found, using default config", DAISY_CONFIG); - "".to_string() - }); - - let mut cfg: Config = toml::from_str(&config_str).unwrap(); - - let absolute_src = std::fs::canonicalize(&cfg.paths.workdir).unwrap_or_else(|_| { - panic!("{} not found, using default src", cfg.paths.workdir); - }); - cfg.paths.workdir = absolute_src.to_str().unwrap().to_string(); - + pub fn new() -> Self { Context { - parser: DaisyParser::new(), - resources: vec![], - config: cfg, + parser: Rc::new(RefCell::new(DaisyParser::new())), + resources: HashMap::new(), + config: Config::new(), } } - - pub fn get_output_path(&self) -> String { - format!( - "{}/{}/", - self.config.paths.workdir, self.config.paths.output - ) - } - - pub fn get_page_path(&self) -> String { - format!("{}/{}/", self.config.paths.workdir, self.config.paths.pages) - } - - pub fn save_content(&self, path: &str, content: &str) -> String { - let output_path = Path::new(path); - - fs::create_dir_all(output_path.parent().unwrap()).unwrap_or_else(|err| { - panic!("Failed to create directory: {}: {}", path, err); - }); - - fs::write(&output_path, content).unwrap_or_else(|err| { - println!("{}", content); - panic!("Failed to write file: {}: {}", path, err); - }); - - output_path.to_str().unwrap().to_string() - } } diff --git a/src/context/parser.rs b/src/context/parser.rs new file mode 100644 index 0000000..b49ea81 --- /dev/null +++ b/src/context/parser.rs @@ -0,0 +1,73 @@ +use std::path::Path; + +use crate::grammar::Token; +use lalrpop_util::ParseError; + +pub fn error_message(src: &Path, err: ParseError, content: &str) -> ! { + match err { + ParseError::InvalidToken { location } => { + let (line, column) = position_to_line_column(&content, location); + panic!( + "Invalid token at {}:{} in file {}", + line, + column, + src.display() + ); + } + ParseError::UnrecognizedEof { location, expected } => { + let (line, column) = position_to_line_column(&content, location); + panic!( + "Unrecognized EOF at {}:{} in file {}. Expected: {:?}", + line, + column, + src.display(), + expected + ); + } + ParseError::UnrecognizedToken { + token: (location, token, _), + expected, + } => { + let (line, column) = position_to_line_column(&content, location); + panic!( + "Unrecognized token '{}' at {}:{} in file {}. Expected: {:?}", + token, + line, + column, + src.display(), + expected + ); + } + ParseError::ExtraToken { token } => { + let (line, column) = position_to_line_column(&content, token.0); + panic!( + "Extra token '{}' at {}:{} in file {}", + token.1, + line, + column, + src.display(), + ); + } + ParseError::User { error } => { + panic!("User error: {} in file {}", error, src.display()); + } + } +} + +fn position_to_line_column(input: &str, pos: usize) -> (usize, usize) { + let mut line = 1; + let mut last_line_start = 0; + + for (i, c) in input.char_indices() { + if i >= pos { + break; + } + if c == '\n' { + line += 1; + last_line_start = i + 1; + } + } + + let column = pos - last_line_start + 1; + (line, column) +} diff --git a/src/context/resolver.rs b/src/context/resolver.rs new file mode 100644 index 0000000..886556a --- /dev/null +++ b/src/context/resolver.rs @@ -0,0 +1,28 @@ +use walkdir::WalkDir; + +use super::resource::ResourceListExt; +use super::{resource::Resource, Context, EXTENSION}; + +pub fn load_dir(ctx: &mut Context) { + let config = &ctx.config; + WalkDir::new(config.paths.get_page_path()) + .into_iter() + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry.file_type().is_file() && entry.path().extension() == Some(EXTENSION.as_ref()) + }) + .for_each(|entry| { + let path = entry.path(); + let resource = ctx.resources.load(ctx.clone(), path.to_path_buf()); + let mut resource = resource.borrow_mut(); + + if let Resource::File(file) = &mut *resource { + file.is_page = true; + } else { + panic!( + "Expected a File resource, got {}", + entry.path().to_str().unwrap() + ); + } + }); +} diff --git a/src/context/resource.rs b/src/context/resource.rs new file mode 100644 index 0000000..07d8919 --- /dev/null +++ b/src/context/resource.rs @@ -0,0 +1,109 @@ +use std::{ + cell::RefCell, + collections::HashMap, + hash::{DefaultHasher, Hash, Hasher}, + path::PathBuf, + rc::Rc, +}; + +use super::{file::File, Context}; + +pub enum Resource { + File(File), + Styling(String, String, String), + Other(String, String), +} + +pub type ResourceList = HashMap>>; + +pub trait ResourceListExt { + fn load(&mut self, ctx: Context, location: PathBuf) -> Rc>; + fn get_pages(&self) -> Vec>>; + fn get(&self) -> Vec>>; +} + +impl ResourceListExt for ResourceList { + fn load(&mut self, ctx: Context, location: PathBuf) -> Rc> { + let absolute_path = ctx.config.paths.get_absolute_filepath(&location); + if self.contains_key(absolute_path.to_str().unwrap()) { + return self.get(absolute_path.to_str().unwrap()).unwrap().clone(); + } + + let build_location = ctx.config.paths.generate_output_path(&absolute_path); + + let resource = match &location.extension().and_then(|ext| ext.to_str()) { + Some("ds") => Rc::new(RefCell::new(Resource::File(File::new( + ctx.clone(), + &location, + )))), + Some("scss") | Some("sass") => { + let content = std::fs::read_to_string(&absolute_path) + .map_err(|_| { + format!( + "Failed to read SCSS file: {}", + absolute_path.to_str().unwrap() + ) + }) + .unwrap(); + let css = grass::from_string(content.clone(), &grass::Options::default()) + .map_err(|err| { + format!( + "Failed to compile SCSS file: {}: {}", + absolute_path.to_str().unwrap(), + err + ) + }) + .unwrap(); + let name = location.file_stem().unwrap().to_str().unwrap(); + let mut hasher = DefaultHasher::new(); + content.hash(&mut hasher); + let hash = hasher.finish(); + let name = format!("{}-{}.css", name, hash); + + Rc::new(RefCell::new(Resource::Styling( + absolute_path.to_str().unwrap().to_string(), + format!( + "{}/{}", + build_location.parent().unwrap().to_str().unwrap(), + name + ), + css.to_string(), + ))) + } + Some(_) => Rc::new(RefCell::new(Resource::Other( + absolute_path.to_str().unwrap().to_string(), + build_location.to_str().unwrap().to_string(), + ))), + None => { + if location.with_extension("ds").exists() { + self.load(ctx, location.with_extension("ds")) + } else { + Rc::new(RefCell::new(Resource::Other( + absolute_path.to_str().unwrap().to_string(), + build_location.to_str().unwrap().to_string(), + ))) + } + } + }; + + self.insert( + absolute_path.to_str().unwrap().to_string(), + resource.clone(), + ); + resource + } + + fn get_pages(&self) -> Vec>> { + self.values() + .filter(|res| match &*res.borrow() { + Resource::File(file) => file.is_page, + _ => false, + }) + .cloned() + .collect() + } + + fn get(&self) -> Vec>> { + self.values().cloned().collect() + } +} diff --git a/src/grammar.lalrpop b/src/grammar.lalrpop index bbf2b5f..61db209 100644 --- a/src/grammar.lalrpop +++ b/src/grammar.lalrpop @@ -1,10 +1,10 @@ use std::str::FromStr; -use crate::ast::environment::{Type, Value}; -use crate::ast::{expression, expression::Expression}; -use crate::ast::{statement, statement::Statement}; -use crate::ast::{node, node::Node}; -use crate::ast::function::default_function; -use crate::ast::strings::{parse_string, parse_multiline_string}; +use std::sync::Arc; +use crate::ast2::environment::{Type, Value}; +use crate::ast2::{expression, expression::Expression}; +use crate::ast2::{statement, statement::Statement}; +use crate::ast2::{node, node::Node, node::Html}; +use crate::parser::{parse_string, parse_multiline_string}; grammar; @@ -38,11 +38,11 @@ KeyValueNil = "nil"; KeyTypeString = "str"; KeyTypeNumber = "num"; KeyTypeFloat = "float"; -KeyTypeBool = "bool"; -KeyTypeElement = "element"; +KeyTypeBoolean = "bool"; +KeyTypeHtml = "html"; KeyTypeFunction = "func"; KeyTypeMap = "map"; -KeyTypeArray = "list"; +KeyTypeList = "list"; Keyword = { KeyIf, @@ -62,16 +62,17 @@ Keyword = { KeyTypeString, KeyTypeNumber, KeyTypeFloat, - KeyTypeBool, - KeyTypeElement, + KeyTypeBoolean, + KeyTypeHtml, KeyTypeFunction, KeyTypeMap, - KeyTypeArray, + KeyTypeList, }; KeyVariableName = { KeyIdentifier, KeyMeta, + KeyTypeHtml, } // types @@ -79,21 +80,21 @@ KeyVariableName = { TypeString: Type = KeyTypeString => Type::String; TypeNumber: Type = KeyTypeNumber => Type::Number; TypeFloat: Type = KeyTypeFloat => Type::Float; -TypeBool: Type = KeyTypeBool => Type::Bool; -TypeElement: Type = KeyTypeElement => Type::Element; +TypeBoolean: Type = KeyTypeBoolean => Type::Boolean; +TypeHtml: Type = KeyTypeHtml => Type::Html; TypeFunction: Type = KeyTypeFunction => Type::Function; TypeMap: Type = KeyTypeMap => Type::Map; -TypeArray: Type = KeyTypeArray => Type::Array; +TypeList: Type = KeyTypeList => Type::List; Type: Type = { TypeString, TypeNumber, TypeFloat, - TypeBool, - TypeElement, + TypeBoolean, + TypeHtml, TypeFunction, TypeMap, - TypeArray, + TypeList, }; // Operations @@ -102,10 +103,10 @@ OpEqual = "=="; OpNotEqual = "!="; OpOr = "||"; OpAnd = "&&"; -OpLessThan = "<"; -OpLessThanOrEqual = "<="; -OpGreaterThan = ">"; -OpGreaterThanOrEqual = ">="; +OpLesser = "<"; +OpLesserEqual = "<="; +OpGreater = ">"; +OpGreaterEqual = ">="; OpAdd = "+"; OpSubtract = "-"; @@ -153,7 +154,7 @@ RawValueString: String = { }; RawValueNumber: i64 = => i64::from_str(s).unwrap(); RawValueFloat: f64 = => f64::from_str(s).unwrap(); -RawValueBool: bool = { +RawValueBoolean: bool = { KeyValueTrue => true, KeyValueFalse => false, }; @@ -161,19 +162,19 @@ RawValueBool: bool = { ValueString: Value = => Value::String(s); ValueNumber: Value = => Value::Number(n); ValueFloat: Value = => Value::Float(f); -ValueBool: Value = => Value::Bool(b); +ValueBoolean: Value = => Value::Boolean(b); ValueNil: Value = KeyValueNil => Value::Nil; -ValueFunction: Value = => Value::Function(default_function, function.0.into(), function.1, function.2.into()); -ValueElement: Value = ":" => Value::Element(element.into()); +ValueFunction: Value = => Value::Function(Arc::new((function.0.into(), function.1, function.2.into())), None); +ValueHtml: Value = => Value::Html(html, None); Value: Value = { ValueString, ValueNumber, ValueFloat, - ValueBool, + ValueBoolean, ValueNil, ValueFunction, - ValueElement, + ValueHtml, }; @@ -184,10 +185,10 @@ Expression: Expression = { (OpNotEqual) => expression::notequal(left.into(), right.into()), (OpOr) => expression::or(left.into(), right.into()), (OpAnd) => expression::and(left.into(), right.into()), - (OpLessThan) => expression::lessthan(left.into(), right.into()), - (OpLessThanOrEqual) => expression::lessthanorequal(left.into(), right.into()), - (OpGreaterThan) => expression::greaterthan(left.into(), right.into()), - (OpGreaterThanOrEqual) => expression::greaterthanorequal(left.into(), right.into()), + (OpLesser) => expression::lesser(left.into(), right.into()), + (OpLesserEqual) => expression::lesser_equal(left.into(), right.into()), + (OpGreater) => expression::greater(left.into(), right.into()), + (OpGreaterEqual) => expression::greater_equal(left.into(), right.into()), ExpressionCall, ExpressionScopeEntry, @@ -195,15 +196,15 @@ Expression: Expression = { }; ExpressionCalculus: Expression = { - (OpAdd) => expression::addition(left.into(), right.into()), - (OpSubtract) => expression::subtraction(left.into(), right.into()), + (OpAdd) => expression::add(left, right), + (OpSubtract) => expression::subtract(left, right), ExpressionFactor, }; ExpressionFactor: Expression = { - (OpMultiply) => expression::multiplication(left.into(), right.into()), - (OpDivide) => expression::division(left.into(), right.into()), + (OpMultiply) => expression::multiply(left, right), + (OpDivide) => expression::divide(left, right), ExpressionTerm, }; @@ -212,20 +213,20 @@ ExpressionTerm: Expression = { "(" ")", ExpressionValue, ExpressionMap, - ExpressionArray, + ExpressionList, ExpressionIdentifier, ExpressionScript, }; ExpressionValue: Expression = => expression::value(value); ExpressionMap: Expression = "{" "}" => expression::map(definitions); -ExpressionArray: Expression = "[" ",")*> "]" => { +ExpressionList: Expression = "[" ",")*> "]" => { if let Some(last) = last { entries.push(last); } - expression::array(entries) + expression::list(entries) }; -ExpressionScopeEntry: Expression = "[" "]" => expression::scope_entry(scope.into(), entry.into()); +ExpressionScopeEntry: Expression = "[" "]" => expression::object_entry(scope.into(), entry.into()); ExpressionIdentifier: Expression = )*> => expression::identifier({ let mut location = vec![location.to_string()]; location.append(&mut subsequent.iter().map(|s| s.to_string()).collect()); @@ -243,19 +244,19 @@ ExpressionScript: Expression = "$" => expression::s // statements StatementCollect: Statement = (KeyCollect) => statement::collect(expression.into()); -StatementBreak: Statement = KeyBreak => statement::break_statement(); -StatementContinue: Statement = KeyContinue => statement::continue_statement(); +StatementBreak: Statement = KeyBreak => statement::break_s(); +StatementContinue: Statement = KeyContinue => statement::continue_s(); StatementReturn: Statement = { - (KeyReturn) => statement::return_statement(Some(expression)), - (KeyReturn) ";" => statement::return_statement(None), + (KeyReturn) => statement::return_s(Some(expression)), + (KeyReturn) ";" => statement::return_s(None), }; -StatementIf: Statement = (KeyIf) "{" "}" => statement::if_statement(condition.into(), body); +StatementIf: Statement = (KeyIf) "{" "}" => statement::if_s(condition.into(), body); StatementIter: Statement = { - (KeyFor) "in" "{" "}" => statement::iter_statement((key.into(), None), collection, body), - (KeyFor) "," "in" "{" "}" => statement::iter_statement((key.into(), Some(value.into())), collection, body), + (KeyFor) "in" "{" "}" => statement::iter((key.into(), None), collection, body), + (KeyFor) "," "in" "{" "}" => statement::iter((key.into(), Some(value.into())), collection, body), }; -StatementFor: Statement = (KeyFor) ";" ";" "{" "}" => statement::for_statement(init, condition, increment, body); +StatementFor: Statement = (KeyFor) ";" ";" "{" "}" => statement::for_s(init, condition, increment, body); StatementDefinition: Statement = => statement::define(definition.0, definition.1, definition.2); StatementAssign: Statement = "=" => statement::assign(identifier.into(), expression.into()); StatementCall: Statement = "(" ",")*> ")" => { @@ -279,10 +280,14 @@ Statement: Statement = { // html -NodeElement: Node = { - "{" "}" => node::element(identifier.into(), attributes, children), - ">" => node::element(identifier.into(), attributes, vec![child]), - ";" => node::element(identifier.into(), attributes, vec![]), +NodeHtml: Node = { + ":" => node_body, +}; + +NodeBody: Node = { + "{" "}" => node::html(Html::new(identifier.into()).with_attributes(attributes).with_children(children)), + ">" => node::html(Html::new(identifier.into()).with_attributes(attributes).with_children(vec![child])), + ";" => node::html(Html::new(identifier.into()).with_attributes(attributes)), }; NodeAttribute: (String, Vec) = { @@ -295,14 +300,14 @@ NodeAttribute: (String, Vec) = { NodeText: Node = => node::text(string); -NodeInsert: Node = "@" "{" "}" => node::insert(expression.into()); +NodeInsert: Node = "@" "{" "}" => node::insertion(expression.into()); Node: Node = { => node::logic_statement(for_loop), => node::logic_statement(iter_loop), - => node::logic_statement(if_statement), + => node::logic_statement(if_s), - NodeElement, + NodeBody, NodeInsert, NodeText, } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9ff2ccf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,27 @@ +#[macro_export] +macro_rules! inherit { + ($name:ident) => { + pub mod $name; + pub use $name::$name; + }; +} + +#[macro_export] +macro_rules! inherits { + ($name:ident, [$($inherit:ident),+]) => { + pub mod $name; + $( + pub use $name::$inherit; + )+ + }; +} + +#[macro_export] +macro_rules! define_function { + ($environment:ident, $name:expr, $function:expr) => { + $environment.set( + $name, + Value::Function(Arc::new($function), None).as_typevalue(), + ); + }; +} diff --git a/src/main.rs b/src/main.rs index 09a5933..70bbd1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,19 @@ use env_logger::Env; use lalrpop_util::lalrpop_mod; -mod ast; +// mod ast; +mod ast2; mod cli; mod context; -mod resolver; +mod parser; +mod prelude; lalrpop_mod!(grammar); fn main() { let env = Env::default().filter_or("DAISY_LOG", "trace"); env_logger::init_from_env(env); - let mut ctx = context::Context::load_config(); + let mut ctx = context::Context::new(); cli::run(&mut ctx); } diff --git a/src/ast/strings.rs b/src/parser/mod.rs similarity index 100% rename from src/ast/strings.rs rename to src/parser/mod.rs diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..cce3fd5 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,3 @@ +pub use daisy::define_function; +pub use daisy::inherit; +pub use daisy::inherits; diff --git a/src/resolver/file.rs b/src/resolver/file.rs index b23326c..06218c7 100644 --- a/src/resolver/file.rs +++ b/src/resolver/file.rs @@ -21,12 +21,13 @@ pub struct File { } impl File { - pub fn load_absolute>(ctx: &mut Context, src: P) -> File { + pub fn load_absolute>(ctx: &Context, src: P) -> File { let content = fs::read_to_string(&src).unwrap_or_else(|_| { panic!("Failed to read file: {:?}", src.as_ref()); }); let ast = ctx .parser + .borrow() .parse(content.as_str()) .unwrap_or_else(|err| Self::error_message(src.as_ref(), err, &content)); diff --git a/src/resolver/mod.rs b/src/resolver/mod.rs index 8523e3b..f2f7d4c 100644 --- a/src/resolver/mod.rs +++ b/src/resolver/mod.rs @@ -13,39 +13,41 @@ use crate::context::Context; pub mod file; pub mod resource; -pub fn load_dir(ctx: &mut Context) { - WalkDir::new(format!( - "{}/{}", - ctx.config.paths.workdir, ctx.config.paths.pages - )) - .into_iter() - .filter_map(|entry| entry.ok()) - .filter(|entry| entry.file_type().is_file() && entry.path().extension() == Some("ds".as_ref())) - .for_each(|entry| { - let path = entry.path(); - let file = get_file(ctx, path.to_str().unwrap().to_string()).unwrap_or_else(|err| { - panic!("Failed to load file {}: {}", path.display(), err); - }); +pub fn load_dir(ctx: Context) { + let config = ctx.config.borrow(); + WalkDir::new(format!("{}/{}", config.paths.workdir, config.paths.pages)) + .into_iter() + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry.file_type().is_file() && entry.path().extension() == Some("ds".as_ref()) + }) + .for_each(|entry| { + let path = entry.path(); + let file = + get_file(ctx.clone(), path.to_str().unwrap().to_string()).unwrap_or_else(|err| { + panic!("Failed to load file {}: {}", path.display(), err); + }); - let mut resource = file.borrow_mut(); + let mut resource = file.borrow_mut(); - if let Resource::File(file) = &mut *resource { - file.is_page = true; - } else { - panic!( - "Expected a File resource, got {}", - entry.path().to_str().unwrap() - ); - } - }); + if let Resource::File(file) = &mut *resource { + file.is_page = true; + } else { + panic!( + "Expected a File resource, got {}", + entry.path().to_str().unwrap() + ); + } + }); } pub fn get_all(ctx: &mut Context) -> Vec>> { ctx.resources.iter().cloned().collect() } -pub fn get_file(ctx: &mut Context, src: String) -> Result>, String> { - let src = Path::new(ctx.config.paths.workdir.as_str()).join(src); +pub fn get_file(ctx: &Context, src: String) -> Result>, String> { + let config = ctx.config.borrow(); + let src = Path::new(config.paths.workdir.as_str()).join(src); if let Some(rs) = ctx.resources.iter().find(|rs| match &*rs.borrow() { Resource::File(file) => file.src == src, Resource::SCSS(src_file, _, _) => src_file == src.to_str().unwrap(), @@ -55,10 +57,11 @@ pub fn get_file(ctx: &mut Context, src: String) -> Result>, } else { match src.extension().and_then(|ext| ext.to_str()) { Some("ds") => { - let file = file::File::load_absolute(ctx, src.to_str().unwrap()); + let file = file::File::load_absolute(&ctx, src.to_str().unwrap()); let rc = Rc::new(RefCell::new(Resource::File(file))); - ctx.resources.push(rc.clone()); + let mut resources = ctx.resources.borrow_mut(); + resources.push(rc.clone()); Ok(rc) } Some("scss") => { @@ -73,7 +76,7 @@ pub fn get_file(ctx: &mut Context, src: String) -> Result>, content.hash(&mut hasher); let hash = hasher.finish(); let path = - Resource::get_output_path(ctx, format!("{}-{}.css", name, hash).as_str()) + Resource::get_output_path(&ctx, format!("{}-{}.css", name, hash).as_str()) .unwrap(); let rc = Rc::new(RefCell::new(Resource::SCSS( @@ -87,13 +90,13 @@ pub fn get_file(ctx: &mut Context, src: String) -> Result>, _ => { let with_ext = src.with_extension("ds"); if with_ext.exists() { - get_file(ctx, with_ext.to_str().unwrap().to_string()) + get_file(&ctx, with_ext.to_str().unwrap().to_string()) } else { let relative_path = - Resource::get_relative_path_from_root(ctx, src.to_str().unwrap()) + Resource::get_relative_path_from_root(&ctx, src.to_str().unwrap()) .map_err(|err| format!("Failed to get relative path: {}", err))?; - let mut output = Resource::get_output_path(ctx, &relative_path) + let mut output = Resource::get_output_path(&ctx, &relative_path) .map_err(|err| format!("Failed to get output path: {}", err))?; if src.extension().is_none() { diff --git a/src/resolver/resource.rs b/src/resolver/resource.rs index 43447e5..60cd0cb 100644 --- a/src/resolver/resource.rs +++ b/src/resolver/resource.rs @@ -14,7 +14,7 @@ pub enum Resource { } impl Resource { - pub fn get_output_path(ctx: &mut Context, src: &str) -> Result { + pub fn get_output_path(ctx: &Context, src: &str) -> Result { let mut path = Path::new(src); path = path.strip_prefix(ctx.get_page_path()).unwrap_or(path); @@ -52,8 +52,9 @@ impl Resource { } } - pub fn get_relative_path_from_root(ctx: &mut Context, src: &str) -> Result { - if let Some(relative_path) = src.strip_prefix(&ctx.config.paths.workdir) { + pub fn get_relative_path_from_root(ctx: &Context, src: &str) -> Result { + let config = ctx.config.borrow(); + if let Some(relative_path) = src.strip_prefix(&config.paths.workdir) { return Ok(format!("/{}", relative_path)); } else { panic!("Failed to strip workdir from path: {}", src);