Add support for Option<> field values
Signed-off-by: Olivier <olivier@librepush.net>
This commit is contained in:
parent
d08c04de5b
commit
d9f008d5ed
@ -26,10 +26,15 @@ pub struct HtmlElement {
|
||||
pub children: Vec<Block>,
|
||||
pub classes: Vec<IStr>,
|
||||
pub dom_id: Option<IStr>,
|
||||
pub attributes: BTreeMap<IStr, Expression>,
|
||||
pub attributes: BTreeMap<IStr, (Expression, ElementAttributeFlags)>,
|
||||
pub loc: Locator,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct ElementAttributeFlags {
|
||||
pub optional: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
pub struct ComponentElement {
|
||||
pub name: IStr,
|
||||
|
@ -18,7 +18,7 @@ BlockContent = _{
|
||||
}
|
||||
|
||||
Element = {
|
||||
ElementName ~ cssClass* ~ domId? ~ (ws+ ~ MapLiteral)? ~ lineEnd ~ (NewBlock | NewSlotBlock)?
|
||||
ElementName ~ cssClass* ~ domId? ~ (ws+ ~ AttrMapLiteral)? ~ lineEnd ~ (NewBlock | NewSlotBlock)?
|
||||
}
|
||||
|
||||
cssClass = _{
|
||||
@ -207,12 +207,19 @@ Variable = { "$" ~ Identifier }
|
||||
|
||||
ListLiteral = { "[" ~ commaSeparatedExprs ~ "]" }
|
||||
|
||||
// Basic key-value pairs forming a map literal {a = .., b = ..}.
|
||||
KVPair = { Identifier ~ wsnl* ~ "=" ~ wsnl* ~ Expr }
|
||||
KVarShorthand = { Variable }
|
||||
commaSeparatedKVPairs = _{ wsnl* ~ ((KVPair | KVarShorthand) ~ wsnl* ~ ("," ~ wsnl* ~ (KVPair | KVarShorthand) ~ wsnl*)* ~ ("," ~ wsnl*)?)? }
|
||||
MapLiteral = { "{" ~ commaSeparatedKVPairs ~ "}" }
|
||||
|
||||
|
||||
// Element attribute key-value pairs
|
||||
// These can have extra features not found in the basic map literals
|
||||
AttrKVPairOptionalMarker = { "?" }
|
||||
AttrKVPair = { Identifier ~ AttrKVPairOptionalMarker? ~ wsnl* ~ "=" ~ wsnl* ~ Expr }
|
||||
AttrKVarShorthand = { Variable ~ AttrKVPairOptionalMarker? }
|
||||
commaSeparatedAttrKVPairs = _{ wsnl* ~ ((AttrKVPair | AttrKVarShorthand) ~ wsnl* ~ ("," ~ wsnl* ~ (AttrKVPair | AttrKVarShorthand) ~ wsnl*)* ~ ("," ~ wsnl*)?)? }
|
||||
AttrMapLiteral = { "{" ~ commaSeparatedAttrKVPairs ~ "}" }
|
||||
|
||||
|
||||
// As in a let binding or for binding.
|
||||
|
@ -1,8 +1,9 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::ast::{
|
||||
Binding, Block, ComponentElement, DefineExpandSlot, DefineFragment, Expression, ForBlock,
|
||||
HtmlElement, IfBlock, MatchBinding, MatchBlock, StringExpr, StringPiece, Template,
|
||||
Binding, Block, ComponentElement, DefineExpandSlot, DefineFragment, ElementAttributeFlags,
|
||||
Expression, ForBlock, HtmlElement, IfBlock, MatchBinding, MatchBlock, StringExpr, StringPiece,
|
||||
Template,
|
||||
};
|
||||
use crate::{intern, IStr, Locator};
|
||||
use lazy_static::lazy_static;
|
||||
@ -88,8 +89,8 @@ impl HornbeamParser {
|
||||
Rule::SupplySlot => {
|
||||
supply_slots.push(HornbeamParser::SupplySlot(next)?);
|
||||
}
|
||||
Rule::MapLiteral => {
|
||||
attributes = HornbeamParser::MapLiteral(next)?;
|
||||
Rule::AttrMapLiteral => {
|
||||
attributes = HornbeamParser::AttrMapLiteral(next)?;
|
||||
}
|
||||
_ => {
|
||||
if let Some(block) = HornbeamParser::helper_block(next)? {
|
||||
@ -113,6 +114,20 @@ impl HornbeamParser {
|
||||
loc,
|
||||
})
|
||||
} else {
|
||||
let attributes: PCResult<BTreeMap<IStr, Expression>> = attributes
|
||||
.into_iter()
|
||||
.map(|(k, (v, attrs))| {
|
||||
if attrs.optional {
|
||||
Err(error(
|
||||
"Optional arguments to components are currently unsupported",
|
||||
span,
|
||||
))
|
||||
} else {
|
||||
Ok((k, v))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let attributes = attributes?;
|
||||
if !supply_slots.is_empty() {
|
||||
let mut slots = BTreeMap::new();
|
||||
for (slot_name, slot_content_blocks, _slot_span) in supply_slots {
|
||||
@ -278,6 +293,52 @@ impl HornbeamParser {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn AttrKVPair(input: Node) -> PCResult<(IStr, (Expression, ElementAttributeFlags))> {
|
||||
Ok(match_nodes!(input.into_children();
|
||||
[Identifier(key), Expr(value)] => (key, (value, ElementAttributeFlags {
|
||||
optional: false
|
||||
})),
|
||||
[Identifier(key), AttrKVPairOptionalMarker(_), Expr(value)] => (key, (value, ElementAttributeFlags {
|
||||
optional: true
|
||||
})),
|
||||
))
|
||||
}
|
||||
|
||||
fn AttrKVPairOptionalMarker(input: Node) -> PCResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn AttrKVarShorthand(input: Node) -> PCResult<(IStr, (Expression, ElementAttributeFlags))> {
|
||||
let (var_expr, optional) = match_nodes!(input.into_children();
|
||||
[Variable(var_expr), AttrKVPairOptionalMarker(_)] => {
|
||||
(var_expr, true)
|
||||
},
|
||||
[Variable(var_expr)] => {
|
||||
(var_expr, false)
|
||||
},
|
||||
);
|
||||
if let Expression::Variable { name, .. } = &var_expr {
|
||||
Ok((name.clone(), (var_expr, ElementAttributeFlags { optional })))
|
||||
} else {
|
||||
unreachable!("Variable should also be returned from Variable");
|
||||
}
|
||||
}
|
||||
|
||||
fn AttrMapLiteral(
|
||||
input: Node,
|
||||
) -> PCResult<BTreeMap<IStr, (Expression, ElementAttributeFlags)>> {
|
||||
input
|
||||
.into_children()
|
||||
.map(|node| match node.as_rule() {
|
||||
Rule::AttrKVPair => HornbeamParser::AttrKVPair(node),
|
||||
Rule::AttrKVarShorthand => HornbeamParser::AttrKVarShorthand(node),
|
||||
other => {
|
||||
unimplemented!("unexpected {other:?} in AttrMapLiteral");
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn SEscape(input: Node) -> PCResult<IStr> {
|
||||
let esc = input.as_str();
|
||||
Ok(match esc {
|
||||
|
@ -1,6 +1,8 @@
|
||||
use crate::ir::{Step, StepDef};
|
||||
use hornbeam_grammar::ast::{Block, Expression, StringExpr, StringPiece, Template};
|
||||
use hornbeam_grammar::{intern, Locator};
|
||||
use hornbeam_grammar::ast::{
|
||||
Binding, Block, Expression, HtmlElement, MatchBinding, StringExpr, StringPiece, Template,
|
||||
};
|
||||
use hornbeam_grammar::{intern, IStr, Locator};
|
||||
use itertools::Itertools;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::btree_map::Entry;
|
||||
@ -137,7 +139,7 @@ fn pull_out_entrypoints_from_block<'a>(
|
||||
}
|
||||
|
||||
/// Step 2. Compile the AST to IR steps.
|
||||
pub(crate) fn compile_functions<'a>(
|
||||
pub(crate) fn compile_functions(
|
||||
functions: &BTreeMap<String, Vec<Block>>,
|
||||
) -> Result<BTreeMap<String, Vec<Step>>, AstToIrError> {
|
||||
let mut result = BTreeMap::new();
|
||||
@ -151,7 +153,7 @@ pub(crate) fn compile_functions<'a>(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn compile_ast_block_to_steps<'a>(
|
||||
fn compile_ast_block_to_steps(
|
||||
block: &Block,
|
||||
instructions: &mut Vec<Step>,
|
||||
) -> Result<(), AstToIrError> {
|
||||
@ -177,7 +179,7 @@ fn compile_ast_block_to_steps<'a>(
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern(format!(" id=\"")),
|
||||
text: intern(" id=\""),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
@ -200,73 +202,63 @@ fn compile_ast_block_to_steps<'a>(
|
||||
|
||||
// This is only handling the case where we are the exclusive owner of all the class
|
||||
// names: see below for more...
|
||||
if !he.classes.is_empty() {
|
||||
if !he.attributes.contains_key(&intern("class")) {
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern(format!(" class=\"")),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: true,
|
||||
text: intern(he.classes.iter().map(|istr| istr.as_str()).join(" ")),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern("\""),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
}
|
||||
if !he.classes.is_empty() && !he.attributes.contains_key(&intern("class")) {
|
||||
let classes = he.classes.iter().map(|istr| istr.as_str()).join(" ");
|
||||
gen_steps_to_write_literal_attribute(he, "class", &classes, instructions);
|
||||
}
|
||||
|
||||
// Write attributes
|
||||
for (attr_name, attr_expr) in &he.attributes {
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern(format!(" {attr_name}=\"")),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
for (attr_name, (attr_expr, attr_flags)) in &he.attributes {
|
||||
let extra_inject = (attr_name.as_str() == "class" && !he.classes.is_empty())
|
||||
.then(|| intern(he.classes.iter().map(|istr| istr.as_str()).join(" ") + " "));
|
||||
if attr_flags.optional {
|
||||
let mut some_stage = Vec::new();
|
||||
gen_steps_to_write_attribute(
|
||||
he,
|
||||
attr_name,
|
||||
attr_expr,
|
||||
extra_inject.clone(),
|
||||
&mut some_stage,
|
||||
);
|
||||
let binding = MatchBinding::TupleVariant {
|
||||
name: intern("Some"),
|
||||
pieces: vec![Binding::Variable(intern("___attrval"))],
|
||||
};
|
||||
|
||||
let mut arms = vec![(binding, some_stage)];
|
||||
|
||||
if let Some(extra_inject) = extra_inject {
|
||||
let mut none_stage = Vec::new();
|
||||
gen_steps_to_write_literal_attribute(
|
||||
he,
|
||||
attr_name,
|
||||
extra_inject.trim_end_matches(' '),
|
||||
&mut none_stage,
|
||||
);
|
||||
arms.push((
|
||||
MatchBinding::UnitVariant {
|
||||
name: intern("None"),
|
||||
},
|
||||
none_stage,
|
||||
));
|
||||
}
|
||||
|
||||
if attr_name.as_str() == "class" && !he.classes.is_empty() {
|
||||
// This handles the case where we need to merge class lists between an
|
||||
// attribute and the shorthand form.
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: true,
|
||||
text: intern(
|
||||
he.classes.iter().map(|istr| istr.as_str()).join(" ") + " ",
|
||||
),
|
||||
def: StepDef::Match {
|
||||
matchable: attr_expr.clone(),
|
||||
arms,
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
})
|
||||
} else {
|
||||
gen_steps_to_write_attribute(
|
||||
he,
|
||||
attr_name,
|
||||
attr_expr,
|
||||
extra_inject,
|
||||
instructions,
|
||||
);
|
||||
}
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteEval {
|
||||
escape: true,
|
||||
expr: attr_expr.clone(),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern("\""),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
// Close the tag
|
||||
@ -469,6 +461,81 @@ fn compile_ast_block_to_steps<'a>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_steps_to_write_literal_attribute(
|
||||
he: &HtmlElement,
|
||||
attr_name: &str,
|
||||
attr_value: &str,
|
||||
instructions: &mut Vec<Step>,
|
||||
) {
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern(format!(" {attr_name}=\"")),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: true,
|
||||
text: intern(attr_value),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern("\""),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
fn gen_steps_to_write_attribute(
|
||||
he: &HtmlElement,
|
||||
attr_name: &str,
|
||||
attr_expr: &Expression,
|
||||
extra_inject: Option<IStr>,
|
||||
instructions: &mut Vec<Step>,
|
||||
) {
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern(format!(" {attr_name}=\"")),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
|
||||
if let Some(extra_inject) = extra_inject {
|
||||
// This handles the case where we need to merge class lists between an
|
||||
// attribute and the shorthand form.
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: true,
|
||||
text: extra_inject,
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteEval {
|
||||
escape: true,
|
||||
expr: attr_expr.clone(),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
|
||||
instructions.push(Step {
|
||||
def: StepDef::WriteLiteral {
|
||||
escape: false,
|
||||
text: intern("\""),
|
||||
},
|
||||
locator: he.loc.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
Loading…
x
Reference in New Issue
Block a user