From d9c8c5aeb06209d95c994c6ff043d2ba3b1763a7 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Thu, 19 Feb 2026 22:50:19 -0500 Subject: [PATCH] Refactor Runtime Signed-off-by: Will Hawkins --- Sources/Common/Execution.swift | 90 ++++- Sources/Common/ProgramTypes.swift | 70 +++- Sources/Common/Protocols.swift | 5 +- Sources/Common/Support.swift | 46 +++ Sources/Lang/Parser.swift | 84 ++--- Sources/Macros/Macros.swift | 19 +- Sources/Parser/Expression.swift | 12 +- Sources/Parser/Parser.swift | 389 +++++++++++++--------- Sources/Runtime/Parser.swift | 213 ++++++++++-- Sources/Runtime/Protocols.swift | 18 +- Sources/Runtime/Runtime.swift | 17 +- Tests/p4rseTests/ParserTests.swift | 23 +- Tests/p4rseTests/RuntimeTests.swift | 85 +---- Tests/p4rseTests/ScopeRuntimeTests.swift | 173 ++++++++++ tree-sitter-p4/grammar.js | 5 +- tree-sitter-p4/test.txt | 23 +- tree-sitter-p4/test/corpus/statements.txt | 64 ++-- 17 files changed, 897 insertions(+), 439 deletions(-) create mode 100644 Tests/p4rseTests/ScopeRuntimeTests.swift diff --git a/Sources/Common/Execution.swift b/Sources/Common/Execution.swift index 20fed16..8bd659e 100644 --- a/Sources/Common/Execution.swift +++ b/Sources/Common/Execution.swift @@ -17,7 +17,8 @@ open class ProgramExecution: CustomStringConvertible { public var scopes: Scopes = Scopes() - var error: Error? + var error: Error? + var debug: DebugLevel = DebugLevel.Error public init() {} @@ -38,10 +39,44 @@ open class ProgramExecution: CustomStringConvertible { npe.error = error return npe } + + public func getDebugLevel() -> DebugLevel { + return self.debug + } + + public func setDebugLevel(_ dl: DebugLevel) -> ProgramExecution { + let pe = self + pe.debug = dl + return pe + } + + open func isDone() -> Bool { + return false + } + + open func setDone() -> ProgramExecution { + // For a bare ProgramExecution, setDone is a noop. + return self + } + + public func enter_scope() -> ProgramExecution { + let new_pe = self + new_pe.scopes = self.scopes.enter() + + return new_pe + } + + public func exit_scope() -> ProgramExecution { + let new_pe = self + new_pe.scopes = self.scopes.exit() + + return new_pe + } + } -public struct Scope: CustomStringConvertible{ +public struct Scope: CustomStringConvertible, Equatable { var variables: [Variable] = Array() public init() {} @@ -59,6 +94,26 @@ public struct Scope: CustomStringConvertible{ } } + public func set(identifier: Identifier, value: P4Value) -> Scope? { + var updated = false + var updated_scope: [Variable] = Array() + for v in variables { + if v == identifier && v.value_type.type().eq(rhs: value.type()) { + updated = true + updated_scope.append(Variable(name: v.name, withValue: value, isConstant: false)) + } else { + updated_scope.append(v) + } + } + var new_scope = Scope() + new_scope.variables = updated_scope + return if updated { + new_scope + } else { + .none + } + } + public func lookup(identifier: Identifier) -> Variable? { for v in variables { if v == identifier { @@ -75,17 +130,26 @@ public struct Scope: CustomStringConvertible{ } } -public struct Scopes: CustomStringConvertible { +public struct Scopes: CustomStringConvertible, Equatable { var scopes: [Scope] = Array() public init() {} - public mutating func enter() { - scopes.append(Scope()) + init(withScopes scopes: [Scope]) { + self.scopes = scopes } - public mutating func exit() { - let _ = scopes.popLast() + public func enter() -> Scopes { + var new_scopes = scopes + new_scopes.append(Scope()) + + return Scopes(withScopes: new_scopes) + } + + public func exit() -> Scopes { + var old_scopes = scopes + _ = old_scopes.popLast() + return Scopes(withScopes: old_scopes) } public var description: String { @@ -125,4 +189,16 @@ public struct Scopes: CustomStringConvertible { scopes.count } } + + public func set(identifier: Identifier, value: P4Value) -> Scopes { + var new_scopes: [Scope] = Array() + for scope in self.scopes { + if let updated_scope = scope.set(identifier: identifier, value: value) { + new_scopes.append(updated_scope) + } else { + new_scopes.append(scope) + } + } + return Scopes(withScopes: new_scopes) + } } diff --git a/Sources/Common/ProgramTypes.swift b/Sources/Common/ProgramTypes.swift index ba54de5..8dc6f5c 100644 --- a/Sources/Common/ProgramTypes.swift +++ b/Sources/Common/ProgramTypes.swift @@ -54,15 +54,20 @@ public class Variable: Identifier { /// A base for all instances of P4 types open class P4ValueBase: P4Value { - public init() {} public func type() -> P4Type { return T.create() } + public func eq(rhs: P4Value) -> Bool { return false } + + public var description: String { + "Value of \(self.type()) type" + } + } extension P4ValueBase: EvaluatableExpression { @@ -85,6 +90,17 @@ public struct P4Struct: P4Type { public init() { self.name = "" } + public var description: String { + return "Struct \(self.name)" + } + + public func eq(rhs: P4Type) -> Bool { + return if let struct_rhs = rhs as? P4Struct { + struct_rhs.name == self.name + } else { + false + } + } } /// The field of a P4 struct @@ -111,6 +127,15 @@ public struct P4Boolean: P4Type { public static func create() -> any P4Type { return P4Boolean() } + public var description: String { + return "Boolean" + } + public func eq(rhs: P4Type) -> Bool { + return switch rhs { + case is P4Boolean: true + default: false + } + } } /// An instance of a P4 boolean @@ -126,14 +151,26 @@ public class P4BooleanValue: P4ValueBase { } return self.value == bool_rhs.value } -} + public override var description: String { + "\(self.value ? "true" : "false") of \(self.type()) type" + } +} /// A P4 int type public struct P4Int: P4Type { public static func create() -> any P4Type { return P4Int() } + public var description: String { + return "Int" + } + public func eq(rhs: P4Type) -> Bool { + return switch rhs { + case is P4Int: true + default: false + } + } } /// An instance of a P4 integer @@ -148,6 +185,9 @@ public class P4IntValue: P4ValueBase { } return self.value == int_rhs.value } + public override var description: String { + "\(self.value) of \(self.type()) type" + } } /// A P4 string type @@ -155,6 +195,15 @@ public struct P4String: P4Type { public static func create() -> any P4Type { return P4String() } + public var description: String { + return "String" + } + public func eq(rhs: P4Type) -> Bool { + return switch rhs { + case is P4String: true + default: false + } + } } /// An instance of a P4 string public class P4StringValue: P4ValueBase { @@ -168,22 +217,9 @@ public class P4StringValue: P4ValueBase { } return self.value == string_rhs.value } -} -/// A P4 value (with a type) -public struct Value: CustomStringConvertible, Equatable { - public var type: P4Type - public var value: P4Value - - public init(withValue value: P4Value, andType type: P4Type) { - self.value = value - self.type = type - } - public var description: String { - return "\(self.value) of \(self.type)" - } - public static func == (lhs: Value, rhs: Value) -> Bool { - return lhs.value.eq(rhs: rhs.value) + public override var description: String { + "\(self.value) of \(self.type()) type" } } diff --git a/Sources/Common/Protocols.swift b/Sources/Common/Protocols.swift index a97d6e7..44896fb 100644 --- a/Sources/Common/Protocols.swift +++ b/Sources/Common/Protocols.swift @@ -31,11 +31,12 @@ public protocol EvaluatableParserStatement { func evaluate(execution: ProgramExecution) -> ProgramExecution } -public protocol P4Type { +public protocol P4Type: CustomStringConvertible { static func create() -> P4Type + func eq(rhs: P4Type) -> Bool } -public protocol P4Value { +public protocol P4Value: CustomStringConvertible { func type() -> P4Type func eq(rhs: P4Value) -> Bool } diff --git a/Sources/Common/Support.swift b/Sources/Common/Support.swift index bcfd436..667baf9 100644 --- a/Sources/Common/Support.swift +++ b/Sources/Common/Support.swift @@ -15,6 +15,52 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +public enum DebugLevel { + case Trace + case Verbose + case Debug + case Error + + func value() -> UInt8 { + return switch self { + case DebugLevel.Trace: 3 + case DebugLevel.Verbose: 2 + case DebugLevel.Debug: 1 + case DebugLevel.Error: 0 + } + } + public func isTrace() -> Bool { + if self.value() >= DebugLevel.Trace.value() { + return true + } + return false + } + public func isVerbose() -> Bool { + if self.value() >= DebugLevel.Verbose.value() { + return true + } + return false + } + public func isDebug() -> Bool { + if self.value() >= DebugLevel.Debug.value() { + return true + } + return false + } + public func isError() -> Bool { + return true + } + + public var description: String { + return switch self { + case DebugLevel.Trace: "Trace" + case DebugLevel.Verbose: "Verbose" + case DebugLevel.Debug: "Debug" + case DebugLevel.Error: "Error" + } + } +} + public struct Error: Equatable { public private(set) var msg: String diff --git a/Sources/Lang/Parser.swift b/Sources/Lang/Parser.swift index 9a8c16c..4f58b35 100644 --- a/Sources/Lang/Parser.swift +++ b/Sources/Lang/Parser.swift @@ -25,6 +25,16 @@ public struct LocalElement { } +public struct ParserAssignmentStatement { + public let lvalue: Identifier + public let value: P4Value + + public init(withLValue lvalue: Identifier, withValue value: P4Value) { + self.lvalue = lvalue + self.value = value + } +} + public struct KeysetExpression { public let key: EvaluatableExpression public let next_state_name: String @@ -63,7 +73,6 @@ public struct ParserTransitionSelectExpression { return ParserTransitionSelectExpression( withSelector: self.selector, withKeysetExpressions: new_kse) } - } public struct ParserTransitionStatement { @@ -93,7 +102,7 @@ public struct VariableDeclarationStatement { } } -public class ParserState: Equatable, CustomStringConvertible { +public class ParserState: Equatable, CustomStringConvertible, Comparable { public private(set) var state_name: String public private(set) var local_elements: [EvaluatableParserStatement] @@ -101,6 +110,13 @@ public class ParserState: Equatable, CustomStringConvertible { public private(set) var transition: ParserTransitionStatement? public private(set) var next_state: ParserState? + public static func < (lhs: ParserState, rhs: ParserState) -> Bool { + // If lhs transitions to rhs, then return true. Otherwise, return false. + + // TODO!! + return false + } + public var description: String { return "Name: \(state_name)" } @@ -112,13 +128,13 @@ public class ParserState: Equatable, CustomStringConvertible { /// Construct a ParserState public init( name: String, withLocalElements localElements: [EvaluatableParserStatement]?, - withStatements statements: [EvaluatableParserStatement]?, + withStatements stmts: [EvaluatableParserStatement]?, withTransition transitionStatement: ParserTransitionStatement ) { state_name = name transition = transitionStatement local_elements = localElements ?? Array() - self.statements = statements ?? Array() + statements = stmts ?? Array() } public func semantic_check(states: ParserStates) -> Bool { @@ -126,22 +142,6 @@ public class ParserState: Equatable, CustomStringConvertible { return self == accept || self == reject } - if let transition_select_expression = transition.transition_expression { - var updated_tse = ParserTransitionSelectExpression( - withSelector: transition_select_expression.selector, withKeysetExpressions: []) - - for kse in transition_select_expression.keyset_expressions { - guard let next_state = states.find(withName: kse.next_state_name) else { - return false - } - let new_kse = KeysetExpression( - withKey: kse.key, withNextStateName: kse.next_state_name, withNextState: next_state) - updated_tse = updated_tse.append_checked_kse(kse: new_kse) - } - self.transition = ParserTransitionStatement(withTransitionExpression: updated_tse) - return true - } - if let next_state_name = transition.next_state_name, let next_state = states.find(withName: next_state_name) { @@ -174,6 +174,9 @@ public class ParserState: Equatable, CustomStringConvertible { } } +nonisolated(unsafe) public let accept: ParserState = ParserState(name: "accept") +nonisolated(unsafe) public let reject: ParserState = ParserState(name: "reject") + public struct ParserStates { public var states: [ParserState] = Array() @@ -221,8 +224,6 @@ public struct ParserStates { } } -nonisolated(unsafe) public let accept: ParserState = ParserState(name: "accept") -nonisolated(unsafe) public let reject: ParserState = ParserState(name: "reject") public struct Parser: P4Type { public var states: ParserStates @@ -250,41 +251,16 @@ public struct Parser: P4Type { public func semantic_check() -> Result<()> { return self.states.semantic_check() } -} -public class ParserInstance: ProgramExecution { - - public var state: ParserState - - private init(state: ParserState) { - self.state = state - super.init() + public var description: String { + return "Parser" } - public static func create(_ parser: Parser) -> Result { - - var augmented_parser = Parser(withName: parser.name) - augmented_parser.states = parser.states.append(state: accept).append(state: reject) - - if case .Error(let e) = augmented_parser.semantic_check() { - return .Error(e) + public func eq(rhs: P4Type) -> Bool { + return if let parser_rhs = rhs as? Parser { + self.name == parser_rhs.name + } else { + false } - - guard let start_state = augmented_parser.findStartState() else { - return Result.Error(Error(withMessage: "Could not find the start state")) - } - let new = ParserInstance(state: start_state) - - return Result.Ok(new) - } - - public func transition(toNextState state: ParserState) -> ParserInstance { - let next = self - next.state = state - return next - } - - public override var description: String { - return "Execution: \(super.description)\nCurrent State: \(state)" } } diff --git a/Sources/Macros/Macros.swift b/Sources/Macros/Macros.swift index a0ec8f5..4ffdfc9 100644 --- a/Sources/Macros/Macros.swift +++ b/Sources/Macros/Macros.swift @@ -31,11 +31,11 @@ public struct UseOkResult: ExpressionMacro { return """ { - if case Result.Ok(let __runtime) = \(argument) { - return __runtime - } else { - print("Oh no") - throw Require.Error.UnexpectedResult + switch \(argument) { + case Result.Ok(let __good): return __good + case Result.Error(let __error): + print("Unexpected result: \\(__error)") + throw Require.Error.UnexpectedResult } }() """ @@ -61,10 +61,11 @@ public struct RequireResult: ExpressionMacro { return """ { - if case Result.Ok(_) = \(argument) { - true - } else { - false + switch \(argument) { + case Result.Ok(let _): return true + case Result.Error(let __error): + print("Unexpected result: \\(__error)") + return false } }() """ diff --git a/Sources/Parser/Expression.swift b/Sources/Parser/Expression.swift index c2d6125..7aeb6ed 100644 --- a/Sources/Parser/Expression.swift +++ b/Sources/Parser/Expression.swift @@ -15,19 +15,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// expression: $ => choice($.identifier, $.integer, $.true, $.false, $.string_literal), // Very limited. - import Common import SwiftTreeSitter import TreeSitterP4 protocol ParseableEvaluatableExpression { - static func parse(node: Node, inTree tree: MutableTree) -> Result + static func parse(node: Node, inTree tree: MutableTree, withScopes scopes: Scopes) -> Result } extension Identifier: ParseableEvaluatableExpression { static func parse( - node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree + node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree, withScopes scopes: Scopes ) -> Result { guard @@ -52,7 +50,7 @@ extension Identifier: ParseableEvaluatableExpression { extension P4BooleanValue: ParseableEvaluatableExpression { static func parse( - node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree + node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree, withScopes scopes: Scopes ) -> Result { guard @@ -91,14 +89,14 @@ extension P4BooleanValue: ParseableEvaluatableExpression { } struct Expression { - public static func Parse(node: Node, inTree: MutableTree) -> Result { + public static func Parse(node: Node, inTree: MutableTree, withScopes scopes: Scopes) -> Result { let localElementsParsers: [ParseableEvaluatableExpression.Type] = [ P4BooleanValue.self, Identifier.self, ] for le_parser in localElementsParsers { if case Result.Ok(.some(let parsed)) = le_parser.parse( - node: node, inTree: inTree) + node: node, inTree: inTree, withScopes: scopes) { return .Ok(parsed) } diff --git a/Sources/Parser/Parser.swift b/Sources/Parser/Parser.swift index 48c8a0f..bfc5c0b 100644 --- a/Sources/Parser/Parser.swift +++ b/Sources/Parser/Parser.swift @@ -25,13 +25,15 @@ import TreeSitterP4 let p4lang = Language(tree_sitter_p4()) public protocol ParseableParserStatement { - static func Parse(node: Node, inTree tree: MutableTree) -> Result + static func Parse( + node: Node, inTree tree: MutableTree, withScope scopes: Scopes + ) -> Result<(EvaluatableParserStatement?, Scopes)> } extension ExpressionStatement: ParseableParserStatement { public static func Parse( - node: Node, inTree tree: MutableTree - ) -> Result { + node: Node, inTree tree: MutableTree, withScope scopes: Scopes + ) -> Result<(EvaluatableParserStatement?, Scopes)> { guard let parser_state_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -39,36 +41,99 @@ extension ExpressionStatement: ParseableParserStatement { "(expressionStatement (expression) @expression)" ).data(using: String.Encoding.utf8)!) else { - return Result.Ok(.none) + return Result.Ok((.none, scopes)) } let qr = parser_state_query.execute(node: node, in: tree) - let query_result = qr.next()! + guard let query_result = qr.next() else { + return Result.Ok((.none, scopes)) + } + let expression_capture = query_result.captures(named: "expression") if !expression_capture.isEmpty { // TODO: Actually create an ExpressionStatement - return Result.Ok(ExpressionStatement()) + return Result.Ok((ExpressionStatement(), scopes)) + } + return Result.Ok((.none, scopes)) + } +} + +extension ParserAssignmentStatement: ParseableParserStatement { + public static func Parse( + node: Node, inTree tree: MutableTree, withScope scopes: Scopes + ) -> Result<(EvaluatableParserStatement?, Scopes)> { + + guard + let parser_state_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(assignmentStatement (expression) @lvalue (assignment) (expression) @value)" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Ok((.none, scopes)) + } + + let qr = parser_state_query.execute(node: node, in: tree) + let parser_declaration = qr.next()! + + let lvalue_capture = parser_declaration.captures(named: "lvalue") + let value_capture = parser_declaration.captures(named: "value") + + // There must be a type name and a variable name + guard !lvalue_capture.isEmpty, + !value_capture.isEmpty, + let lvalue_expression_raw = lvalue_capture[0].node.text, + let value_capture_raw = value_capture[0].node.text + else { + return Result.Error( + Error(withMessage: "Could not parse a parser assignment statement")) + } + + let lvalue_identifier = Identifier(name: lvalue_expression_raw) + + let value = + if !value_capture_raw.isEmpty { + value_capture_raw + } else { + "" + } + + guard case Result.Ok(let declared_value) = scopes.evaluate(identifier: lvalue_identifier) else { + return Result.Error( + Error(withMessage: "Cannot assign to variable not in scope")) + } + + return switch Parser.ParseValueType(type: declared_value.type(), withValue: value) { + case Result.Ok(let value_type): + Result.Ok( + (ParserAssignmentStatement(withLValue: lvalue_identifier, withValue: value_type), scopes)) + case Result.Error(let e): + Result.Error( + Error( + withMessage: + "\(declared_value) has type \(declared_value.type()) but rvalue has mismatched type (\(e))")) } - return Result.Ok(.none) } } extension VariableDeclarationStatement: ParseableParserStatement { public static func Parse( - node: Node, inTree tree: MutableTree - ) -> Result { + node: Node, inTree tree: MutableTree, withScope scopes: Scopes + ) -> Result<(EvaluatableParserStatement?, Scopes)> { guard let parser_state_query = try? SwiftTreeSitter.Query( language: p4lang, data: String( - "((annotations)? (typeRef) @type-name variable_name: (identifier) @identifier ((assignment) (expression) @value)?)" + "(variableDeclaration (annotations)? (typeRef) @type-name variable_name: (identifier) @identifier ((assignment) (expression) @value)?)" ).data(using: String.Encoding.utf8)!) else { - return Result.Ok(.none) + return Result.Ok((.none, scopes)) } let qr = parser_state_query.execute(node: node, in: tree) - let parser_declaration = qr.next()! + guard let parser_declaration = qr.next() else { + return .Ok((.none, scopes)) + } let type_name_capture = parser_declaration.captures(named: "type-name") let variable_name_capture = parser_declaration.captures(named: "identifier") @@ -80,7 +145,8 @@ extension VariableDeclarationStatement: ParseableParserStatement { let variable_name = variable_name_capture[0].node.text, let type_name = type_name_capture[0].node.text else { - return Result.Error(Error(withMessage: "Could not parse a parser declaration")) + return Result.Error( + Error(withMessage: "Could not parse a parser variable declaration statement")) } let value = @@ -90,11 +156,21 @@ extension VariableDeclarationStatement: ParseableParserStatement { "" } - return switch Parser.ParseValueType(type: type_name, withValue: value) { + guard case .Ok(let p4_type) = Parser.ParseBasicType(type: type_name) else { + return Result.Error( + Error(withMessage: "Could not parse a P4 type from \(type_name)")) + } + + return switch Parser.ParseValueType(type: p4_type, withValue: value) { case Result.Ok(let value_type): + // This scope should have an additional variable in scope. Result.Ok( - VariableDeclarationStatement( - withVariable: Variable(name: variable_name, withValue: value_type, isConstant: false))) + ( + VariableDeclarationStatement( + withVariable: Variable(name: variable_name, withValue: value_type, isConstant: false)), + scopes.declare( + variable: Variable(name: variable_name, withValue: value_type, isConstant: false)) + )) case Result.Error(let e): Result.Error(e) } @@ -102,13 +178,23 @@ extension VariableDeclarationStatement: ParseableParserStatement { } public struct Parser { - static func ParseValueType(type: String, withValue value: String) -> Result { + static func ParseBasicType(type: String) -> Result { if type == "bool" { + return .Ok(P4Boolean.create()) + } else if type == "string" { + return .Ok(P4String.create()) + } else if type == "int" { + return .Ok(P4Int.create()) + } + return Result.Error(Error(withMessage: "Type name not recognized")) + } + + static func ParseValueType(type: P4Type, withValue value: String) -> Result { + if type.eq(rhs: P4Boolean.create()) { // Default if value == "" { return .Ok(P4BooleanValue(withValue: false)) } - if value == "true" { return .Ok(P4BooleanValue(withValue: true)) } else if value == "false" { @@ -116,10 +202,10 @@ public struct Parser { } return .Error(Error(withMessage: "Cannot convert \(value) into boolean value")) - } else if type == "string" { + } else if type.eq(rhs: P4String.create()) { return .Ok(P4StringValue.init(withValue: value)) - } else if type == "int" { + } else if type.eq(rhs: P4Int.create()) { // Default if value == "" { return .Ok(P4IntValue.init(withValue: 0)) @@ -137,101 +223,51 @@ public struct Parser { public struct P4Parser { static func LocalElements( - node: Node, inTree tree: MutableTree - ) -> Result<[EvaluatableParserStatement]> { - - guard - let parser_le_statement_query = try? SwiftTreeSitter.Query( - language: p4lang, - data: String( - "(parserLocalElement) @parser-local-element" - ).data(using: String.Encoding.utf8)!) - else { - return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) - } - + node: Node, inTree tree: MutableTree, withScope scopes: Scopes + ) -> Result<(EvaluatableParserStatement, Scopes)> { let localElementsParsers: [ParseableParserStatement.Type] = [ VariableDeclarationStatement.self ] - var localElements: [EvaluatableParserStatement] = Array() - - let qr = parser_le_statement_query.execute(node: node, in: tree) - for raw_le_statement in qr { - let raw_le_statement_capture = raw_le_statement.captures(named: "parser-local-element") - var parsed_le_statement: EvaluatableParserStatement? = .none - - for le_parser in localElementsParsers { - if case Result.Ok(.some(let parsed)) = le_parser.Parse( - node: raw_le_statement_capture[0].node, inTree: tree) - { - parsed_le_statement = parsed - break - } - } - - if let le_statement = parsed_le_statement { - localElements.append(le_statement) - } else { - // There were no parseable statements. - return Result.Error( - Error(withMessage: "Failed to parse a local element: \(raw_le_statement)")) + for local_element_parser in localElementsParsers { + if case Result.Ok((.some(let parsed), let parsed_updated_scopes)) = + local_element_parser.Parse( + node: node, inTree: tree, withScope: scopes) + { + return Result.Ok((parsed, parsed_updated_scopes)) } } - return Result.Ok(localElements) + return Result.Error( + Error( + withMessage: + "Failed to parse any local elements from specified local elements: \(node.text!)") + ) } static func Statements( - node: Node, inTree tree: MutableTree - ) -> Result<[EvaluatableParserStatement]> { - - guard - let parser_statement_query = try? SwiftTreeSitter.Query( - language: p4lang, - data: String( - "(parserStatement) @parser-statement" - ).data(using: String.Encoding.utf8)!) - else { - return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) - } - + node: Node, inTree tree: MutableTree, withScope scopes: Scopes + ) -> Result<(EvaluatableParserStatement, Scopes)> { let statementParsers: [ParseableParserStatement.Type] = [ - ExpressionStatement.self, VariableDeclarationStatement.self, + ExpressionStatement.self, VariableDeclarationStatement.self, ParserAssignmentStatement.self ] - var statements: [EvaluatableParserStatement] = Array() - - let qr = parser_statement_query.execute(node: node, in: tree) - for raw_statement in qr { - let raw_statement_capture = raw_statement.captures(named: "parser-statement") - - var parsed_statement: EvaluatableParserStatement? = .none - - // Iterate through statement parsers and give each one a chance. - for parser in statementParsers { - if case Result.Ok(.some(let parsed)) = parser.Parse( - node: raw_statement_capture[0].node, inTree: tree) - { - parsed_statement = parsed - break - } + // Iterate through statement parsers and give each one a chance. + for parser in statementParsers { + if case Result.Ok((.some(let parsed), let updatedScopes)) = parser.Parse( + node: node, inTree: tree, withScope: scopes) + { + return .Ok((parsed, updatedScopes)) } - if let statement = parsed_statement { - statements.append(statement) - } else { - // There were no parseable statements. - return Result.Error( - Error(withMessage: "Failed to parse a statement element: \(raw_statement)")) - } } - return Result.Ok(statements) + return Result.Error( + Error(withMessage: "Failed to parse a statement element: \(node.text!)")) } static func TransitionKeysetExpression( - node: Node, inTree tree: MutableTree - ) -> Result<[KeysetExpression]> { + node: Node, inTree tree: MutableTree, withScopes scopes: Scopes + ) -> Result<([KeysetExpression], Scopes)> { guard let transition_selection_expression_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -248,24 +284,25 @@ public struct Parser { for expression in qr { let next_state_name = expression.captures[1].node.text! - if case .Error(let e) = Expression.Parse(node: expression.captures[0].node, inTree: tree) - .map(block: { expression in - kses.append( - KeysetExpression( - withKey: expression, withNextStateName: next_state_name)) - return .Ok(expression) - }) - { + if case .Error(let e) = Expression.Parse( + node: expression.captures[0].node, inTree: tree, withScopes: scopes + ) + .map(block: { expression in + kses.append( + KeysetExpression( + withKey: expression, withNextStateName: next_state_name)) + return .Ok(expression) + }) { return .Error(e) } } - return .Ok(kses) + return .Ok((kses, scopes)) } static func TransitionSelectExpression( - node: Node, inTree tree: MutableTree - ) -> Result { + node: Node, inTree tree: MutableTree, withScope scopes: Scopes + ) -> Result<(ParserTransitionStatement?, Scopes)> { guard let transition_selection_expression_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -285,21 +322,26 @@ public struct Parser { let selector = query_result.captures(named: "selector") let body = query_result.captures(named: "body") - return Expression.Parse(node: selector[0].node, inTree: tree).map { expression in - return switch TransitionKeysetExpression(node: body[0].node, inTree: tree) { - case .Ok(let kse): - Result.Ok( - ParserTransitionStatement( - withTransitionExpression: ParserTransitionSelectExpression( - withSelector: expression, withKeysetExpressions: kse))) - case .Error(let e): Result.Error(e) + return Expression.Parse(node: selector[0].node, inTree: tree, withScopes: scopes).map { + expression in + return + switch TransitionKeysetExpression(node: body[0].node, inTree: tree, withScopes: scopes) + { + case .Ok((let kse, let newScopes)): + Result<(ParserTransitionStatement?, Scopes)>.Ok( + ( + ParserTransitionStatement( + withTransitionExpression: ParserTransitionSelectExpression( + withSelector: expression, withKeysetExpressions: kse)), newScopes + )) + case .Error(let e): Result.Error(e) } } } static func TransitionStatement( - node: Node, inTree tree: MutableTree - ) -> Result { + node: Node, inTree tree: MutableTree, withScope scopes: Scopes + ) -> Result<(ParserTransitionStatement?, Scopes)> { guard let next_state_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -314,18 +356,21 @@ public struct Parser { if let next_state_result = qr.next() { let transition_capture = next_state_result.captures(named: "next-state") - return .Ok(ParserTransitionStatement(withNextState: transition_capture[0].node.text!)) + return .Ok( + (ParserTransitionStatement(withNextState: transition_capture[0].node.text!), scopes)) } - return TransitionSelectExpression(node: node, inTree: tree) + return TransitionSelectExpression(node: node, inTree: tree, withScope: scopes) } - static func State(node: Node, inTree tree: MutableTree) -> Result { + static func State( + node: Node, inTree tree: MutableTree, withScopes scopes: Scopes + ) -> Result<(ParserState, Scopes)> { guard let parser_state_query = try? SwiftTreeSitter.Query( language: p4lang, data: String( - "(parserState (state) (identifier) @state-name (parserLocalElements)? @state-local-elements (parserStatements)? @state-statements (parserTransitionStatement) @transition)" + "(parserState (state) (identifier) @state-name (parserLocalElements ((parserLocalElement) @state-local-element (semicolon))*)* (parserStatements ((parserStatement) @state-statement (semicolon))*)* (parserTransitionStatement) @transition)" ).data(using: String.Encoding.utf8)!) else { return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) @@ -337,54 +382,74 @@ public struct Parser { let transition_capture = parser_declaration.captures(named: "transition") let state_name_capture = parser_declaration.captures(named: "state-name") - let state_le_capture = parser_declaration.captures(named: "state-local-elements") - let statements_capture = parser_declaration.captures(named: "state-statements") + let state_le_capture = parser_declaration.captures(named: "state-local-element") + let statements_capture = parser_declaration.captures(named: "state-statement") - // There must be a state name and there must be a transition statement. + // TODO: Now that scopes are involved, doing this out of order will not work! guard !state_name_capture.isEmpty, !transition_capture.isEmpty, let parsed_state_name = state_name_capture[0].node.text, - case .Ok(let transition_statement) = TransitionStatement( - node: transition_capture[0].node, inTree: tree) + case .Ok((let transition_statement, (var newStateScopes))) = TransitionStatement( + node: transition_capture[0].node, inTree: tree, withScope: scopes) else { return Result.Error(Error(withMessage: "Could not parse a parser declaration")) } - let maybe_parsed_les = - if !state_le_capture.isEmpty { - LocalElements(node: state_le_capture[0].node, inTree: tree) - } else { - Result.Ok([EvaluatableParserStatement]()) - } + var parsed_les: [EvaluatableParserStatement] = Array() + var parse_err: Error? = .none - guard case Result<[EvaluatableParserStatement]>.Ok(let parsed_les) = maybe_parsed_les else { - return Result.Error(maybe_parsed_les.error()!) + for state_le in state_le_capture { + state_le.node.enumerateChildren { node in + switch LocalElements( + node: node, inTree: tree, withScope: newStateScopes) + { + case .Ok((let le, let le_parsed_scopes)): + newStateScopes = le_parsed_scopes + parsed_les.append(le) + case .Error(let e): + parse_err = e + } + } } - let maybe_parsed_statements = - if !statements_capture.isEmpty { - Statements(node: statements_capture[0].node, inTree: tree) - } else { - Result.Ok([EvaluatableParserStatement]()) - } - guard - case Result<[EvaluatableParserStatement]>.Ok(let parsed_statements) = - maybe_parsed_statements - else { - return Result.Error(maybe_parsed_statements.error()!) + if let parse_err = parse_err { + return Result.Error(parse_err) + } + + var parsed_s: [EvaluatableParserStatement] = Array() + + if !statements_capture.isEmpty { + for statement in statements_capture { + statement.node.enumerateChildren { node in + switch Statements( + node: node, inTree: tree, withScope: newStateScopes) + { + case .Ok((let le, let le_parsed_scopes)): + newStateScopes = le_parsed_scopes + parsed_s.append(le) + case .Error(let e): + parse_err = e + } + } + } + } + + if let parse_err = parse_err { + return Result.Error(parse_err) } - // TODO: Validate that there is only one! return Result.Ok( - ParserState( - name: parsed_state_name, withLocalElements: parsed_les, - withStatements: parsed_statements, - withTransition: transition_statement)) + ( + ParserState( + name: parsed_state_name, withLocalElements: parsed_les, + withStatements: parsed_s, + withTransition: transition_statement!), newStateScopes + )) } } static func Parser( - withName name: Identifier, node: Node, inTree tree: MutableTree - ) -> Result { + withName name: Identifier, node: Node, inTree tree: MutableTree, withScopes scopes: Scopes + ) -> Result<(Lang.Parser, Scopes)> { guard let parser_state_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -405,10 +470,14 @@ public struct Parser { var error: Error? = .none + var parser_scopes = scopes + // TODO: Assert that there is only one. captures[0].node.enumerateChildren { parser_state in - switch P4Parser.State(node: parser_state, inTree: tree) { - case Result.Ok(let state): parser.states = parser.states.append(state: state) + switch P4Parser.State(node: parser_state, inTree: tree, withScopes: scopes) { + case Result.Ok(let (state, new_parser_scopes)): + parser.states = parser.states.append(state: state) + parser_scopes = new_parser_scopes case Result.Error(let e): error = e } } @@ -416,7 +485,8 @@ public struct Parser { if let error = error { return .Error(error) } - return Result.Ok(parser) + + return Result.Ok((parser, parser_scopes)) } public static func Program(_ source: String) -> Result { @@ -449,14 +519,19 @@ public struct Parser { var program = Lang.Program() + // Set up a lexical scope for parsing. + var program_scope = Scopes().enter() + let parser_qc = parser_declaration_query.execute(in: tree) for parser_declaration in parser_qc { switch Parser( withName: Identifier(name: parser_declaration.nodes[0].text!), - node: parser_declaration.nodes[1], inTree: tree) + node: parser_declaration.nodes[1], inTree: tree, withScopes: program_scope) { - case Result.Ok(let parser): program.types.append(parser) + case Result.Ok((let parser, let new_program_scope)): + program.types.append(parser) + program_scope = new_program_scope case Result.Error(let error): return Result.Error(error) } } diff --git a/Sources/Runtime/Parser.swift b/Sources/Runtime/Parser.swift index 30d4af9..b26be77 100644 --- a/Sources/Runtime/Parser.swift +++ b/Sources/Runtime/Parser.swift @@ -18,48 +18,197 @@ import Common import Lang -extension ParserState: EvaluatableParserTransition { - public func evaluate(execution: ProgramExecution) -> (ParserState, ProgramExecution) { - var currentExecution = execution +extension ParserAssignmentStatement: EvaluatableParserStatement { + public func evaluate(execution: ProgramExecution) -> ProgramExecution { + let updated_scopes = execution.scopes.set(identifier: self.lvalue, value: self.value) + + execution.scopes = updated_scopes + + return execution + } +} + +public struct ParserStateDirectTransition: ParserStateInstance { + public let currrent_state: ParserState + public let next_state: ParserStateInstance + + public func execute( + program: Common.ProgramExecution + ) -> (any ParserStateInstance, Common.ProgramExecution) { + var program = program.exit_scope() + program = program.enter_scope() + + for local_element in currrent_state.local_elements { + program = local_element.evaluate(execution: program) + } + + for statement in currrent_state.statements { + program = statement.evaluate(execution: program) + } + + return (self.next_state, program) + } + + public func done() -> Bool { + return false + } + + public func current() -> Lang.ParserState { + return currrent_state + } + +} + +public struct ParserStateNoTransition: ParserStateInstance { + public let currrent_state: ParserState + public func execute( + program: Common.ProgramExecution + ) -> (any ParserStateInstance, Common.ProgramExecution) { + let program = program.exit_scope() + return (self, program) + } + + public func done() -> Bool { + return true + } + + public func current() -> Lang.ParserState { + return currrent_state + } +} + +public struct ParserStateSelectTransition: ParserStateInstance { + public func execute( + program: Common.ProgramExecution + ) -> (any ParserStateInstance, Common.ProgramExecution) { + // Otherwise, we exit the scope from the previous state and enter a new one! + var program = program.exit_scope() + program = program.enter_scope() // First, evaluate the local elements. - for local_element in local_elements { - currentExecution = local_element.evaluate(execution: currentExecution) + for local_element in currrent_state.local_elements { + program = local_element.evaluate(execution: program) } // Then, evaluate the statements. - for statement in statements { - currentExecution = statement.evaluate(execution: currentExecution) + for statement in currrent_state.statements { + program = statement.evaluate(execution: program) } - if direct_transition() { - return (next_state!, currentExecution) - } - if let transition_expression = self.transition, - let transition_select_expression = transition_expression.transition_expression - { - return transition_select_expression.evaluate(execution: currentExecution) - } - return (reject, currentExecution) - } -} - -extension ParserTransitionSelectExpression: EvaluatableParserTransition { - func evaluate(execution: Common.ProgramExecution) -> (Lang.ParserState, Common.ProgramExecution) { - // First, evaluate the selector. - - switch self.selector.evaluate(execution: execution) { + switch self.selector.evaluate(execution: program) { case .Ok(let selector_value): - for kse in self.keyset_expressions { - if case .Ok(let kse_key) = kse.key.evaluate(execution: execution), - kse_key.eq(rhs: selector_value) - { - return (kse.next_state!, execution) - } + for (key, target) in zip(self.keys, self.states) { + if case .Ok(let kse_key) = key.evaluate(execution: program), + kse_key.eq(rhs: selector_value) + { + return (target, program) } - case .Error(let e): return (reject, execution.setError(error: e)) + } + return ( + self, program.setError(error: Error(withMessage: "No selector key matched")).setDone() + ) + case .Error(let e): return (self, program.setError(error: e)) + } + } + + public func done() -> Bool { + return false + } + + public func current() -> Lang.ParserState { + return currrent_state + } + + public let keys: [any EvaluatableExpression] + public let states: [ParserStateInstance] + public let selector: any EvaluatableExpression + public let currrent_state: ParserState +} + +extension ParserState: Compilable { + public typealias ToCompile = (ParserState, [String: ParserStateInstance]) + public typealias Compiled = ParserStateInstance + public static func compile(_ parser: ToCompile) -> Result { + let (state, current) = parser + if state == accept || state == reject { + return .Ok(ParserStateNoTransition(currrent_state: state)) + } + + if state.direct_transition(), + let transition_statement = state.transition { + return .Ok( + ParserStateDirectTransition( + currrent_state: state, next_state: current[transition_statement.next_state_name!]!)) + } + + if let transition_select_statement = state.transition, + let transition_select_expression = transition_select_statement.transition_expression { + + var keys: Array = Array() + var states: Array = Array() + + for kse in transition_select_expression.keyset_expressions { + guard let next_state = current[kse.next_state_name] else { + return .Error(Error(withMessage: "Cannot find \(kse.next_state_name) as transition target")) + } + keys.append(kse.key) + states.append(next_state) + } + return .Ok(ParserStateSelectTransition(keys: keys, states: states, selector: transition_select_expression.selector, currrent_state: state)) + } + + return .Error(Error(withMessage: "Invalid parser state: No meaningful transition")) + } +} + +extension ParserStates: Compilable { + public typealias ToCompile = ParserStates + public typealias Compiled = ParserStateInstance + public static func compile(_ parser: ToCompile) -> Result { + var compiled_states = [String: ParserStateInstance]() + + compiled_states["accept"] = ParserStateNoTransition(currrent_state: accept) + compiled_states["reject"] = ParserStateNoTransition(currrent_state: reject) + + // TODO: We assume that states are in transition-order! + for state in parser.states { + switch ParserState.compile((state, compiled_states)) { + case .Ok(let compiled): compiled_states[state.state_name] = compiled + case .Error(let e): return .Error(e) + } + } + + // Now, find the start state: + if let start_state = compiled_states["start"] { + return .Ok(start_state) + } else { + return .Error(Error(withMessage: "No start state defined")) + } + } +} + +public class ParserInstance: ProgramExecution { + + let start_state: ParserStateInstance + + public init(_ _start_state: ParserStateInstance) { + self.start_state = _start_state + } + + public override var description: String { + return "Execution: \(super.description)\nStart State: \(start_state)" + } +} + +extension ParserInstance: Compilable { + public typealias ToCompile = Parser + public typealias Compiled = ParserInstance + + public static func compile(_ parser: ToCompile) -> Result { + return switch ParserStates.compile(parser.states) { + case .Ok(let start_state): Result.Ok(ParserInstance(start_state)) + case .Error(let e): Result.Error(e) } - return (reject, execution) } } diff --git a/Sources/Runtime/Protocols.swift b/Sources/Runtime/Protocols.swift index 2a7c327..00d51b0 100644 --- a/Sources/Runtime/Protocols.swift +++ b/Sources/Runtime/Protocols.swift @@ -19,7 +19,7 @@ import Common import Lang protocol EvaluatableParserTransition { - func evaluate(execution: ProgramExecution) -> (ParserState, ProgramExecution) + func program(execution: ProgramExecution) -> (ParserState, ProgramExecution) } protocol EvaluatableParserTransitionStatement { @@ -28,4 +28,20 @@ protocol EvaluatableParserTransitionStatement { public protocol Execution { func execute() -> (ParserState, ProgramExecution) +} + +public protocol Compilable { + associatedtype ToCompile + associatedtype Compiled + static func compile(_: ToCompile) -> Result +} + +public protocol ParserStateInstance { + func execute(program: ProgramExecution) -> (ParserStateInstance, ProgramExecution) + func done() -> Bool + func current() -> ParserState +} + +public protocol ParserExecution { + func execute() -> (ParserState, ProgramExecution) } \ No newline at end of file diff --git a/Sources/Runtime/Runtime.swift b/Sources/Runtime/Runtime.swift index 081b3c0..c7d21ea 100644 --- a/Sources/Runtime/Runtime.swift +++ b/Sources/Runtime/Runtime.swift @@ -20,7 +20,7 @@ import Lang /// The runtime for a parser public class ParserRuntime: CustomStringConvertible { - var parser: ParserInstance + public var parser: ParserInstance init(execution: ParserInstance) { self.parser = execution @@ -31,7 +31,7 @@ public class ParserRuntime: CustomStringConvertible { return switch program.starting_parser() { case .Ok(let parser): - switch ParserInstance.create(parser) { + switch ParserInstance.compile(parser) { case .Ok(let execution): .Ok(Runtime.ParserRuntime(execution: execution)) case .Error(let error): .Error(error) } @@ -40,8 +40,7 @@ public class ParserRuntime: CustomStringConvertible { } /// Run the P4 parser on a given packet - public func run(input: Packet) -> Result<(ParserState, ProgramExecution)> { - parser.scopes.enter() + public func run() -> Result<(ParserState, ProgramExecution)> { return .Ok(parser.execute()) } @@ -51,15 +50,15 @@ public class ParserRuntime: CustomStringConvertible { } /// Instances of parsers are executable -extension ParserInstance: Execution { +extension ParserInstance: ParserExecution { public func execute() -> (ParserState, ProgramExecution) { var execution = self as ProgramExecution - var state = self.state + var c = self.start_state // Evaluate until the state is either accept or reject. - while state != accept && state != reject { - (state, execution) = state.evaluate(execution: execution) + while !c.done() && !execution.hasError() { + (c, execution) = c.execute(program: execution) } - return (state, execution) + return (c.current(), execution) } } diff --git a/Tests/p4rseTests/ParserTests.swift b/Tests/p4rseTests/ParserTests.swift index b47102b..963ba98 100644 --- a/Tests/p4rseTests/ParserTests.swift +++ b/Tests/p4rseTests/ParserTests.swift @@ -26,28 +26,6 @@ import TreeSitterP4 @testable import Parser -@Test func test_simple_parser() async throws { - let simple_parser_declaration = """ - parser main_parser() { - state start { - transition start; - } - }; - """ - - let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) - - #expect(parser.states.count() == 1) - let state = try! #require(parser.states.find(withName: "start")) - #expect(state.state_name == "start") - #expect(state.statements.count == 0) - - #expect(#RequireOkResult(parser.states.semantic_check())) - let next_state = try! #require(state.next_state) - #expect(next_state == state) -} - @Test func test_simple_parser_syntax_error() async throws { let simple_parser_declaration = """ parser main_parser() { @@ -87,6 +65,7 @@ import TreeSitterP4 parser main_parser() { state start { true; + false; transition start; } }; diff --git a/Tests/p4rseTests/RuntimeTests.swift b/Tests/p4rseTests/RuntimeTests.swift index 3bec0fc..b0b4c89 100644 --- a/Tests/p4rseTests/RuntimeTests.swift +++ b/Tests/p4rseTests/RuntimeTests.swift @@ -38,14 +38,8 @@ import TreeSitterP4 """ let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - #expect(#RequireOkResult(Runtime.ParserRuntime.create(program: program))) - let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) - - guard case Common.Result.Ok(let (state_result, _)) = runtime.run(input: Packet()) - else { - assert(false) - } + let (state_result, _) = try! #UseOkResult(runtime.run()) // We should be in the accept state. #expect(state_result == Lang.accept) @@ -62,15 +56,8 @@ import TreeSitterP4 """ let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - #expect(#RequireOkResult(Runtime.ParserRuntime.create(program: program))) - let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) - - guard case Common.Result.Ok(let (state_result, _)) = runtime.run(input: Packet()) - else { - assert(false) - } - + let (state_result, _) = try! #UseOkResult(runtime.run()) // We should be in the accept state. #expect(state_result == Lang.reject) } @@ -87,55 +74,13 @@ import TreeSitterP4 """ let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + #expect( #RequireErrorResult( - Error(withMessage: "Could not find the start state"), + Error(withMessage: "No start state defined"), Runtime.ParserRuntime.create(program: program))) } -@Test func test_simple_local_element_variable_declaration() async throws { - let simple_parser_declaration = """ - parser main_parser() { - state start { - bool b = false; - string s = "testing"; - true; - false; - transition reject; - } - }; - """ - - let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) - - // This seems awkward to me! - // TODO: Is there a better way? - guard case Common.Result.Ok(let (state_result, execution_result)) = runtime.run(input: Packet()) - else { - assert(false) - } - - // There should be 1 scope. - #expect(execution_result.scopes.count == 1) - - guard let scope = execution_result.scopes.current else { - assert(false) - } - - // We should be in the accept state. - #expect(state_result == Lang.reject) - - // There are two variables declared. - #expect(scope.count == 2) - - // Check the names/values of the variables in scope. - let b = try #require(scope.lookup(identifier: Identifier(name: "b"))) - let s = try #require(scope.lookup(identifier: Identifier(name: "s"))) - #expect(b.value_type.eq(rhs: P4BooleanValue(withValue: false))) - #expect(s.value_type.eq(rhs: P4StringValue(withValue: "\"testing\""))) -} - @Test func test_simple_parser_with_transition_select_expression() async throws { let simple_parser_declaration = """ parser main_parser() { @@ -148,18 +93,14 @@ import TreeSitterP4 }; """ + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - #expect(#RequireOkResult(program.find_parser(withName: Identifier(name: "main_parser")))) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) #expect(parser.states.count() == 1) - let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) - - guard case Common.Result.Ok(let (state_result, _)) = runtime.run(input: Packet()) - else { - assert(false) - } #expect(state_result == Lang.accept) } @@ -176,16 +117,10 @@ import TreeSitterP4 """ let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - #expect(#RequireOkResult(program.find_parser(withName: Identifier(name: "main_parser")))) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) + let (state_result, _) = try! #UseOkResult(runtime.run()) #expect(parser.states.count() == 1) - - let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) - - guard case Common.Result.Ok(let (state_result, _)) = runtime.run(input: Packet()) - else { - assert(false) - } #expect(state_result == Lang.reject) -} \ No newline at end of file +} diff --git a/Tests/p4rseTests/ScopeRuntimeTests.swift b/Tests/p4rseTests/ScopeRuntimeTests.swift new file mode 100644 index 0000000..4e5c476 --- /dev/null +++ b/Tests/p4rseTests/ScopeRuntimeTests.swift @@ -0,0 +1,173 @@ +// p4rse, Copyright 2026, Will Hawkins +// +// This file is part of p4rse. +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import Common +import Foundation +import Lang +import Macros +import Runtime +import SwiftTreeSitter +import Testing +import TreeSitter +import TreeSitterP4 + +@testable import Parser + +@Test func test_simple_local_element_variable_declaration() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool b = false; + string s = "testing"; + true; + false; + true; + transition reject; + } + }; + """ + + + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) + let (state_result, execution_result) = try! #UseOkResult(runtime.run()) + + #expect(execution_result.scopes.count == 1) + + guard let scope = execution_result.scopes.current else { + assert(false) + } + + // We should be in the accept state. + #expect(state_result == Lang.reject) + + // There are two variables declared. + #expect(scope.count == 2) + + // Check the names/values of the variables in scope. + let b = try #require(scope.lookup(identifier: Identifier(name: "b"))) + let s = try #require(scope.lookup(identifier: Identifier(name: "s"))) + #expect(b.value_type.eq(rhs: P4BooleanValue(withValue: false))) + #expect(s.value_type.eq(rhs: P4StringValue(withValue: "\"testing\""))) +} + +@Test func test_simple_scope() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state starts { + bool where_to = false; + int va = 5; + transition accept; + } + state start { + bool where_to = true; + transition select (where_to) { + false: reject; + true: starts; + }; + } + }; + """ + + + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) + let (state_result, execution_result) = try! #UseOkResult(runtime.run()) + + #expect(state_result == Lang.accept) + + #expect(execution_result.scopes.count == 1) + let scope = try! #require(execution_result.scopes.current) + #expect(scope.count == 2) + let va = try #require(scope.lookup(identifier: Identifier(name: "va"))) + let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to"))) + #expect(where_to.value_type.eq(rhs: P4BooleanValue(withValue: false))) + #expect(va.value_type.eq(rhs: P4IntValue(withValue: 5))) +} + +@Test func test_simple_scope2() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state starts { + bool where_to = false; + int va = 5; + transition accept; + } + state start { + bool where_to = true; + where_to = false; + transition select (where_to) { + false: reject; + true: starts; + }; + } + }; + """ + + + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) + let (state_result, execution_result) = try! #UseOkResult(runtime.run()) + + #expect(parser.states.count() == 2) + + #expect(state_result == Lang.reject) + + #expect(execution_result.scopes.count == 1) + let scope = try! #require(execution_result.scopes.current) + + #expect(scope.count == 1) + let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to"))) + #expect(where_to.value_type.eq(rhs: P4BooleanValue(withValue: false))) +} + +@Test func test_simple_assignment() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool where_to = true; + string where_from = "here"; + where_to = false; + where_from = "there"; + transition select (true) { + false: reject; + true: accept; + }; + } + }; + """ + + + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) + let (state_result, execution_result) = try! #UseOkResult(runtime.run()) + + #expect(parser.states.count() == 1) + + #expect(state_result == Lang.accept) + + #expect(execution_result.scopes.count == 1) + let scope = try! #require(execution_result.scopes.current) + + #expect(scope.count == 2) + let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to"))) + #expect(where_to.value_type.eq(rhs: P4BooleanValue(withValue: false))) + let where_from = try #require(scope.lookup(identifier: Identifier(name: "where_from"))) + #expect(where_from.value_type.eq(rhs: P4StringValue(withValue: "\"there\""))) +} \ No newline at end of file diff --git a/tree-sitter-p4/grammar.js b/tree-sitter-p4/grammar.js index 6ee1344..98506ad 100644 --- a/tree-sitter-p4/grammar.js +++ b/tree-sitter-p4/grammar.js @@ -75,14 +75,15 @@ export default grammar({ // General statements statements: $ => repeat1(seq($.statement, $.semicolon)), - statement: $ => choice($.conditionalStatement, $.blockStatement, $.expressionStatement),// Limited, so far. + statement: $ => choice($.conditionalStatement, $.blockStatement, $.expressionStatement, $.assignmentStatement),// Limited, so far. blockStatement: $ => seq(optional($.annotations), '{', optional($.statements), '}'), conditionalStatement: $ => choice(prec(1, seq($.if, '(', $.expression, ')', $.statement)), prec(2, seq($.if, '(', $.expression, ')', $.statement, $.else, $.statement))), expressionStatement: $=> $.expression, + assignmentStatement: $=> seq($.expression, $.assignment, $.expression), // Parser statements parserStatements: $ => repeat1(seq($.parserStatement, $.semicolon)), - parserStatement: $ => choice($.conditionalStatement, $.parserBlockStatement, $.expressionStatement), // Limited, so far. + parserStatement: $ => choice($.conditionalStatement, $.parserBlockStatement, $.expressionStatement, $.assignmentStatement), // Limited, so far. parserBlockStatement: $ => seq(optional($.annotations), '{', $.parserStatements, '}'), parserTransitionStatement: $ => seq($.transition, $.transitionSelectionExpression), diff --git a/tree-sitter-p4/test.txt b/tree-sitter-p4/test.txt index 0ffde94..5e86163 100644 --- a/tree-sitter-p4/test.txt +++ b/tree-sitter-p4/test.txt @@ -1,7 +1,16 @@ -parser simple() { - state start { - bool l; - transition accept; - } -} - + parser main_parser() { + state next_state { + transition reject; + } + state not_next_state { + transition reject; + } + state start { + transition starts; + } + state starts { + transition select (se) { + true: next_state; + }; + } + }; diff --git a/tree-sitter-p4/test/corpus/statements.txt b/tree-sitter-p4/test/corpus/statements.txt index a725d23..173d3f6 100644 --- a/tree-sitter-p4/test/corpus/statements.txt +++ b/tree-sitter-p4/test/corpus/statements.txt @@ -106,15 +106,12 @@ parser simple() { ) ========================= -Simple If Statement (With Body) +Simple Assignment Statement (To Identifier) ========================= parser simple() { state start { - if (true) { - true; - } else { - false; - }; + string l = "testing"; + l = "five"; transition accept; } }; @@ -131,41 +128,32 @@ parser simple() { (parserState (state) (identifier) + (parserLocalElements + (parserLocalElement + (variableDeclaration + (typeRef + (baseType + (string) + ) + ) + (identifier) + (assignment) + (expression + (string_literal) + ) + ) + ) + (semicolon) + ) (parserStatements (parserStatement - (conditionalStatement - (if) + (assignmentStatement (expression - (true) + (identifier) ) - (statement - (blockStatement - (statements - (statement - (expressionStatement - (expression - (true) - ) - ) - ) - (semicolon) - ) - ) - ) - (else) - (statement - (blockStatement - (statements - (statement - (expressionStatement - (expression - (false) - ) - ) - ) - (semicolon) - ) - ) + (assignment) + (expression + (string_literal) ) ) ) @@ -184,4 +172,4 @@ parser simple() { ) (semicolon) ) - +