diff --git a/Cargo.lock b/Cargo.lock index fff075e..4ff50ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,6 +618,7 @@ dependencies = [ "owo-colors", "petgraph", "rowan", + "strip-ansi-escapes", ] [[package]] @@ -1069,6 +1070,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1181,6 +1191,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index de54f2b..c7a2659 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -19,6 +19,7 @@ indoc = "2" dashmap = "5.5.3" crossbeam = "0.8.4" owo-colors = {version = "4", features = ["supports-colors"]} +strip-ansi-escapes = "0.2.0" [lints] workspace = true diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs new file mode 100644 index 0000000..42d160c --- /dev/null +++ b/crates/lang/src/ast.rs @@ -0,0 +1,78 @@ +use crate::lst_parser::syntax_kind::SyntaxKind::*; +use crate::SyntaxNode; +use rowan::Language; +macro_rules! ast_nodes { + ($($ast:ident, $kind:ident);+) => { + $( + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + #[repr(transparent)] + pub struct $ast(SyntaxNode); + impl rowan::ast::AstNode for $ast { + type Language = crate::Lang; + + fn can_cast(kind: ::Kind) -> bool { + kind == $kind + } + + fn cast(node: SyntaxNode) -> Option { + if node.kind() == $kind { + Some(Self(node)) + } else { + None + } + } + + fn syntax(&self) -> &SyntaxNode { + &self.0 + } + } + )+ + }; +} + +ast_nodes!( + Def, DEF; + DefName, DEF_NAME; + DefBody, DEF_BODY; + + Mod, MODULE; + ModName, MODULE_NAME; + ModBody, MODULE_BODY; + + Use, USE; + UsePat, USE_PAT; + PatItem, PAT_ITEM; + PatGlob, PAT_GLOB; + PatGroup, PAT_GROUP; + + Literal, LITERAL; + IntLit, INT_NUM; + FloatLit, FLOAT_NUM; + StringLit, STRING; + + Matrix, MATRIX; + MatrixRow, MAT_ROW; + Vector, VEC; + List, LIST; + CollectionItem, COLLECTION_ITEM; + + ParenthesizedExpr, PARENTHESIZED_EXPR; + Expression, EXPR; + + Pipeline, PIPELINE; + + Instruction, INSTR; + InstructionName, INSTR_NAME; + InstructionParams, INSTR_PARAMS; + + AttributeSet, ATTR_SET; + Attribute, ATTR; + AttributeName, ATTR_NAME; + AttributeValue, ATTR_VALUE; + + ParseError, PARSE_ERR; + LexError, LEX_ERR; + + Root, ROOT; + Eof, EOF +); diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index a45e202..f48385e 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -2,7 +2,9 @@ use crate::lst_parser::syntax_kind::SyntaxKind; +pub mod ast; pub mod lst_parser; +pub mod world; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Lang {} diff --git a/crates/lang/src/lst_parser/grammar.rs b/crates/lang/src/lst_parser/grammar.rs index 2db0824..001050e 100644 --- a/crates/lang/src/lst_parser/grammar.rs +++ b/crates/lang/src/lst_parser/grammar.rs @@ -33,6 +33,6 @@ fn check_parser(input: &str, parser_fn: fn(&mut Parser), output: &str) { let p_out = dbg!(parser.finish()); let o = Output::from_parser_output(toks, p_out); - let s = format!("{o:?}"); + let s = strip_ansi_escapes::strip_str(format!("{o:?}")); assert_eq!(&s, output); } diff --git a/crates/lang/src/lst_parser/output.rs b/crates/lang/src/lst_parser/output.rs index ce96fb5..78433b1 100644 --- a/crates/lang/src/lst_parser/output.rs +++ b/crates/lang/src/lst_parser/output.rs @@ -28,6 +28,7 @@ impl std::fmt::Debug for Output { } const INDENT_STR: &str = " "; +/// colored argument currently broken fn debug_print_green_node( node: NodeOrToken<&GreenNodeData, &GreenTokenData>, f: &mut dyn std::fmt::Write, @@ -39,12 +40,6 @@ fn debug_print_green_node( f.write_str(INDENT_STR)?; } - if !colored { - owo_colors::set_override(false); - } else { - owo_colors::set_override(true); - } - let r = match node { NodeOrToken::Node(n) => { let kind = Lang::kind_from_raw(node.kind()); @@ -103,10 +98,6 @@ fn debug_print_green_node( } }; - if !colored { - owo_colors::unset_override(); - } - r } diff --git a/crates/lang/src/world.rs b/crates/lang/src/world.rs new file mode 100644 index 0000000..2ce9997 --- /dev/null +++ b/crates/lang/src/world.rs @@ -0,0 +1,14 @@ +use std::path::Path; + +mod error; +mod files; + +struct World; + +impl World { + pub fn new(entry_point: &Path) -> Result { + todo!() + } +} + +enum WorldCreationError {} diff --git a/crates/lang/src/world/error.rs b/crates/lang/src/world/error.rs new file mode 100644 index 0000000..1cebf8d --- /dev/null +++ b/crates/lang/src/world/error.rs @@ -0,0 +1,10 @@ +use std::path::PathBuf; + +use crate::{ast::ParseError, lst_parser::error::SyntaxError}; + +use super::files::{FileId, Loc, OpenFileError}; + +pub enum Error { + Syntax(Loc, SyntaxError), + OpenFileError(OpenFileError), +} diff --git a/crates/lang/src/world/files.rs b/crates/lang/src/world/files.rs new file mode 100644 index 0000000..0c112ad --- /dev/null +++ b/crates/lang/src/world/files.rs @@ -0,0 +1,56 @@ +use std::{ + collections::HashMap, + io, + path::{Path, PathBuf}, +}; + +mod loc; + +pub use loc::Loc; +use rowan::ast::AstNode; + +use crate::{ + ast::ParseError, + lst_parser::{self, error::SyntaxError, input, output::Output}, + world::{error::Error, files::source_file::SourceFile}, +}; + +struct Files { + inner: Vec, + path_to_id_map: HashMap, +} + +impl Files { + pub fn add_file(&mut self, path: &Path) -> Result<(FileId, Vec), OpenFileError> { + if !path.exists() { + return Err(OpenFileError::NotFound(path.to_owned())); + } + + let file_id = FileId(self.inner.len()); + let (source_file, errs) = match SourceFile::open(path) { + Ok((source_file, errs)) => { + let errs = errs + .into_iter() + .map(|(ptr, err)| Error::Syntax(Loc::from_ptr(ptr, file_id), err)) + .collect::>(); + (source_file, errs) + } + Err(e) => return Err(OpenFileError::IoError(path.to_path_buf(), e)), + }; + + self.inner.push(source_file); + self.path_to_id_map.insert(path.to_path_buf(), file_id); + + Ok((file_id, errs)) + } +} + +pub enum OpenFileError { + NotFound(PathBuf), + IoError(PathBuf, std::io::Error), +} + +#[derive(Copy, Clone, Debug)] +pub struct FileId(usize); + +mod source_file; diff --git a/crates/lang/src/world/files/loc.rs b/crates/lang/src/world/files/loc.rs new file mode 100644 index 0000000..fa865d8 --- /dev/null +++ b/crates/lang/src/world/files/loc.rs @@ -0,0 +1,29 @@ +use rowan::ast::{AstNode, AstPtr}; + +use crate::Lang; + +use super::FileId; + +#[derive(Clone)] +pub struct Loc> { + file: FileId, + syntax: AstPtr, +} + +impl> Loc { + pub fn new(node: N, file: FileId) -> Self { + Self::from_ptr(AstPtr::new(&node), file) + } + + pub fn from_ptr(ptr: AstPtr, file: FileId) -> Self { + Self { file, syntax: ptr } + } + + pub fn file(&self) -> FileId { + self.file + } + + pub fn syntax(&self) -> AstPtr { + self.syntax.clone() + } +} diff --git a/crates/lang/src/world/files/source_file.rs b/crates/lang/src/world/files/source_file.rs new file mode 100644 index 0000000..8ed8043 --- /dev/null +++ b/crates/lang/src/world/files/source_file.rs @@ -0,0 +1,113 @@ +use crate::lst_parser::{self, grammar, input, syntax_kind}; +use crate::SyntaxNode; + +use crate::lst_parser::output::Output; + +use crate::lst_parser::error::SyntaxError; + +use crate::ast::ParseError; + +use rowan::ast::{AstNode, AstPtr}; + +use std::path::Path; +use std::{fs, io}; + +use rowan::GreenNode; + +use std::path::PathBuf; + +pub(crate) struct SourceFile { + pub(crate) path: PathBuf, + pub(crate) lst: rowan::GreenNode, +} + +impl SourceFile { + pub(crate) fn open(p: &Path) -> io::Result<(Self, Vec<(AstPtr, SyntaxError)>)> { + assert!(p.exists()); + + let f = fs::read_to_string(p)?; + let (lst, errs) = Self::parse(f); + + Ok(( + Self { + path: p.to_path_buf(), + lst, + }, + errs, + )) + } + + pub(crate) fn parse(f: String) -> (GreenNode, Vec<(AstPtr, SyntaxError)>) { + let toks = syntax_kind::lex(&f); + let input = input::Input::new(&toks); + let mut parser = lst_parser::Parser::new(input); + + grammar::source_file(&mut parser); + + let p_out = parser.finish(); + let (lst, errs) = Output::from_parser_output(toks, p_out).dissolve(); + + (lst.clone(), Self::find_errs(lst, errs)) + } + + pub(crate) fn find_errs( + lst: GreenNode, + mut errs: Vec, + ) -> Vec<(AstPtr, SyntaxError)> { + let mut out = Vec::new(); + errs.reverse(); + + let lst = SyntaxNode::new_root(lst); + Self::find_errs_recursive(&mut out, lst, &mut errs); + + out + } + + pub(crate) fn find_errs_recursive( + mut out: &mut Vec<(AstPtr, SyntaxError)>, + lst: SyntaxNode, + mut errs: &mut Vec, + ) { + lst.children() + .filter_map(|c| ParseError::cast(c)) + .for_each(|e| out.push((AstPtr::new(&e), errs.pop().unwrap()))); + + lst.children() + .for_each(|c| Self::find_errs_recursive(out, c, errs)); + } +} + +#[cfg(test)] +mod tests { + use crate::world::files::source_file::SourceFile; + + fn check_find_errs(input: &str, expected: &[&str]) { + let (_, errs) = SourceFile::parse(input.to_string()); + + let errs = errs + .into_iter() + .map(|(loc, err)| format!("{:?}@{:?}", err, loc.syntax_node_ptr().text_range())) + .collect::>(); + + assert_eq!( + errs, + expected + .into_iter() + .map(|s| s.to_string()) + .collect::>() + ) + } + + #[test] + fn test_find_errs() { + check_find_errs( + "def meow = ;\n mod ;", + &["Expected([DEF_BODY])@11..11", "Expected([IDENT])@18..18"], + ); + + check_find_errs( + "def awawa = a |", + &["UnterminatedTopLevelItem@0..15", "PipelineNeedsSink@12..15"], + ) + } +} diff --git a/crates/svg-filters/src/main.rs b/crates/svg-filters/src/main.rs index 5ce0102..a063791 100644 --- a/crates/svg-filters/src/main.rs +++ b/crates/svg-filters/src/main.rs @@ -1,8 +1,65 @@ -use svg_filters::codegen::SvgDocument; +use svg_filters::{ + codegen::SvgDocument, + types::nodes::{ + primitives::{ + blend::BlendMode, + color_matrix::ColorMatrixType, + component_transfer::TransferFn, + displacement_map::Channel, + turbulence::{NoiseType, StitchTiles}, + }, + standard_input::StandardInput, + }, +}; fn main() { let mut doc = SvgDocument::new(); - eprintln!("{}", doc.generate_svg_pretty()); - println!("{}", doc.generate_svg()); + let f = doc.create_filter("cmyk-chromabb"); + + let noise = f.turbulence(0., 0.1, 2, 0, StitchTiles::Stitch, NoiseType::FractalNoise); + let noise = f.component_transfer_rgba( + noise, + TransferFn::Discrete { + table_values: vec![0., 0.2, 0.4, 0.6, 0.8, 1.], + }, + TransferFn::Discrete { + table_values: vec![0., 0.2, 0.4, 0.6, 0.8, 1.], + }, + TransferFn::Discrete { + table_values: vec![0., 0.2, 0.4, 0.6, 0.8, 1.], + }, + TransferFn::Linear { + slope: 0., + intercept: 0.5, + }, + ); + + let cyan = f.color_matrix( + StandardInput::SourceGraphic, + ColorMatrixType::Matrix(Box::new([ + 0., 0., 0., 0., 0., // + 0., 1., 0., 0., 0., // + 0., 0., 1., 0., 0., // + 0., 0., 0., 1., 0., + ])), + ); + let cyan = f.offset(cyan, 25., 0.); + let cyan = f.displacement_map(cyan, noise, 50., Channel::R, Channel::A); + + let magenta = f.color_matrix( + StandardInput::SourceGraphic, + ColorMatrixType::Matrix(Box::new([ + 1., 0., 0., 0., 0., // + 0., 0., 0., 0., 0., // + 0., 0., 1., 0., 0., // + 0., 0., 0., 1., 0., + ])), + ); + let magenta = f.displacement_map(magenta, noise, 50., Channel::R, Channel::A); + let magenta = f.offset(magenta, -25., 0.); + + f.blend(cyan, magenta, BlendMode::Screen); + + println!("{}", doc.generate_svg_pretty()); }