Initial Array Access Implementation

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-03-13 09:41:04 -04:00
parent 1982fda677
commit 0b4db34177
10 changed files with 266 additions and 11 deletions
+12 -1
View File
@@ -17,10 +17,17 @@
open class ProgramExecution: CustomStringConvertible { open class ProgramExecution: CustomStringConvertible {
public var scopes: ValueScopes = ValueScopes() public var scopes: ValueScopes = ValueScopes()
let initialValues: ValueScopes?
var error: Error? var error: Error?
var debug: DebugLevel = DebugLevel.Error var debug: DebugLevel = DebugLevel.Error
public init() {} public init() {
initialValues = .none
}
public init(withGlobalValues values: ValueScopes) {
initialValues = values
}
open var description: String { open var description: String {
return "Runtime:\nScopes: \(scopes)" return "Runtime:\nScopes: \(scopes)"
@@ -80,6 +87,10 @@ open class ProgramExecution: CustomStringConvertible {
new_pe.scopes = new_scopes new_pe.scopes = new_scopes
return new_pe return new_pe
} }
public func initial_values() -> ValueScopes? {
return self.initialValues
}
} }
public typealias ValueScope = Scope<P4Value> public typealias ValueScope = Scope<P4Value>
+51 -1
View File
@@ -198,8 +198,12 @@ public class P4IntValue: P4Value {
public init(withValue value: Int) { public init(withValue value: Int) {
self.value = value self.value = value
} }
public func access() -> Int {
return self.value
}
public func eq(rhs: P4Value) -> Bool { public func eq(rhs: P4Value) -> Bool {
print("Int value equal.")
guard let int_rhs = rhs as? P4IntValue else { guard let int_rhs = rhs as? P4IntValue else {
return false return false
} }
@@ -251,3 +255,49 @@ public class P4StringValue: P4Value {
public class Packet { public class Packet {
public init() {} public init() {}
} }
/// A P4 array type
public struct P4Array: P4Type {
public static func create() -> any P4Type {
return P4Array()
}
public var description: String {
return "Array"
}
public func eq(rhs: any P4Type) -> Bool {
return switch rhs {
case is P4Array: true
default: false
}
}
}
/// An instance of a P4 array
public class P4ArrayValue: P4Value {
public func type() -> any P4Type {
return P4Array()
}
let value: Array<EvaluatableExpression>
public init(withValue value: Array<EvaluatableExpression>) {
self.value = value
}
public func access(_ index: Int) -> EvaluatableExpression {
return self.value[index]
}
public func eq(rhs: P4Value) -> Bool {
guard let _ = rhs as? P4ArrayValue else {
return false
}
// TODO!!
return true
}
public var description: String {
"\(self.value) of \(self.type()) type"
}
}
+5 -5
View File
@@ -16,10 +16,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
public struct Scope<T>: CustomStringConvertible, Sequence { public struct Scope<T>: CustomStringConvertible, Sequence {
public typealias Element = T public typealias Element = (key: Identifier, value: T)
public typealias Iterator = Dictionary<Identifier, T>.Values.Iterator public typealias Iterator = Dictionary<Identifier, T>.Iterator
public func makeIterator() -> Iterator { public func makeIterator() -> Iterator {
self.symbols.values.makeIterator() self.symbols.makeIterator()
} }
var symbols: [Identifier: T] = Dictionary() var symbols: [Identifier: T] = Dictionary()
@@ -123,8 +123,8 @@ public struct Scopes<T>: CustomStringConvertible, Sequence {
return .Error(Error(withMessage: "Cannot find \(identifier) in lexical scope.")) return .Error(Error(withMessage: "Cannot find \(identifier) in lexical scope."))
} }
public typealias Element = T public typealias Element = (key: Identifier, value: T)
public typealias Iterator = Dictionary<Identifier, T>.Values.Iterator public typealias Iterator = Dictionary<Identifier, T>.Iterator
public func makeIterator() -> Iterator { public func makeIterator() -> Iterator {
scopes.last?.makeIterator() ?? Scope<T>().makeIterator() scopes.last?.makeIterator() ?? Scope<T>().makeIterator()
} }
+65 -1
View File
@@ -110,7 +110,7 @@ struct Expression {
let localElementsParsers: [CompilableExpression.Type] = [ let localElementsParsers: [CompilableExpression.Type] = [
P4BooleanValue.self, P4StringValue.self, P4IntValue.self, TypedIdentifier.self, P4BooleanValue.self, P4StringValue.self, P4IntValue.self, TypedIdentifier.self,
BinaryOperatorExpression.self, BinaryOperatorExpression.self, ArrayAccessExpression.self
] ]
for le_parser in localElementsParsers { for le_parser in localElementsParsers {
@@ -311,3 +311,67 @@ extension BinaryOperatorExpression: CompilableExpression {
withLhs: left_hand_side, withRhs: right_hand_side)) withLhs: left_hand_side, withRhs: right_hand_side))
} }
} }
extension ArrayAccessExpression: CompilableExpression {
static func compile(node: SwiftTreeSitter.Node, withContext context: CompilerContext) -> Common.Result<(any Common.EvaluatableExpression)?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node, EvaluatableExpression?>(
node: expression, type: "arrayAccessExpression")
let array_access_expression_node = expression
var currentChildIdx = 0
var currentChildIdxSafe = 1
var currentChild: Node? = .none
// What is the "name" of the array?
if array_access_expression_node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Malformed array access expression"))
}
currentChild = expression.child(at: currentChildIdx)
#RequireNodeType<Node, EvaluatableExpression?>(
node: currentChild!, type: "expression",
nice_type_name: "array identifier expression")
let array_access_identifier_node = currentChild!
// Check for the [
currentChildIdx = currentChildIdx + 1
currentChildIdxSafe = currentChildIdxSafe + 1
if array_access_expression_node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing [ for array access expression")
)
}
// What is the indexor of the array?
currentChildIdx = currentChildIdx + 1
currentChildIdxSafe = currentChildIdxSafe + 1
if array_access_expression_node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing indexor expression for array access expression")
)
}
currentChild = array_access_expression_node.child(at: currentChildIdx)
#RequireNodeType<Node, EvaluatableExpression?>(
node: currentChild!, type: "expression",
nice_type_name: "array indexor expression")
let array_access_indexor_node = currentChild!
let maybe_array_identifier = Expression.Compile(node: array_access_identifier_node, withContext: context)
guard case Result.Ok(let array_identifier) = maybe_array_identifier else {
return Result.Error(maybe_array_identifier.error()!)
}
let maybe_array_indexor = Expression.Compile(node: array_access_indexor_node, withContext: context)
guard case Result.Ok(let array_indexor) = maybe_array_indexor else {
return Result.Error(maybe_array_indexor.error()!)
}
return .Ok(ArrayAccessExpression(withName: array_identifier, withIndexor: array_indexor))
}
}
+12 -1
View File
@@ -24,6 +24,10 @@ import TreeSitterP4
public struct Program { public struct Program {
public static func Compile(_ source: String) -> Result<P4Lang.Program> { public static func Compile(_ source: String) -> Result<P4Lang.Program> {
return Program.Compile(source, withGlobalTypes: .none)
}
public static func Compile(_ source: String, withGlobalTypes globalTypes: LexicalScopes?) -> Result<P4Lang.Program> {
let maybe_parser = ConfigureP4Parser() let maybe_parser = ConfigureP4Parser()
guard case .Ok(let p) = maybe_parser else { guard case .Ok(let p) = maybe_parser else {
@@ -45,6 +49,11 @@ public struct Program {
var errors: [Error] = Array() var errors: [Error] = Array()
// If the caller gave any global types, add them here.
if let globalTypes = globalTypes {
compilation_context = compilation_context.update(newNames: globalTypes)
}
result?.rootNode?.enumerateNamedChildren { declaration_node in result?.rootNode?.enumerateNamedChildren { declaration_node in
if declaration_node.nodeType != "declaration" { if declaration_node.nodeType != "declaration" {
return return
@@ -185,7 +194,9 @@ public struct Program {
} }
// Any of the types that are in the top-level scope should go into the program! // Any of the types that are in the top-level scope should go into the program!
program.types = Array(compilation_context.names) program.types = Array(compilation_context.names.map() { (_, v) in
v
})
return Result.Ok(program) return Result.Ok(program)
} }
} }
+10
View File
@@ -72,3 +72,13 @@ public struct BinaryOperatorExpression {
self.right = rhs self.right = rhs
} }
} }
public struct ArrayAccessExpression {
public let indexor: EvaluatableExpression
public let name: EvaluatableExpression
public init(withName name: EvaluatableExpression, withIndexor indexor: EvaluatableExpression) {
self.name = name
self.indexor = indexor
}
}
+29
View File
@@ -113,3 +113,32 @@ extension BinaryOperatorExpression: EvaluatableExpression {
return self.evaluator.1 return self.evaluator.1
} }
} }
extension ArrayAccessExpression: EvaluatableExpression {
public func evaluate(execution: Common.ProgramExecution) -> Common.Result<any Common.P4Value> {
let maybe_name = self.name.evaluate(execution: execution)
guard case Result.Ok(let name) = maybe_name else {
return maybe_name
}
let maybe_indexor = self.indexor.evaluate(execution: execution)
guard case Result.Ok(let indexor) = maybe_indexor else {
return maybe_indexor
}
guard let indexor_int = indexor as? P4IntValue else {
return Result.Error(Error(withMessage: "\(indexor) cannot index an array"))
}
guard let array = name as? P4ArrayValue else {
return Result.Error(Error(withMessage: "\(name) does not name an array"))
}
let accessed = array.access(indexor_int.access())
return accessed.evaluate(execution: execution)
}
public func type() -> any Common.P4Type {
return P4Int.create()
}
}
+7
View File
@@ -120,6 +120,13 @@ extension Parser: ParserExecution {
execution = execution.declare(identifier: accept.state().state, withValue: accept) execution = execution.declare(identifier: accept.state().state, withValue: accept)
execution = execution.declare(identifier: reject.state().state, withValue: reject) execution = execution.declare(identifier: reject.state().state, withValue: reject)
// Add initial values to the global scope
if let initial = execution.initial_values() {
for (name, value) in initial {
execution = execution.declare(identifier: name, withValue: value)
}
}
// First, add every state to the scope! // First, add every state to the scope!
for state in self.states.states { for state in self.states.states {
execution = execution.declare(identifier: state.state, withValue: state) execution = execution.declare(identifier: state.state, withValue: state)
+19 -2
View File
@@ -22,16 +22,27 @@ import P4Lang
public struct ParserRuntime: CustomStringConvertible { public struct ParserRuntime: CustomStringConvertible {
public var parser: Parser public var parser: Parser
let initialValues: ValueScopes?
init(parser: Parser) { init(parser: Parser) {
self.parser = parser self.parser = parser
self.initialValues = .none
}
init(parser: Parser, withInitialValues initial: ValueScopes?) {
self.parser = parser
self.initialValues = initial
} }
/// Create a parser runtime from a P4 program /// Create a parser runtime from a P4 program
public static func create(program: P4Lang.Program) -> Result<ParserRuntime> { public static func create(program: P4Lang.Program) -> Result<ParserRuntime> {
return ParserRuntime.create(program: program, withInitialValues: .none)
}
public static func create(program: P4Lang.Program, withInitialValues initial: ValueScopes?) -> Result<ParserRuntime> {
return switch program.starting_parser() { return switch program.starting_parser() {
case .Ok(let parser): case .Ok(let parser):
.Ok(P4Runtime.ParserRuntime(parser: parser)) .Ok(P4Runtime.ParserRuntime(parser: parser, withInitialValues: initial))
case .Error(let error): .Error(error) case .Error(let error): .Error(error)
} }
} }
@@ -39,7 +50,13 @@ public struct ParserRuntime: CustomStringConvertible {
/// Run the P4 parser on a given packet /// Run the P4 parser on a given packet
public func run() -> Result<(ParserState, ProgramExecution)> { public func run() -> Result<(ParserState, ProgramExecution)> {
let (end_state, execution) = parser.execute(execution: ProgramExecution()) let pe = if let initial = initialValues {
ProgramExecution(withGlobalValues: initial)
} else {
ProgramExecution()
}
let (end_state, execution) = parser.execute(execution: pe)
if let error = execution.getError() { if let error = execution.getError() {
return .Error(error) return .Error(error)
} }
@@ -229,3 +229,59 @@ import TreeSitterP4
// false // false
#expect(state_result == P4Lang.reject) #expect(state_result == P4Lang.reject)
} }
@Test func test_array_access() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = ta[1] == 2;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_types = LexicalScopes().enter()
test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array.create())
var test_values = ValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4ArrayValue(withValue: [
P4IntValue(withValue: 1), P4IntValue(withValue: 2), P4IntValue(withValue: 3),
]))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(state_result == P4Lang.accept)
}
@Test func test_array_access2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = ta[0] == 2;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_types = LexicalScopes().enter()
test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4Array.create())
var test_values = ValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4ArrayValue(withValue: [
P4IntValue(withValue: 1), P4IntValue(withValue: 2), P4IntValue(withValue: 3),
]))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(state_result == P4Lang.reject)
}