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

View File

@ -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<Rule>;
@ -12,3 +14,21 @@ pub type IStr = ArcIntern<String>;
pub fn intern(s: impl Into<String>) -> ArcIntern<String> {
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,
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<T> = Result<T, PCError<Rule>>;
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<R: Copy + Debug + Hash + Ord>(msg: &str, span: Span) -> PCError<R> {
PCError::new_from_span(
ErrorVariant::CustomError {
@ -51,7 +60,8 @@ impl HornbeamParser {
}
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 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<Block> {
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<Block> {
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<Expression> {
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<Expression> {
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<Expression> {
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<Block> {
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<Template> {
let res = HornbeamParser::parse(Rule::Hornbeam, input)?;
pub fn parse_template(input: &str, filename: &str) -> PCResult<Template> {
let res = HornbeamParser::parse_with_userdata(
Rule::Hornbeam,
input,
ParserUserData {
file: intern(filename),
},
)?;
let hornbeam = res.single()?;
HornbeamParser::Hornbeam(hornbeam)
}
@ -356,7 +393,8 @@ mod tests {
r#"
// This is a simple Hornbeam template that just shows a <div>
div
"#
"#,
"inp"
)
.unwrap());
}
@ -368,7 +406,8 @@ div
div
:someslot
"Oops!"
"#
"#,
"inp"
));
assert_yaml_snapshot!(parse_template(
@ -376,7 +415,8 @@ div
MyComponent
:someslot
"That's better!"
"#
"#,
"inp"
)
.unwrap());
}
@ -391,7 +431,8 @@ MyComponent
${"abc" + "def${ 1 + 1 }"}
Not too bad now.
''
"#
"#,
"inp"
)
.unwrap());
}
@ -402,7 +443,8 @@ MyComponent
r#"
if 10 / 2 == 5 or $point.x == 42 // div for a div?
div
"#
"#,
"inp"
)
.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?"
else // peculiar.
"Not even close, eh?"
"#
"#,
"inp"
)
.unwrap());
}
@ -433,7 +476,8 @@ div
slot :main
div
optional slot :footer
"#
"#,
"inp"
)
.unwrap());
}
@ -452,7 +496,8 @@ div
''
Footer
@footer-copyright{year = today().year}
"#
"#,
"inp"
)
.unwrap());
}

View File

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

View File

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

View File

@ -317,6 +317,7 @@ div
fragment Footer
"Or even adjacent ones"
"#,
"inp",
)
.unwrap();
assert_yaml_snapshot!(pull_out_entrypoints(template, "TemplateName").unwrap());
@ -336,6 +337,7 @@ div
fragment Footer
"Or even adjacent ones"
"#,
"inp",
)
.unwrap();
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"
OtherComponent {param1=1, param2="two", param3=$three}
"#,
"inp",
)
.unwrap();
assert_yaml_snapshot!(compile_functions(

View File

@ -168,7 +168,7 @@ mod tests {
use std::collections::BTreeMap;
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 mut compiled = compile_functions(&entrypoints).unwrap();
compiled.values_mut().for_each(apply_all_peephole_passes);