slang_solidity 1.3.5

A modular set of compiler APIs empowering the next generation of Solidity code analysis and developer tooling. Written in Rust and distributed in multiple languages.
Documentation
{% 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 %}