vihaco / virtual ISA & machine framework
Guides
  1. Defining Instructions
  2. Advanced Instruction Usage
  3. Parser Integration
  4. Pattern Parser Generator
  5. Advanced Parser Customization
  6. Using Messages
  7. Building Components
  8. Observing Effects
  9. Defining a Composite

Module Parsing and Resolution

This guide picks up where Pattern Parser Integration ends. A pattern-derived parser handles one syntax type. The module layer adds:

  • an SST section header;
  • one or more fn @name() { ... } blocks;
  • indentation, blank lines, and // comments; and
  • a resolver that turns the typed parsed module into the runtime module your machine loads.

Module parsing is strict. Each function body is a Vec<I> produced by I::parser(). Symbolic operands, source sugar, and other source-only forms are represented explicitly in the syntax type and its patterns.

Parsed module types

vihaco::syntax exposes the typed intermediate representation:

use vihaco::SurfaceInstruction;
use vihaco_parser::Ident;

pub struct ParsedModule<I, Ty, H>
where
    I: SurfaceInstruction,
{
    pub header: H,
    pub functions: Vec<ParsedFunction<I, Ty>>,
}

pub struct ParsedFunction<I, Ty>
where
    I: SurfaceInstruction,
{
    pub name: Ident,
    pub params: Vec<Param<Ty>>,
    pub return_ty: Option<Ty>,
    pub body: Vec<I>,
}

pub struct Param<Ty> {
    pub name: Ident,
    pub ty: Ty,
}

Whitespace and // comments are skipped between instructions. An unknown instruction or a partially matched pattern makes the function parse fail. The consumer-provided Ty parses parameter and return-type syntax, so the framework does not impose a universal type language.

Step 1: define the instruction and type syntax

Types used in parsed function bodies implement the SurfaceInstruction marker in addition to Parse. The pattern derive emits both implementations for instruction enums. The source type is any consumer-owned type that derives Parse.

use vihaco::Instruction;
use vihaco_parser_derive::Parse;

#[derive(Debug, Clone, PartialEq, Instruction, Parse)]
#[syntax_class(instruction, head = "device")]
enum DeviceInstruction {
    Halt,
    #[pattern = "'wait $0"]
    Wait(u32),
}

#[derive(Debug, Clone, PartialEq, Parse)]
#[syntax_class(type)]
enum DeviceType {
    #[pattern = "`i64`"]
    I64,
    #[pattern = "`f64`"]
    F64,
}

The source for that type uses fully qualified instructions:

fn @main() {
    device::wait 10
    device::halt
}

Step 2: define the section header

Section headers implement FromText; SstHeader marks types accepted by the SST section parser.

#[derive(Debug, Clone, PartialEq)]
pub struct DeviceHeader {
    pub core_count: u32,
}

impl vihaco::FromText for DeviceHeader {
    fn from_text(text: &str) -> eyre::Result<Self> {
        Ok(Self {
            core_count: text.trim().parse()?,
        })
    }
}

impl vihaco::SstHeader for DeviceHeader {}

Use a zero-sized header type when a section has no header data.

Step 3: parse the section

use vihaco::{NoContext, SstFile};
use vihaco::syntax::ParsedModule;

let file = SstFile::<NoContext>::from_text(source)?;
let parsed =
    ParsedModule::<DeviceInstruction, DeviceType, DeviceHeader>::parse_section(file.root())?;

parsed.header is the typed DeviceHeader, while each function body contains only DeviceInstruction values and its signature uses DeviceType.

Step 4: resolve into a runtime module

Resolve<I, Ty, H> owns the application-specific conversion from a ParsedModule<I, Ty, H> to any output module type.

use vihaco::module::LocalModule;
use vihaco::syntax::{ParsedModule, Resolve};
use vihaco::{Type, Value};

#[derive(Default)]
struct DeviceResolver;

impl Resolve<DeviceInstruction, DeviceType, DeviceHeader> for DeviceResolver {
    type Module = LocalModule<DeviceInstruction, Value, Type>;

    fn resolve_module(
        &mut self,
        parsed: ParsedModule<DeviceInstruction, DeviceType, DeviceHeader>,
    ) -> eyre::Result<Self::Module> {
        let mut module = LocalModule::default();
        for function in parsed.functions {
            module.code.extend(function.body);
        }
        Ok(module)
    }
}

A resolver may instead translate a source-oriented instruction type into a different runtime instruction type, expand an explicitly modeled sugar variant, or intern data carried by typed fields. The important boundary is that parsing has already produced typed variants; resolution never receives an unstructured source line.

Model source-only forms explicitly

Patterns can represent symbols and sugar directly:

use vihaco_parser::Ident;

#[derive(vihaco_parser_derive::Parse)]
#[syntax_class(instruction, head = "control")]
enum ControlSurface {
    #[pattern = "'branch `@` $0"]
    Branch(Ident),
    #[pattern = "'repeat $0"]
    Repeat(u32),
}

A resolver can map Branch(Ident) through a label table and expand Repeat(u32) into multiple runtime instructions. Malformed spellings are rejected by the parser instead of being deferred as untyped data.

Quoted strings use QuotedString; domain expressions use nested local enums or structs that derive Parse. Keep state such as intern tables in the resolver; the parsed field should carry the owned source value needed for that later conversion.

Parse composite sections by component

A generated composite instruction enum is the runtime dispatch type. SST source is parsed through user-declared surface instruction types, each deriving Parse with its own namespace and patterns. Parse each component section as a ParsedModule<ComponentSurface, ComponentType, ComponentHeader>, resolve it, and load the resulting runtime instructions into that component.

This keeps source syntax attached to the component that owns it. Composite loading routes sections to components; it does not require a second source grammar for the generated machine instruction enum.

On this page
  1. Parsed module types
  2. Step 1: define the instruction and type syntax
  3. Step 2: define the section header
  4. Step 3: parse the section
  5. Step 4: resolve into a runtime module
  6. Model source-only forms explicitly
  7. Parse composite sections by component