Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Syntax Definition in P4-SpecTec

With the scope of Nano-P4 in mind, let us see how its syntax is expressed in P4-SpecTec. The full syntax is defined in 1-syntax.watsup. Reading it gives a feel for what P4-SpecTec syntax definitions look like at scale, before the type-checking and evaluation rules in later chapters.

Terminals and Non-terminals

Every syntax production in P4-SpecTec is built from two kinds of atoms.

Non-terminals are references to other syntax productions. They appear as lowercase names, for example expression, type, or name.

Terminals are concrete tokens of the language being specified. They appear in two forms:

  • Keyword terminals are written in ALL_CAPS: IF, ELSE, STRUCT, PARSER, CONTROL, and so on. These correspond to reserved keywords in Nano-P4.
  • Punctuation terminals are written with a leading backtick: `(, `), `{, `}, `;, `., `=, and so on. The backtick distinguishes a literal token from the syntax of P4-SpecTec.

For example, the production for an assignment statement:

syntax assignmentStatement = lvalue '=' expression ';'

reads as: an assignment is an lvalue non-terminal, followed by the literal = token, followed by an expression non-terminal, followed by the literal ; token.

Literals

The two literal forms in Nano-P4 are booleans and integers.

syntax booleanLiteral =
  | TRUE
  | FALSE

syntax integerLiteral =
  | nat W int
  | nat S int

TRUE and FALSE are terminals. Integer literals carry two pieces of metadata: a width nat and a value int. The W terminal marks an unsigned bit-string and S a signed integer. These correspond to bit<N> and int<N> literals in source Nano-P4.

Identifiers

Nano-P4 uses two distinct identifier categories:

syntax identifier = _ID text
syntax typeIdentifier = _TID text

identifier carries the ID tag and a text payload; typeIdentifier uses TID. This split mirrors the official P4 grammar, where the lexer distinguishes regular identifiers from type names already declared.

From these two primitives, several name non-terminals are derived:

syntax nonTypeName =
  | identifier
  | APPLY | KEY | ACTIONS | STATE

syntax typeName = typeIdentifier

syntax name = nonTypeName

nonTypeName adds the contextual keywords APPLY, KEY, ACTIONS, and STATE as valid identifiers: they are reserved in some positions but can appear as plain names in others. typeName is just a typeIdentifier wrapped for clarity. name collapses to nonTypeName, which is what most of the spec refers to.

Types

syntax integerType =
  | BIT `< int `>
  | INT `< int `>

syntax baseType =
  | integerType
  | BOOL
  | MATCH_KIND

syntax type =
  | baseType
  | namedType

integerType captures bit<N> and int<N> with the width stored as an int meta-value directly in the syntax tree. baseType bundles integer types with the two keyword types BOOL and MATCH_KIND. type is the union of base types and named types (structs and headers resolved by name).

Parameters

syntax parameter =
  direction type name

syntax direction = _EMPTY | IN | OUT | INOUT

A parameter is a direction, a type, and a name, laid out in sequence. direction has four cases: the three P4 keywords and _EMPTY for the directionless case (parameters with no direction annotation).

The parameterList production handles the empty-or-nonempty split:

syntax parameterList =
  | _EMPTY
  | nonEmptyParameterList

The _EMPTY here is a P4-SpecTec internal sentinel, not a P4 keyword. It is a terminal token in the grammar, but never appears in a real Nano-P4 source file.

syntax nonEmptyParameterList =
  | parameter
  | nonEmptyParameterList ',' parameter

Any sequence production such as nonEmptyParameterList uses left-recursive form, matching the Yacc/Bison style of the P4 grammar.

However, the spec must convert these to right-recursive lists to access elements in order. Therefore, alongside the syntax, the spec defines a helper function to flatten a parameterList into a flat list parameter*:

dec $flatten_parameterList(parameterList) : parameter*
def $flatten_parameterList(_EMPTY) = eps
def $flatten_parameterList(parameter) = [ parameter ]
def $flatten_parameterList(nonEmptyParameterList ',' parameter)
  = $flatten_parameterList(nonEmptyParameterList) ++ [ parameter ]

This pattern, a dec / def pair that recursively accumulates elements into a list, appears throughout 1-syntax.watsup for every list-valued production: nameList, argumentList, statementList, and so on. The type checker and evaluator call these helpers instead of pattern-matching on the recursive list syntax directly.

Expressions

Expressions form the most layered part of the grammar. The spec defines them in named groups before assembling them under expression:

syntax expression =
  | literalExpression
  | referenceExpression
  | unaryExpression
  | binaryExpression
  | memberAccessExpression
  | callExpression
  | parenthesizedExpression

A few sub-productions are worth noting.

Unary and binary expressions encode their operators as separate syntax productions:

syntax unop = '!' | '~' | '-' | '+'

syntax binop =
  | '*' | '+' | '-'
  | '<=' | '>=' | '<' | '>' | '!=' | '=='
  | '&' | '^' | '|' | '&&' | '||'

Member access and call expressions break a mutual recursion problem: expression needs to refer to memberAccessBase, but memberAccessBase must itself refer back to expression. P4-SpecTec resolves this with forward declarations:

syntax memberAccessBase           ;; forward declaration

syntax memberAccessExpression = memberAccessBase '.' member
syntax callExpression = callTarget `( argumentList `)

;; ... expression is now fully defined ...

syntax memberAccessBase = expression
syntax callTarget = namedType

P4-SpecTec requires a syntax declaration before first use, so these are declared with no alternatives and then given their full definition later in the file after expression itself is complete.

L-values

syntax lvalue =
  | referenceExpression
  | lvalue '.' member
  | `( lvalue `)

L-values are a strict subset of expressions: a variable reference, a member access rooted at an l-value, or a parenthesized l-value. The spec keeps lvalue separate from expression so the typechecker can restrict what appears on the left-hand side of an assignment without inspecting expression structure at every assignment site.

Statements

syntax statement =
  | emptyStatement
  | variableDeclaration
  | assignmentStatement
  | callStatement
  | blockStatement
  | conditionalStatement

conditionalStatement requires both branches:

syntax conditionalStatement =
  IF `( expression `) blockStatement ELSE blockStatement

This directly encodes the Nano-P4 restriction from the Scope section: if without else is not allowed.

Unlike P4, variableDeclaration requires an initializer:

syntax initializer = '=' expression

syntax variableDeclaration =
  type name initializer ';'

Declarations

Nano-P4 has the following top-level declaration forms, each with its own production:

syntax declaration =
  | instantiation
  | actionDeclaration
  | matchKindDeclaration
  | externDeclaration
  | parserDeclaration
  | controlDeclaration
  | typeDeclaration

A few representative examples:

syntax actionDeclaration =
  ACTION name `( parameterList `) blockStatement

syntax externObjectDeclaration =
  EXTERN name `{ externMethodPrototypeList `}

syntax controlDeclaration =
  CONTROL name
    `( parameterList `)
    `{ controlLocalDeclarationList APPLY controlBody `}

controlDeclaration places APPLY inside the body braces, which matches the actual P4 syntax.

Parser and control declarations follow the split-body pattern seen in the scope section: local declarations come first, then states (for parsers) or the apply body (for controls).

Putting It Together

At the top level, a Nano-P4 program is a sequence of declarations:

syntax program =
  | _EMPTY
  | program declaration

As with all list productions, a $flatten_program helper converts it to a flat declaration* that the rest of the spec consumes.

With this syntax definition in hand, the typechecker and evaluator can refer to every Nano-P4 construct by name, and the spec stays readable as the rules grow more complex in later chapters.