Crate astmaker

source ·
Expand description

Introduction

astmaker is a DSL for programming language designers to build Abstract Syntax Trees and tree-walking models quickly.

Features

AST definition:

  • custom location type
  • structural nodes (struct) and variant nodes (enum)
  • custom node attributes type

Model definition:

  • visitor pattern
  • support for generics and lifetimes

Architecture

When creating an AST, this crate will define the following types and traits:

pub trait NodeAttributes {
  type Attributes;
}

#[derive(Debug, Clone, PartialEq)]
pub struct Node<T: NodeAttributes> {
  pub location: LocationType,
  pub data: Box<T>,
  pub attrs: Option<T::NodeAttributes>,
}

When creating a model, this crate will define the following types:

pub trait Visitor: Sized {
  fn visit<T: NodeAttributes + Visitable<Self, T>>(
    &mut self,
    node: &mut Node<T>,
  ) -> OutputType;
}

pub trait Visitable<C: Visitor, T: NodeAttributes> {
  fn visit(context: &mut C, node: &mut Node<T>) -> OutputType;
}

Basic usage

This crates provide 2 macros:

  • ast!: to define the AST
  • model!: to implement the tree-walking model

Each macro provide a custom DSL.

Defining Abstract Syntax Tress

use astmaker::{ast, model};

ast!{
  location = (usize, usize);

  pub node VariantNode =
    | A -> Node<StructuralNodeA>
    | B -> Node<StructuralNodeB>
    ;

  pub node StructuralNodeA = {
    data: u8,
  }

  pub node StructuralNodeB = {
    data: u16,
  }

  pub node NodeWithAttributes where attrs: String = {
    data: u32,
  }
}

When not specified, the default attributes type is the unit type ().

The generated code will contain the structs and enums as well as their implementation of the NodeAttributes trait.

Every generated type implements the traits Debug, Clone and PartialEq.

Defining tree-walking models

pub struct Model;

model!{
  impl Model -> Result<(), ()> {
    where VariantNode => {
      match node.data.as_mut() {
        VariantNode::A(child) => context.visit(child)?,
        VariantNode::B(child) => context.visit(child)?,
      }

      Ok(())
    },
    where StructuralNodeA => {
      Ok(())
    },
    where StructuralNodeB => {
      Ok(())
    },
  }
}

The impl for Type part will implement the Visitor trait for the supplied type. Each where clause will implement the Visitable trait for the node type.

Generics and lifetimes are also supported:

pub struct Model<'a, T> {
  data: &'a T,
}

model!{
  impl<'a, T> Model -> Result<(), ()> {
    // ...
  }
}

Macros