{% macro render_builder(language) -%}
{%- set builder = model.ir_languages[language].builder -%}
#![allow(clippy::too_many_lines)]
use std::rc::Rc;
#[allow(clippy::wildcard_imports)]
use super::nodes::*;
use crate::cst::{
Edge, EdgeLabel, Node, NodeKind, NonterminalKind, NonterminalNode, TerminalKind, TerminalNode,
};
//
// Sequences:
//
{% for parent_type, sequence in builder.sequences %}
{% if not sequence.has_added_fields %}
pub fn build_{{ parent_type | snake_case }}(node: &Rc<NonterminalNode>) -> Option<{{ parent_type }}>
{
assert_nonterminal_kind(node, NonterminalKind::{{ parent_type }});
let mut helper = ChildrenHelper::new(&node.children);
{% for field in sequence.fields %}
{%- if field.is_removed -%}
_ = helper.accept_label(EdgeLabel::{{ field.label | pascal_case }})?;
{%- elif field.is_optional -%}
let {{ field.label }} =
{%- if field.type.kind == "Terminal" -%}
helper.accept_label(EdgeLabel::{{ field.label | pascal_case }}).map(terminal_node_cloned)
{%- elif field.type.kind == "UniqueTerminal" -%}
helper.accept_label(EdgeLabel::{{ field.label | pascal_case }}).is_some()
{%- else -%}
helper.accept_label(EdgeLabel::{{ field.label | pascal_case }}).and_then(|node|
build_{{ field.type.name | snake_case }}(nonterminal_node(node))
)
{%- endif -%}
;
{%- else -%}
let {{ field.label }} =
{%- if field.type.is_terminal -%}
terminal_node_cloned(helper.accept_label(EdgeLabel::{{ field.label | pascal_case }})?);
{%- else -%}
build_{{ field.type.name | snake_case }}(
nonterminal_node(helper.accept_label(EdgeLabel::{{ field.label | pascal_case }})?),
)?;
{%- endif -%}
{%- endif -%}
{%- endfor %}
if !helper.finalize() {
return None;
}
Some(Rc::new({{ parent_type }}Struct {
node_id: node.id(),
{%- for field in sequence.fields -%}
{%- if not field.is_removed -%}
{{ field.label }},
{%- endif -%}
{%- endfor %}
}))
}
{% endif %}
{% endfor %}
//
// Choices:
//
{% for parent_type, choice in builder.choices %}
pub fn build_{{ parent_type | snake_case }}(node: &Rc<NonterminalNode>) -> Option<{{ parent_type }}> {
assert_nonterminal_kind(node, NonterminalKind::{{ parent_type }});
let mut helper = ChildrenHelper::new(&node.children);
let variant = helper.accept_label(EdgeLabel::Variant)?;
let item = match variant.kind() {
{% for type in choice.variants | filter(attribute="kind", value="Nonterminal") -%}
NodeKind::Nonterminal(NonterminalKind::{{ type.name }}) => {{ parent_type }}::{{ type.name }}(build_{{ type.name | snake_case }}(nonterminal_node(variant))?),
{%- endfor -%}
{% for type in choice.variants | filter(attribute="kind", value="Terminal") -%}
NodeKind::Terminal(TerminalKind::{{ type.name }}) => {
{{ parent_type }}::{{ type.name }}(terminal_node_cloned(variant))
},
{%- endfor -%}
{% for type in choice.variants | filter(attribute="kind", value="UniqueTerminal") -%}
NodeKind::Terminal(TerminalKind::{{ type.name }}) => {
{{ parent_type }}::{{ type.name }}
},
{%- endfor -%}
NodeKind::Nonterminal(_) | NodeKind::Terminal(_) => {
unreachable!("unexpected variant node of kind {kind}", kind = variant.kind());
}
};
if !helper.finalize() {
return None;
}
Some(item)
}
{% endfor %}
//
// Repeated & Separated
//
{% for parent_type, collection in builder.collections %}
pub fn build_{{ parent_type | snake_case }}(node: &Rc<NonterminalNode>) -> Option<{{ parent_type }}> {
assert_nonterminal_kind(node, NonterminalKind::{{ parent_type }});
let mut items = {{ parent_type }}::new();
let mut helper = ChildrenHelper::new(&node.children);
while let Some(child) = helper.accept_label(EdgeLabel::Item) {
{%- if collection.item_type.is_terminal -%}
items.push(terminal_node_cloned(child));
{%- else -%}
if let Some(item) = build_{{ collection.item_type.name | snake_case }}(nonterminal_node(child)) {
items.push(item);
}
{%- endif -%}
_ = helper.accept_label(EdgeLabel::Separator);
}
if !helper.finalize() {
return None;
}
Some(items)
}
{% endfor %}
//
// Common:
//
#[allow(dead_code)]
#[inline]
fn assert_nonterminal_kind(node: &Rc<NonterminalNode>, kind: NonterminalKind) {
assert_eq!(node.kind, kind, "expected non-terminal of kind {kind}, got {node:?}");
}
#[allow(dead_code)]
#[inline]
fn terminal_node_cloned(node: &Node) -> Rc<TerminalNode> {
node.as_terminal().map(Rc::clone).expect("expected terminal node")
}
#[allow(dead_code)]
#[inline]
fn nonterminal_node(node: &Node) -> &Rc<NonterminalNode> {
node.as_nonterminal().expect("expected non-terminal node")
}
struct ChildrenHelper<'a> {
children: &'a Vec<Edge>,
index: usize,
}
impl<'a> ChildrenHelper<'a> {
fn new(children: &'a Vec<Edge>) -> Self {
let mut index = 0;
while index < children.len() {
if !children[index].is_trivia() && children[index].is_valid() {
break;
}
index += 1;
}
Self { children, index }
}
fn accept_label(&mut self, label: EdgeLabel) -> Option<&Node> {
if self.index >= self.children.len() || self.children[self.index].label != label {
return None;
}
let node = &self.children[self.index].node;
loop {
self.index += 1;
if self.index >= self.children.len() ||
(!self.children[self.index].is_trivia() && self.children[self.index].is_valid()) {
break;
}
}
Some(node)
}
fn finalize(mut self) -> bool {
// skip over trailing trivia and unrecognized nodes
while self.index < self.children.len() {
if !self.children[self.index].is_trivia() && self.children[self.index].is_valid() {
return false;
}
self.index += 1;
}
true
}
}
{% endmacro render_builder %}