Insert Locators into the interesting AST nodes

This commit is contained in:
Olivier 'reivilibre' 2023-03-01 21:00:38 +00:00
parent 67545f9e8e
commit f54f2093de
7 changed files with 115 additions and 37 deletions

View File

@ -1,4 +1,4 @@
use crate::IStr; use crate::{IStr, Locator};
use serde::Serialize; use serde::Serialize;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -24,6 +24,7 @@ pub struct HtmlElement {
pub classes: Vec<IStr>, pub classes: Vec<IStr>,
pub dom_id: Option<IStr>, pub dom_id: Option<IStr>,
pub attributes: BTreeMap<IStr, Expression>, pub attributes: BTreeMap<IStr, Expression>,
pub loc: Locator,
} }
#[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
@ -31,6 +32,7 @@ pub struct ComponentElement {
pub name: IStr, pub name: IStr,
pub slots: BTreeMap<IStr, Vec<Block>>, pub slots: BTreeMap<IStr, Vec<Block>>,
pub attributes: BTreeMap<IStr, Expression>, pub attributes: BTreeMap<IStr, Expression>,
pub loc: Locator,
} }
#[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
@ -38,18 +40,21 @@ pub struct IfBlock {
pub condition: Expression, pub condition: Expression,
pub blocks: Vec<Block>, pub blocks: Vec<Block>,
pub else_blocks: Vec<Block>, pub else_blocks: Vec<Block>,
pub loc: Locator,
} }
#[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct DefineExpandSlot { pub struct DefineExpandSlot {
pub name: IStr, pub name: IStr,
pub optional: bool, pub optional: bool,
pub loc: Locator,
} }
#[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct DefineFragment { pub struct DefineFragment {
pub name: IStr, pub name: IStr,
pub blocks: Vec<Block>, pub blocks: Vec<Block>,
pub loc: Locator,
} }
#[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize)]
@ -128,19 +133,23 @@ pub enum Expression {
FieldLookup { FieldLookup {
obj: Box<Expression>, obj: Box<Expression>,
ident: IStr, ident: IStr,
loc: Locator,
}, },
MethodCall { MethodCall {
obj: Box<Expression>, obj: Box<Expression>,
ident: IStr, ident: IStr,
args: Vec<Expression>, args: Vec<Expression>,
loc: Locator,
}, },
// Other Primaries // Other Primaries
Variable { Variable {
name: IStr, name: IStr,
loc: Locator,
}, },
FunctionCall { FunctionCall {
name: IStr, name: IStr,
args: Vec<Expression>, args: Vec<Expression>,
loc: Locator,
}, },
} }

View File

@ -1,8 +1,10 @@
pub mod ast; pub mod ast;
mod parser; mod parser;
use serde::Serialize;
use arc_interner::ArcIntern; use arc_interner::ArcIntern;
pub use parser::parse_template; pub use parser::parse_template;
use pest::Span;
use parser::Rule; use parser::Rule;
pub type ParseError = pest::error::Error<Rule>; pub type ParseError = pest::error::Error<Rule>;
@ -12,3 +14,21 @@ pub type IStr = ArcIntern<String>;
pub fn intern(s: impl Into<String>) -> ArcIntern<String> { pub fn intern(s: impl Into<String>) -> ArcIntern<String> {
ArcIntern::new(s.into()) 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,
}
}
}

View File

