diff --git a/hornbeam_grammar/src/ast.rs b/hornbeam_grammar/src/ast.rs index beae484..1d58301 100644 --- a/hornbeam_grammar/src/ast.rs +++ b/hornbeam_grammar/src/ast.rs @@ -1,4 +1,4 @@ -use crate::IStr; +use crate::{IStr, Locator}; use serde::Serialize; use std::collections::BTreeMap; @@ -24,6 +24,7 @@ pub struct HtmlElement { pub classes: Vec, pub dom_id: Option, pub attributes: BTreeMap, + pub loc: Locator, } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] @@ -31,6 +32,7 @@ pub struct ComponentElement { pub name: IStr, pub slots: BTreeMap>, pub attributes: BTreeMap, + pub loc: Locator, } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] @@ -38,18 +40,21 @@ pub struct IfBlock { pub condition: Expression, pub blocks: Vec, pub else_blocks: Vec, + pub loc: Locator, } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct DefineExpandSlot { pub name: IStr, pub optional: bool, + pub loc: Locator, } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct DefineFragment { pub name: IStr, pub blocks: Vec, + pub loc: Locator, } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] @@ -128,19 +133,23 @@ pub enum Expression { FieldLookup { obj: Box, ident: IStr, + loc: Locator, }, MethodCall { obj: Box, ident: IStr, args: Vec, + loc: Locator, }, // Other Primaries Variable { name: IStr, + loc: Locator, }, FunctionCall { name: IStr, args: Vec, + loc: Locator, }, } diff --git a/hornbeam_grammar/src/lib.rs b/hornbeam_grammar/src/lib.rs index c35397f..0ca6034 100644 --- a/hornbeam_grammar/src/lib.rs +++ b/hornbeam_grammar/src/lib.rs @@ -1,8 +1,10 @@ pub mod ast; mod parser; +use serde::Serialize; use arc_interner::ArcIntern; pub use parser::parse_template; +use pest::Span; use parser::Rule; pub type ParseError = pest::error::Error; @@ -12,3 +14,21 @@ pub type IStr = ArcIntern; pub fn intern(s: impl Into) -> ArcIntern { ArcIntern::new(s.into()) } + +#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize)] +pub struct Locator { + pub filename: IStr, + pub line: u16, + pub column: u16, +} + +impl Locator { + pub(crate) fn from_span(span: Span, file: IStr) -> Locator { + let (line, col) = span.start_pos().line_col(); + Locator { + filename: file, + line: line.min(u16::MAX as usize) as u16, + column: col.min(u16::MAX as usize) as u16, + } + } +} diff --git a/hornbeam_grammar/src/parser.rs b/hornbeam_grammar/src/parser.rs index 290316c..267d1a2 100644 --- a/hornbeam_grammar/src/parser.rs +++ b/hornbeam_grammar/src/parser.rs @@ -4,7 +4,7 @@ use crate::ast::{ Block, ComponentElement, DefineExpandSlot, DefineFragment, Expression, HtmlElement, IfBlock, StringExpr, StringPiece, Template, }; -use crate::{intern, IStr}; +use crate::{intern, IStr, Locator}; use lazy_static::lazy_static; use pest::error::ErrorVariant; use pest::pratt_parser::{Assoc, Op, PrattParser}; @@ -15,12 +15,21 @@ use std::fmt::Debug; use std::hash::Hash; type PCResult = Result>; -type Node<'i> = pest_consume::Node<'i, Rule, ()>; +type Node<'i> = pest_consume::Node<'i, Rule, ParserUserData>; #[derive(Parser)] #[grammar = "hornbeam.pest"] struct HornbeamParser; +#[derive(Clone, Debug)] +struct ParserUserData { + pub(crate) file: IStr, +} + +fn nodeloc(node: &Node) -> Locator { + Locator::from_span(node.as_span(), node.user_data().file.clone()) +} + fn error(msg: &str, span: Span) -> PCError { PCError::new_from_span( ErrorVariant::CustomError { @@ -51,7 +60,8 @@ impl HornbeamParser { } fn Element(input: Node) -> PCResult { - let loc = input.as_span(); + let span = input.as_span(); + let loc = nodeloc(&input); let mut children = input.into_children(); let name = HornbeamParser::ElementName(children.next().unwrap())?; @@ -86,7 +96,7 @@ impl HornbeamParser { Ok(if name.chars().next().unwrap().is_ascii_lowercase() { if !supply_slots.is_empty() { - return Err(error("You can't supply slots to HTML elements.", loc)); + return Err(error("You can't supply slots to HTML elements.", span)); } Block::HtmlElement(HtmlElement { @@ -95,6 +105,7 @@ impl HornbeamParser { classes, dom_id, attributes, + loc, }) } else { if !supply_slots.is_empty() { @@ -106,6 +117,7 @@ impl HornbeamParser { name, slots, attributes, + loc, }) } else { let mut slots = BTreeMap::new(); @@ -114,22 +126,25 @@ impl HornbeamParser { name, slots, attributes, + loc, }) } }) } fn DefineFragment(input: Node) -> PCResult { + let loc = nodeloc(&input); Ok(match_nodes!(input.into_children(); [Identifier(name), blocks..] => { Block::DefineFragment(DefineFragment { - name, blocks: HornbeamParser::helper_blocks(blocks)? + name, blocks: HornbeamParser::helper_blocks(blocks)?, loc }) } )) } fn DefineExpandSlot(input: Node) -> PCResult { + let loc = nodeloc(&input); let (optional, name) = match_nodes!(input.into_children(); [SlotOptional(_), Identifier(name)] => { (true, name) @@ -138,7 +153,11 @@ impl HornbeamParser { (false, name) } ); - Ok(Block::DefineExpandSlot(DefineExpandSlot { name, optional })) + Ok(Block::DefineExpandSlot(DefineExpandSlot { + name, + optional, + loc, + })) } fn SlotOptional(_input: Node) -> PCResult<()> { @@ -231,19 +250,23 @@ impl HornbeamParser { } fn Variable(input: Node) -> PCResult { + let loc = nodeloc(&input); let name = intern(input.into_children().single()?.as_str()); - Ok(Expression::Variable { name }) + Ok(Expression::Variable { name, loc }) } fn Expr(input: Node) -> PCResult { - PRATT_PARSER - .map_primary(|primary| Ok(match primary.as_rule() { - Rule::IntLiteral => Expression::IntLiteral { val: primary.as_str().parse().map_err(|e| error(&format!("can't parse int: {e:?}"), primary.as_span()))? }, - Rule::String => Expression::StringExpr(HornbeamParser::String(Node::new(primary))?), - Rule::Variable => HornbeamParser::Variable(Node::new(primary))?, - Rule::FunctionCall => HornbeamParser::FunctionCall(Node::new(primary))?, + let ud = input.user_data().clone(); + let result = PRATT_PARSER + .map_primary(|primary| { + let node = Node::new_with_user_data(primary, ud.clone()); + Ok(match node.as_rule() { + Rule::IntLiteral => Expression::IntLiteral { val: node.as_str().parse().map_err(|e| error(&format!("can't parse int: {e:?}"), node.as_span()))? }, + Rule::String => Expression::StringExpr(HornbeamParser::String(node)?), + Rule::Variable => HornbeamParser::Variable(node)?, + Rule::FunctionCall => HornbeamParser::FunctionCall(node)?, other => unimplemented!("unimp primary {other:?}!"), - })) + })}) .map_prefix(|op, rhs| Ok(match op.as_rule() { Rule::negation => Expression::Negate { sub: Box::new(rhs?) }, other => unimplemented!("unimp prefix {other:?}!"), @@ -258,28 +281,33 @@ impl HornbeamParser { Rule::band => Expression::BAnd { left: Box::new(lhs?), right: Box::new(rhs?) }, other => unimplemented!("unimp infix {other:?}!"), })) - .map_postfix(|lhs, op| Ok(match op.as_rule() { + .map_postfix(|lhs, op| { + let node = Node::new_with_user_data(op, ud.clone()); + let loc = nodeloc(&node); + Ok(match node.as_rule() { Rule::unwrap => unimplemented!("unimp unwrap"), Rule::FieldLookup => { - let ident = intern(Node::new(op).into_children().single()?.as_str()); - Expression::FieldLookup { obj: Box::new(lhs?), ident } + let ident = intern(node.into_children().single()?.as_str()); + Expression::FieldLookup { obj: Box::new(lhs?), ident, loc } }, Rule::MethodCall => { - match_nodes!(Node::new(op).into_children(); + match_nodes!(node.into_children(); [Identifier(ident), Expr(args)..] => { - Expression::MethodCall { obj: Box::new(lhs?), ident, args: args.collect() } + Expression::MethodCall { obj: Box::new(lhs?), ident, args: args.collect(), loc } } ) } other => unimplemented!("unimp postfix {other:?}!"), - })) - .parse(input.into_children().into_pairs()) + })}) + .parse(input.into_children().into_pairs()); + result } fn FunctionCall(input: Node) -> PCResult { + let loc = nodeloc(&input); Ok(match_nodes!(input.into_children(); [Identifier(name), Expr(args)..] => { - Expression::FunctionCall { name, args: args.collect() } + Expression::FunctionCall { name, args: args.collect(), loc } } )) } @@ -289,6 +317,8 @@ impl HornbeamParser { } fn IfBlock(input: Node) -> PCResult { + let loc = nodeloc(&input); + let (cond, blocks, else_blocks) = match_nodes!(input.into_children(); [IfCondition(cond), blocks.., ElseBlock(else_blocks)] => { (cond, blocks, else_blocks) @@ -302,6 +332,7 @@ impl HornbeamParser { condition: cond, blocks: HornbeamParser::helper_blocks(blocks)?, else_blocks, + loc, })) } @@ -339,8 +370,14 @@ impl HornbeamParser { } } -pub fn parse_template(input: &str) -> PCResult