grammar,compiler: Add Support For Fixed-Width Integers
Continuous Integration / Grammar Tests (push) Successful in 4m13s
Continuous Integration / Library Format Tests (push) Successful in 5m17s
Continuous Integration / Library Tests (push) Failing after 8m34s
Continuous Integration / Cli Tests (push) Failing after 4m40s

Distinguishing between signed and unsigned fixed-width integer
types must still be done.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-05-18 06:52:21 -04:00
parent cbebcae20a
commit a7d8fd1304
16 changed files with 328 additions and 48 deletions
+20 -5
View File
@@ -424,19 +424,30 @@ public class P4BooleanValue: P4DataValue {
}
}
public enum BitWidth: Equatable {
case Infinite
case Width(Int)
}
/// A P4 int type
public struct P4Int: P4Type {
public init() {}
let width: BitWidth
public init(_ width: BitWidth = BitWidth.Infinite) {
self.width = width
}
public var description: String {
return "Int"
return "Int (width: \(self.width))"
}
public func eq(rhs: P4Type) -> Bool {
return switch rhs {
case is P4Int: true
case let rrhs as P4Int: rrhs.width == self.width
default: false
}
}
public func def() -> P4DataValue? {
return P4IntValue(withValue: 0)
}
@@ -444,12 +455,16 @@ public struct P4Int: P4Type {
/// An instance of a P4 integer
public class P4IntValue: P4DataValue {
let int_type: P4Int
public func type() -> P4Type {
return P4Int()
return int_type
}
let value: Int
public init(withValue value: Int) {
public init(withValue value: Int, andWidth width: BitWidth = BitWidth.Infinite) {
self.int_type = P4Int(width)
self.value = value
}
+2
View File
@@ -101,6 +101,8 @@ public func Fold<T, A>(input: [T], initial: A, block: (T, A) -> A) -> A {
#externalMacro(module: "Macros", type: "RequireNodesType")
@freestanding(codeItem) public macro SkipUnlessNodeType<N>(node: N, type: String) =
#externalMacro(module: "Macros", type: "SkipUnlessNodeType")
@freestanding(codeItem) public macro SkipUnlessNodesTypes<N>(node: N, types: [String]) =
#externalMacro(module: "Macros", type: "SkipUnlessNodesTypes")
@freestanding(codeItem) public macro MustOr<E, N>(result: E, thing: E?, or: N) =
#externalMacro(module: "Macros", type: "MustOr")
+34 -1
View File
@@ -229,6 +229,38 @@ public struct SkipUnlessNodeType: CodeItemMacro {
}
}
public struct SkipUnlessNodesTypes: CodeItemMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext
) throws -> [CodeBlockItemSyntax] {
let arguments = node.arguments.indices
var arg_index = arguments.startIndex
let node_to_check = node.arguments[arg_index].expression
arg_index = arguments.index(after: arg_index)
guard let expected_types = node.arguments[arg_index].expression.as(ArrayExprSyntax.self) else {
throw MacroError(withMessage: "Node(s) to check must be in an array")
}
arg_index = arguments.index(after: arg_index)
let ifs = expected_types.elements.map { l in
"\(node_to_check).nodeType != \(l.expression)"
}.joined(separator: " && ")
return [
CodeBlockItemSyntax(
"""
if \(raw: ifs) {
return Result.Ok(.none)
}
""")
]
}
}
public struct MustOr: CodeItemMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext
@@ -261,6 +293,7 @@ public struct MustOr: CodeItemMacro {
struct P4Macros: CompilerPlugin {
var providingMacros: [Macro.Type] = [
RequireResult.self, RequireErrorResult.self, UseOkResult.self, UseErrorResult.self,
RequireNodeType.self, SkipUnlessNodeType.self, RequireNodesType.self, MustOr.self,
RequireNodeType.self, SkipUnlessNodeType.self, SkipUnlessNodesTypes.self, RequireNodesType.self,
MustOr.self,
]
}
+35 -3
View File
@@ -101,9 +101,41 @@ extension P4IntValue: CompilableExpression {
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> {
let node = node.child(at: 0)!
#SkipUnlessNodeType<SwiftTreeSitter.Node>(node: node, type: "integer")
if let parsed_int = Int(node.text!) {
return .Ok(P4Value(P4IntValue(withValue: parsed_int)))
#SkipUnlessNodesTypes<SwiftTreeSitter.Node>(
node: node, types: ["integer", "integer_elaborated"])
var bit_width: BitWidth = BitWidth.Infinite
let value_source: String
if node.nodeType == "integer_elaborated" {
let re = /([0-9]+)([ws])([\-0-9]+)/
let integer_components = node.text!.matches(of: re)
if integer_components.isEmpty || integer_components.count > 1 {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Failed to parse elaborated integer: \(node.text!)"))
}
let width_source = "\(integer_components[0].1)"
guard let width = Int(width_source) else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Failed to parse width from elaborated integer: \(width_source)"))
}
/// TODO: Handle signed vs. unsigned.
bit_width = BitWidth.Width(width)
value_source = "\(integer_components[0].3)"
} else {
value_source = node.text!
}
if let parsed_int = Int(value_source) {
return .Ok(P4Value(P4IntValue(withValue: parsed_int, andWidth: bit_width)))
} else {
return .Error(
ErrorWithLocation(
+36 -1
View File
@@ -34,7 +34,42 @@ extension P4Int: CompilableType {
public static func CompileType(
type: SwiftTreeSitter.Node, withContext: CompilerContext
) -> Common.Result<(any Common.P4Type)?> {
return type.text == "int" ? .Ok(P4Int()) : .Ok(.none)
// Drill down, as appropriate.
let base_type_node = type.child(at: 0)!
#SkipUnlessNodeType<SwiftTreeSitter.Node>(
node: base_type_node, type: "baseType")
let type_node = base_type_node.child(at: 0)!
#SkipUnlessNodeType<SwiftTreeSitter.Node>(
node: type_node, type: "int_type")
var walker = Walker(node: type_node)
var int_node: Node? = .none
#MustOr(
result: int_node, thing: walker.getNext(),
or: Result<P4Type?>.Error(
ErrorWithLocation(
sourceLocation: type_node.toSourceLocation(),
withError: "Missing elements in int type declaration")))
// Move passed the keyword.
walker.next()
if let bit_width_node = walker.getNext() {
guard let bit_width = Int(bit_width_node.child(at: 1)!.text!),
bit_width != 0
else {
return .Error(
ErrorWithLocation(
sourceLocation: bit_width_node.toSourceLocation(),
withError: "Could not parse \(bit_width_node.text!) into integer"))
}
return .Ok(P4Int(BitWidth.Width(bit_width)))
}
return .Ok(P4Int())
}
}
+2 -2
View File
@@ -17,6 +17,7 @@
import Common
import P4Lang
import P4Protos
public struct Generated {
let gen: String
@@ -48,13 +49,12 @@ public struct CodeGenerator: LanguageVisitor {
return visitor.start(node, context: generated)
}
/// TODO: Can we generate these implementations somehow?
public typealias Context = Generated
public func visit(
_ v: Program, _ c: VisitorContext<Generated>
) -> Result<VisitorContext<Generated>> {
var result: Result<VisitorContext<Generated>> = Fold(
input: v.types, initial: .Ok(c.next(uc: c.getUserContext().append("[")))
) { (current, acc) in
+1 -1
View File
@@ -283,7 +283,7 @@ import P4Lang
#RequireErrorResult(
Error(
withMessage:
"{57, 10}: Failed to parse a statement element: {57, 1}: Cannot assign value with type Boolean to identifier z with type Int"
"{57, 10}: Failed to parse a statement element: {57, 1}: Cannot assign value with type Boolean to identifier z with type Int (width: Infinite)"
),
Program.Compile(simple_parser_declaration))
)
@@ -182,5 +182,5 @@ import TreeSitterP4
let error = try #UseErrorResult(Program.Compile(simple_parser_declaration))
#expect(error.msg().contains("{29, 12}: Type of expression in return statement (Boolean) is not compatible with function return type (Int)"))
#expect(error.msg().contains("{29, 12}: Type of expression in return statement (Boolean) is not compatible with function return type (Int (width: Infinite))"))
}
@@ -132,7 +132,7 @@ import TreeSitterP4
#RequireErrorResult(
Error(
withMessage:
"Error(s) parsing select cases: {81, 4}: Key expression of type Boolean is not compatible with selector type Int"
"Error(s) parsing select cases: {81, 4}: Key expression of type Boolean is not compatible with selector type Int (width: Infinite)"
),
Program.Compile(simple_parser_declaration)))
}
+1 -1
View File
@@ -147,7 +147,7 @@ import TreeSitterP4
#expect(
#RequireErrorResult<(InstantiatedParserState, ProgramExecution)>(
Error(withMessage: "Cannot call parser: Argument 1's type (Int) is incompatible with the parameter type (Boolean)"),
Error(withMessage: "Cannot call parser: Argument 1's type (Int (width: Infinite)) is incompatible with the parameter type (Boolean)"),
runtime.run(withArguments: args)))
}
+2 -2
View File
@@ -327,7 +327,7 @@ import TreeSitterP4
#expect(
#RequireErrorResult(
Error(
withMessage: "{49, 13}: Failed to parse a statement element: {49, 8}: Cannot assign value of type Int to field yesno of type Boolean"
withMessage: "{49, 13}: Failed to parse a statement element: {49, 8}: Cannot assign value of type Int (width: Infinite) to field yesno of type Boolean"
),
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
)
@@ -457,7 +457,7 @@ import TreeSitterP4
#expect(
#RequireErrorResult(
Error(
withMessage: "{49, 20}: Failed to parse a statement element: {49, 11}: Cannot assign value of type Boolean to field count of type Int"
withMessage: "{49, 20}: Failed to parse a statement element: {49, 11}: Cannot assign value of type Boolean to field count of type Int (width: Infinite)"
),
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
)
+1 -1
View File
@@ -45,5 +45,5 @@ struct StringConvertible: CustomStringConvertible {
@Test func test_result_type_p4value_convertible() async throws {
let result = Result.Ok(P4Value(P4IntValue(withValue: 5)))
#expect("\(result)" == "Ok: Value: 5 of Int type of type Int")
#expect("\(result)" == "Ok: Value: 5 of Int (width: Infinite) type of type Int (width: Infinite)")
}
+105 -11
View File
@@ -18,8 +18,8 @@
import Common
import Foundation
import Macros
import P4Runtime
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
@@ -36,13 +36,15 @@ import TreeSitterP4
transition reject;
}
};
""";
"""
let err = Program.Compile(simple_parser_declaration)
guard case Result.Error(let e) = err else {
assert(false, "Expected an error, but had success")
}
#expect(e.msg().contains("Failed to parse a statement element: Could not parse a P4 type from \(invalid_type_name)"))
#expect(
e.msg().contains(
"Failed to parse a statement element: Could not parse a P4 type from \(invalid_type_name)"))
}
}
@@ -128,6 +130,90 @@ import TreeSitterP4
Program.Compile(simple_parser_declaration)))
}
@Test func test_invalid_type_in_declaration3() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
int<5> specific_width_int = 5;
int unspecific_width_int = specific_width_int;
transition reject;
}
};
"""
let error = try! #UseErrorResult(Program.Compile(simple_parser_declaration))
#expect(
error.msg().contains(
"Cannot initialize specific_width_int (with type Int (width: Width(5))) from expression with type Int (width: Infinite)"
))
}
@Test func test_valid_specific_width_int_type_in_declaration() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
int<5> specific_width_int = 5w5;
int<5> unspecific_width_int = specific_width_int;
transition reject;
}
};
"""
#expect(#RequireOkResult(Program.Compile(simple_parser_declaration)))
}
@Test func test_expression_in_declaration_initializer_specific_width_int_type() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
int<5> specific_width_int = 5w5;
bool where_to = 5w5 == specific_width_int == true;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
// 5w5 == specific_width_int == true
// true == true
// true
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_expression_in_declaration_initializer_specific_width_int_type2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
int<5> specific_width_int = 5w5;
bool where_to = 5w6 == specific_width_int == false;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
// 5w6 == specific_width_int == false
// false == false
// true
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_expression_in_declaration_initializer() async throws {
let simple_parser_declaration = """
parser main_parser() {
@@ -141,7 +227,8 @@ import TreeSitterP4
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let runtime = try #UseOkResult(
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
// 5 == 5 == true
@@ -163,7 +250,8 @@ import TreeSitterP4
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let runtime = try #UseOkResult(
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
// 5 == 5 == true
@@ -185,7 +273,8 @@ import TreeSitterP4
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let runtime = try #UseOkResult(
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
// 6 == 5 == true
@@ -207,7 +296,8 @@ import TreeSitterP4
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let runtime = try #UseOkResult(
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
// 6 == 5 == false
@@ -229,7 +319,8 @@ import TreeSitterP4
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let runtime = try #UseOkResult(
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
// TODO: This test should throw an error.
@@ -253,12 +344,14 @@ import TreeSitterP4
};
"""
var test_types = VarTypeScopes().enter()
test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
test_types = test_types.declare(
identifier: Identifier(name: "ta"),
withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
#expect(
#RequireErrorResult(
Error(
withMessage:
"{49, 22}: Failed to parse a statement element: Cannot initialize where_to (with type Boolean) from expression with type Int"
"{49, 22}: Failed to parse a statement element: Cannot initialize where_to (with type Boolean) from expression with type Int (width: Infinite)"
),
Program.Compile(simple_parser_declaration, withGlobalInstances: test_types)))
}
@@ -275,7 +368,8 @@ import TreeSitterP4
#expect(
#RequireErrorResult(
Error(
withMessage: "{85, 9}: Failed to parse a statement element: {85, 4}: Cannot assign value with type Int to identifier pmtr with type Boolean"
withMessage:
"{85, 9}: Failed to parse a statement element: {85, 4}: Cannot assign value with type Int (width: Infinite) to identifier pmtr with type Boolean"
),
Program.Compile(simple_parser_declaration)))
}
+6 -2
View File
@@ -40,9 +40,12 @@ export default grammar({
// Common - Types
typeRef: $ => choice($.baseType, $.type_identifier),
baseType: $ => choice($.bool, $.error, $.string, $.int, $.bit /* omitting "templated" types" */),
baseType: $ => choice($.bool, $.error, $.string, $.int_type, $.bit /* omitting "templated" types" */),
constructor_parameters: $ => seq('(', optional($.parameter_list), ')'),
int_type: $ => seq($.int, optional($.bit_width)),
bit_width: $ => seq('<', $.integer, '>'),
// Common - Parsers
parserType: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), $.parameters),
@@ -120,7 +123,7 @@ export default grammar({
// Expressions
expression: $ => choice($.grouped_expression, $.simple_expression),
grouped_expression: $ => seq('(', $.expression, ')'),
simple_expression: $ => choice($.identifier, $.integer, $.booleanLiteralExpression, $.string_literal, $.binaryOperatorExpression, $.arrayAccessExpression, $.fieldAccessExpression, $.function_call), // Very limited.
simple_expression: $ => choice($.identifier, $.integer, $.integer_elaborated, $.booleanLiteralExpression, $.string_literal, $.binaryOperatorExpression, $.arrayAccessExpression, $.fieldAccessExpression, $.function_call), // Very limited.
booleanLiteralExpression: $ => choice($.true, $.false),
selectExpression: $ => seq($.select, '(', $.expression, ')', '{', $.selectBody, '}'), // TODO: Should be expression list and not just a single expression
transitionSelectionExpression: $ => choice($.identifier, $.selectExpression),
@@ -211,6 +214,7 @@ export default grammar({
type_identifier: $ => /[A-Za-z_]+/,
string_literal: $ => /"[^"]*"/,
integer: $ => /[0-9]+/,
integer_elaborated: $ => /[0-9]+[ws][-0-9]+/,
annotation_literal: $ => /@[A-Za-z_]+/,
default_keyset: $=> '_',
+62 -1
View File
@@ -46,7 +46,7 @@ parser simple() {
)
=========================
Simple Declaration (int)
Simple Declaration (infinite-precision int)
=========================
parser simple() {
state start {
@@ -73,9 +73,11 @@ parser simple() {
(variableDeclaration
(typeRef
(baseType
(int_type
(int)
)
)
)
(identifier)
)
)
@@ -92,6 +94,59 @@ parser simple() {
)
)
=========================
Simple Declaration (fixed-precision int)
=========================
parser simple() {
state start {
int<55> l;
transition accept;
}
};
---
(p4program
(declaration
(parserDeclaration
(parserType
(parser)
(identifier)
(parameters)
)
(parserStates
(parserState
(state)
(identifier)
(parserStatements
(parserStatement
(variableDeclaration
(typeRef
(baseType
(int_type
(int)
(bit_width
(integer)
)
)
)
)
(identifier)
)
)
)
(parserTransitionStatement
(transition)
(transitionSelectionExpression
(identifier)
)
)
)
)
)
)
)
=========================
Simple Declaration (string)
=========================
@@ -331,9 +386,11 @@ bool functionb(bool a, int b) {
(parameter
(typeRef
(baseType
(int_type
(int)
)
)
)
(identifier)
)
)
@@ -393,9 +450,11 @@ bool functionb(in bool a, out int b, inout string c) {
)
(typeRef
(baseType
(int_type
(int)
)
)
)
(identifier)
)
)
@@ -468,9 +527,11 @@ extern bool functionb(in bool a, out int b, inout string c);
)
(typeRef
(baseType
(int_type
(int)
)
)
)
(identifier)
)
)
@@ -38,9 +38,11 @@ int fun() {
(function_declaration
(typeRef
(baseType
(int_type
(int)
)
)
)
(identifier)
(parameters)
(blockStatement
@@ -382,9 +384,11 @@ parser simple() {
(variableDeclaration
(typeRef
(baseType
(int_type
(int)
)
)
)
(identifier)
(assignment)
(expression