@ -4,7 +4,7 @@ use crate::ast::{
Block, ComponentElement, DefineExpandSlot, DefineFragment, Expression, HtmlElement, IfBlock, Block, ComponentElement, DefineExpandSlot, DefineFragment, Expression, HtmlElement, IfBlock,
StringExpr, StringPiece, Template, StringExpr, StringPiece, Template,
}; };
use crate::{intern, IStr}; use crate::{intern, IStr, Locator};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use pest::error::ErrorVariant; use pest::error::ErrorVariant;
use pest::pratt_parser::{Assoc, Op, PrattParser}; use pest::pratt_parser::{Assoc, Op, PrattParser};
@ -15,12 +15,21 @@ use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
type PCResult<T> = Result<T, PCError<Rule>>; type PCResult<T> = Result<T, PCError<Rule>>;
type Node<'i> = pest_consume::Node<'i, Rule, ()>; type Node<'i> = pest_consume::Node<'i, Rule, ParserUserData>;
#[derive(Parser)] #[derive(Parser)]
#[grammar = "hornbeam.pest"] #[grammar = "hornbeam.pest"]
struct HornbeamParser; 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<R: Copy + Debug + Hash + Ord>(msg: &str, span: Span) -> PCError<R> { fn error<R: Copy + Debug + Hash + Ord>(msg: &str, span: Span) -> PCError<R> {
PCError::new_from_span( PCError::new_from_span(
ErrorVariant::CustomError { ErrorVariant::CustomError {
@ -51,7 +60,8 @@ impl HornbeamParser {
} }
fn Element(input: Node) -> PCResult<Block> { fn Element(input: Node) -> PCResult<Block> {
let loc = input.as_span(); let span = input.as_span();
let loc = nodeloc(&input);
let mut children = input.into_children(); let mut children = input.into_children();
let name = HornbeamParser::ElementName(children.next().unwrap())?; let name = HornbeamParser::ElementName(children.next().unwrap())?;
@ -86,7 +96,7 @@ impl HornbeamParser {
Ok(if name.chars().next().unwrap().is_ascii_lowercase() { Ok(if name.chars().next().unwrap().is_ascii_lowercase() {
if !supply_slots.is_empty() { 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 { Block::HtmlElement(HtmlElement {
@ -95,6 +105,7 @@ impl HornbeamParser {
classes, classes,
dom_id, dom_id,
attributes, attributes,
loc,
}) })
} else { } else {
if !supply_slots.is_empty() { if !supply_slots.is_empty() {
@ -106,6 +117,7 @@ impl HornbeamParser {
name, name,
slots, slots,
attributes, attributes,
loc,
}) })
} else { } else {
let mut slots = BTreeMap::new(); let mut slots = BTreeMap::new();
@ -114,22 +126,25 @@ impl HornbeamParser {
name, name,
slots, slots,
attributes, attributes,
loc,
}) })
} }
}) })
} }
fn DefineFragment(input: Node) -> PCResult<Block> { fn DefineFragment(input: Node) -> PCResult<Block> {
let loc = nodeloc(&input);
Ok(match_nodes!(input.into_children(); Ok(match_nodes!(input.into_children();
[Identifier(name), blocks..] => { [Identifier(name), blocks..] => {
Block::DefineFragment(DefineFragment { Block::DefineFragment(DefineFragment {
name, blocks: HornbeamParser::helper_blocks(blocks)? name, blocks: HornbeamParser::helper_blocks(blocks)?, loc
}) })
} }
)) ))
} }
fn DefineExpandSlot(input: Node) -> PCResult<Block> { fn DefineExpandSlot(input: Node) -> PCResult<Block> {
let loc = nodeloc(&input);
let (optional, name) = match_nodes!(input.into_children(); let (optional, name) = match_nodes!(input.into_children();
[SlotOptional(_), Identifier(name)] => { [SlotOptional(_), Identifier(name)] => {
(true, name) (true, name)
@ -138,7 +153,11 @@ impl HornbeamParser {
(false, name) (false, name)
} }
); );
Ok(Block::DefineExpandSlot(DefineExpandSlot { name, optional })) Ok(Block::DefineExpandSlot(DefineExpandSlot {
name,
optional,
loc,
}))
} }
fn SlotOptional(_input: Node) -> PCResult<()> { fn SlotOptional(_input: Node) -> PCResult<()> {
@ -231,19 +250,23 @@ impl HornbeamParser {
} }
fn Variable(input: Node) -> PCResult<Expression> { fn Variable(input: Node) -> PCResult<Expression> {
let loc = nodeloc(&input);
let name = intern(input.into_children().single()?.as_str()); let name = intern(input.into_children().single()?.as_str());
Ok(Expression::Variable { name }) Ok(Expression::Variable { name, loc })
} }
fn Expr(input: Node) -> PCResult<Expression> { fn Expr(input: Node) -> PCResult<Expression> {
PRATT_PARSER let ud = input.user_data().clone();
.map_primary(|primary| Ok(match primary.as_rule() { let result = PRATT_PARSER
Rule::IntLiteral => Expression::IntLiteral { val: primary.as_str().parse().map_err(|e| error(&format!("can't parse int: {e:?}"), primary.as_span()))? }, .map_primary(|primary| {
Rule::String => Expression::StringExpr(HornbeamParser::String(Node::new(primary))?), let node = Node::new_with_user_data(primary, ud.clone());
Rule::Variable => HornbeamParser::Variable(Node::new(primary))?, Ok(match node.as_rule() {
Rule::FunctionCall => HornbeamParser::FunctionCall(Node::new(primary))?, 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:?}!"), other => unimplemented!("unimp primary {other:?}!"),
})) })})
.map_prefix(|op, rhs| Ok(match op.as_rule() { .map_prefix(|op, rhs| Ok(match op.as_rule() {
Rule::negation => Expression::Negate { sub: Box::new(rhs?) }, Rule::negation => Expression::Negate { sub: Box::new(rhs?) },
other => unimplemented!("unimp prefix {other:?}!"), other => unimplemented!("unimp prefix {other:?}!"),
@ -258,28 +281,33 @@ impl HornbeamParser {
Rule::band => Expression::BAnd { left: Box::new(lhs?), right: Box::new(rhs?) }, Rule::band => Expression::BAnd { left: Box::new(lhs?), right: Box::new(rhs?) },
other => unimplemented!("unimp infix {other:?}!"), 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::unwrap => unimplemented!("unimp unwrap"),
Rule::FieldLookup => { Rule::FieldLookup => {
let ident = intern(Node::new(op).into_children().single()?.as_str()); let ident = intern(node.into_children().single()?.as_str());
Expression::FieldLookup { obj: Box::new(lhs?), ident } Expression::FieldLookup { obj: Box::new(lhs?), ident, loc }
}, },
Rule::MethodCall => { Rule::MethodCall => {
match_nodes!(Node::new(op).into_children(); match_nodes!(node.into_children();
[Identifier(ident), Expr(args)..] => { [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:?}!"), other => unimplemented!("unimp postfix {other:?}!"),
})) })})
.parse(input.into_children().into_pairs()) .parse(input.into_children().into_pairs());
result
} }
fn FunctionCall(input: Node) -> PCResult<Expression> { fn FunctionCall(input: Node) -> PCResult<Expression> {
let loc = nodeloc(&input);
Ok(match_nodes!(input.into_children(); Ok(match_nodes!(input.into_children();
[Identifier(name), Expr(args)..] => { [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<Block> { fn IfBlock(input: Node) -> PCResult<Block> {
let loc = nodeloc(&input);
let (cond, blocks, else_blocks) = match_nodes!(input.into_children(); let (cond, blocks, else_blocks) = match_nodes!(input.into_children();
[IfCondition(cond), blocks.., ElseBlock(else_blocks)] => { [IfCondition(cond), blocks.., ElseBlock(else_blocks)] => {
(cond, blocks, else_blocks) (cond, blocks, else_blocks)
@ -302,6 +332,7 @@ impl HornbeamParser {
condition: cond, condition: cond,
blocks: HornbeamParser::helper_blocks(blocks)?, blocks: HornbeamParser::helper_blocks(blocks)?,
else_blocks, else_blocks,
loc,
})) }))
} }
@ -339,8 +370,14 @@ impl HornbeamParser {
} }
} }
pub fn parse_template(input: &str) -> PCResult<Template> { pub fn parse_template(input: &str, filename: &str) -> PCResult<Template> {
let res = HornbeamParser::parse(Rule::Hornbeam, input)?; let res = HornbeamParser::parse_with_userdata(
Rule::Hornbeam,
input,
ParserUserData {
file: intern(filename),
},
)?;
let hornbeam = res.single()?; let hornbeam = res.single()?;
HornbeamParser::Hornbeam(hornbeam) HornbeamParser::Hornbeam(hornbeam)
} }
@ -356,7 +393,8 @@ mod tests {
r#" r#"
// This is a simple Hornbeam template that just shows a <div> // This is a simple Hornbeam template that just shows a <div>
div div
"# "#,
"inp"
) )
.unwrap()); .unwrap());
} }
@ -368,7 +406,8 @@ div
div div
:someslot :someslot
"Oops!" "Oops!"
"# "#,
"inp"
)); ));
assert_yaml_snapshot!(parse_template( assert_yaml_snapshot!(parse_template(
@ -376,7 +415,8 @@ div
MyComponent MyComponent
:someslot :someslot
"That's better!" "That's better!"
"# "#,
"inp"
) )
.unwrap()); .unwrap());
} }
@ -391,7 +431,8 @@ MyComponent
${"abc" + "def${ 1 + 1 }"} ${"abc" + "def${ 1 + 1 }"}
Not too bad now. Not too bad now.
'' ''
"# "#,
"inp"
) )
.unwrap()); .unwrap());
} }
@ -402,7 +443,8 @@ MyComponent
r#" r#"
if 10 / 2 == 5 or $point.x == 42 // div for a div? if 10 / 2 == 5 or $point.x == 42 // div for a div?
div div
"# "#,
"inp"
) )
.unwrap()); .unwrap());
@ -416,7 +458,8 @@ else if 1 + 2 - 1 == 3 // not too far off, I suppose
"Not quite, but fairly close. What kind of world is this?" "Not quite, but fairly close. What kind of world is this?"
else // peculiar. else // peculiar.
"Not even close, eh?" "Not even close, eh?"
"# "#,
"inp"
) )
.unwrap()); .unwrap());
} }
@ -433,7 +476,8 @@ div
slot :main slot :main
div div
optional slot :footer optional slot :footer
"# "#,
"inp"
) )
.unwrap()); .unwrap());
} }
@ -452,7 +496,8 @@ div
'' ''
Footer Footer
@footer-copyright{year = today().year} @footer-copyright{year = today().year}
"# "#,
"inp"
) )
.unwrap()); .unwrap());
} }

