use std::collections::HashMap;
use lang_forge::syntax_lang::{Element, Node};
use lang_forge::{Kind, Language};
const PAGE: &str = "<h1>{{ title }}</h1>\n<ul>\n{% for $user in users %} <li>{{ \"{$user} says hi\" | upper }}</li>\n{% endfor %}</ul>\n";
#[derive(Clone, Debug)]
enum Value {
Text(String),
List(Vec<Value>),
}
impl Value {
fn text(&self) -> String {
match self {
Value::Text(t) => t.clone(),
Value::List(items) => items.iter().map(Value::text).collect::<Vec<_>>().join(", "),
}
}
}
struct Renderer<'a> {
lang: &'a Language,
src: &'a str,
vars: HashMap<String, Value>,
}
impl Renderer<'_> {
fn field<'n>(&self, node: &'n Node<Kind>, name: &str) -> Option<&'n Element<Kind>> {
let label = self.lang.label_id(name)?;
node.children().find(|c| c.kind().label() == Some(label))
}
fn fields<'n>(&self, node: &'n Node<Kind>, name: &str) -> Vec<&'n Element<Kind>> {
let label = self.lang.label_id(name);
node.children()
.filter(|c| label.is_some() && c.kind().label() == label)
.collect()
}
fn text_of(&self, element: &Element<Kind>) -> &str {
let span = element.span();
&self.src[span.start().to_usize()..span.end().to_usize()]
}
fn items(&self, elements: &[&Element<Kind>], out: &mut String) {
for element in elements {
match element {
Element::Token(_) => out.push_str(self.text_of(element)),
Element::Node(node) => self.item(node, out),
}
}
}
fn item(&self, node: &Node<Kind>, out: &mut String) {
match self.lang.kind_name(*node.kind()) {
"hole" => {
if let Some(Element::Node(value)) = self.field(node, "value") {
out.push_str(&self.eval(value).text());
}
}
"for_tag" => {
let var = self
.field(node, "var")
.map(|v| self.text_of(v).to_owned())
.unwrap_or_default();
let Some(Element::Node(seq)) = self.field(node, "seq") else {
return;
};
let Value::List(items) = self.eval(seq) else {
return;
};
let body = self.fields(node, "body");
for item in items {
let mut inner = Renderer {
lang: self.lang,
src: self.src,
vars: self.vars.clone(),
};
let _ = inner.vars.insert(var.clone(), item);
inner.items(&body, out);
}
}
_ => {}
}
}
fn eval(&self, node: &Node<Kind>) -> Value {
let kind = self.lang.kind_name(*node.kind());
match kind {
"expr" => match node.children().find(|c| !matches!(c, Element::Token(t) if lang_forge::syntax_lang::TokenKind::is_trivia(t.kind()))) {
Some(Element::Node(inner)) => self.eval(inner),
Some(token) => self.atom(token),
None => Value::Text(String::new()),
},
"filter" => {
let value = self.operand(node, "lhs").text();
let filter = self.field(node, "rhs").map(|f| self.text_of(f)).unwrap_or("");
Value::Text(if filter == "upper" { value.to_uppercase() } else { value })
}
"concat" => Value::Text(self.operand(node, "lhs").text() + &self.operand(node, "rhs").text()),
"STR" => {
let mut out = String::new();
for part in self.fields(node, "parts") {
match part {
Element::Node(hole) => {
if let Some(Element::Node(value)) = self.field(hole, "value") {
out.push_str(&self.eval(value).text());
}
}
Element::Token(_) => out.push_str(self.text_of(part)),
}
}
Value::Text(out)
}
_ => Value::Text(String::new()),
}
}
fn operand(&self, node: &Node<Kind>, name: &str) -> Value {
match self.field(node, name) {
Some(Element::Node(n)) => self.eval(n),
Some(token) => self.atom(token),
None => Value::Text(String::new()),
}
}
fn atom(&self, element: &Element<Kind>) -> Value {
let text = self.text_of(element);
self.vars
.get(text)
.cloned()
.unwrap_or_else(|| Value::Text(text.to_owned()))
}
}
fn main() -> Result<(), lang_forge::Error> {
let lang = Language::from_lsf(include_str!("schematics/tmpl.lsf"))?;
println!(
"forged {} (format {}), {} kinds",
lang.display_name(),
lang.format(),
lang.kind_count()
);
for kind in ["hole", "for_tag"] {
let fields: Vec<String> = lang
.fields(lang.kind(kind).expect("a rule"))
.map(|f| format!("{}: {:?}", f.name(), f.cardinality()))
.collect();
println!(" {kind} fields: {}", fields.join(", "));
}
let mut vars = HashMap::new();
let _ = vars.insert("title".to_owned(), Value::Text("Users".to_owned()));
let _ = vars.insert(
"users".to_owned(),
Value::List(vec![
Value::Text("ada".to_owned()),
Value::Text("grace".to_owned()),
]),
);
let image = lang.to_image();
let loaded = Language::from_image(&image).expect("its own image loads");
for (name, lang) in [("forged", &lang), ("from image", &loaded)] {
let parse = lang.parse(PAGE);
for d in parse.diagnostics() {
println!("{name}: {}", d.message());
}
let renderer = Renderer {
lang,
src: PAGE,
vars: vars.clone(),
};
let mut out = String::new();
let items = renderer.fields(parse.tree(), "items");
renderer.items(&items, &mut out);
println!("--- {name} ({} image bytes) ---\n{out}", image.len());
}
Ok(())
}