View File

@ -415,7 +415,7 @@ impl<'a, L: Sync + Send, O: OutputSystem + Send, LS: LocalisationSystem + Sync +
} }
Ok(Value::Str(Arc::new(output))) Ok(Value::Str(Arc::new(output)))
} }
Expression::FieldLookup { obj, ident } => { Expression::FieldLookup { obj, ident, loc } => {
let obj_val = self.evaluate_expression(scope_idx, obj)?; let obj_val = self.evaluate_expression(scope_idx, obj)?;
match obj_val { match obj_val {
Value::Reflective(reflective) => match reflective.reflect_ref() { Value::Reflective(reflective) => match reflective.reflect_ref() {
@ -468,7 +468,7 @@ impl<'a, L: Sync + Send, O: OutputSystem + Send, LS: LocalisationSystem + Sync +
Expression::MethodCall { .. } => { Expression::MethodCall { .. } => {
unimplemented!() unimplemented!()
} }
Expression::Variable { name } => { Expression::Variable { name, loc } => {
let locals = &self.scopes[scope_idx].variables; let locals = &self.scopes[scope_idx].variables;
match locals.get(name as &str) { match locals.get(name as &str) {
Some(variable_value) => Ok(variable_value.clone()), Some(variable_value) => Ok(variable_value.clone()),

View File

@ -88,8 +88,9 @@ impl<'a, LS> LoadedTemplates<(), LS> {
&mut self, &mut self,
template_name: &str, template_name: &str,
template: &'a str, template: &'a str,
filename: &str,
) -> Result<(), InterpreterError<Infallible, Infallible>> { ) -> Result<(), InterpreterError<Infallible, Infallible>> {
let template = parse_template(template)?; let template = parse_template(template, filename)?;
let ir = ast_to_optimised_ir(template_name, template)?; let ir = ast_to_optimised_ir(template_name, template)?;
for (k, v) in ir { for (k, v) in ir {
self.template_functions.insert(k, Arc::new(v)); self.template_functions.insert(k, Arc::new(v));

View File

@ -317,6 +317,7 @@ div
fragment Footer fragment Footer
"Or even adjacent ones" "Or even adjacent ones"
"#, "#,
"inp",
) )
.unwrap(); .unwrap();
assert_yaml_snapshot!(pull_out_entrypoints(template, "TemplateName").unwrap()); assert_yaml_snapshot!(pull_out_entrypoints(template, "TemplateName").unwrap());
@ -336,6 +337,7 @@ div
fragment Footer fragment Footer
"Or even adjacent ones" "Or even adjacent ones"
"#, "#,
"inp",
) )
.unwrap(); .unwrap();
assert_yaml_snapshot!(compile_functions( assert_yaml_snapshot!(compile_functions(
@ -352,6 +354,7 @@ div.stylish#myid {size=42, stringy="yup", arb=$ritrary}
"This is a div with a few extras" "This is a div with a few extras"
OtherComponent {param1=1, param2="two", param3=$three} OtherComponent {param1=1, param2="two", param3=$three}
"#, "#,
"inp",
) )
.unwrap(); .unwrap();
assert_yaml_snapshot!(compile_functions( assert_yaml_snapshot!(compile_functions(

View File

@ -168,7 +168,7 @@ mod tests {
use std::collections::BTreeMap; use std::collections::BTreeMap;
fn parse_ir_and_peephole(text: &str) -> BTreeMap<String, Vec<Step<()>>> { fn parse_ir_and_peephole(text: &str) -> BTreeMap<String, Vec<Step<()>>> {
let template = parse_template(text).unwrap(); let template = parse_template(text, "inp").unwrap();
let entrypoints = pull_out_entrypoints(template, "TemplateName").unwrap(); let entrypoints = pull_out_entrypoints(template, "TemplateName").unwrap();
let mut compiled = compile_functions(&entrypoints).unwrap(); let mut compiled = compile_functions(&entrypoints).unwrap();
compiled.values_mut().for_each(apply_all_peephole_passes); compiled.values_mut().for_each(apply_all_peephole_passes);