From 3693bdc02dfd49f25c6833f50696c0b48da09343 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Tue, 10 Feb 2026 03:37:57 -0500 Subject: [PATCH] Start Evaluation Signed-off-by: Will Hawkins --- Sources/Common/Execution.swift | 24 ++++ Sources/Common/ProgramTypes.swift | 7 + Sources/Common/Protocols.swift | 2 +- Sources/Common/Support.swift | 7 + Sources/Lang/Expressions.swift | 0 Sources/Lang/Parser.swift | 211 ++++++++++++++++++++++++---- Sources/Lang/Program.swift | 5 +- Sources/Parser/Expression.swift | 109 ++++++++++++++ Sources/Parser/Parser.swift | 119 ++++++++++++++-- Sources/Runtime/Parser.swift | 43 ++++-- Sources/Runtime/Program.swift | 7 + Sources/Runtime/Protocols.swift | 2 +- Sources/Runtime/Runtime.swift | 13 +- Tests/p4rseTests/ParserTests.swift | 114 +++++++-------- Tests/p4rseTests/RuntimeTests.swift | 91 +++++++++++- 15 files changed, 635 insertions(+), 119 deletions(-) create mode 100644 Sources/Lang/Expressions.swift create mode 100644 Sources/Parser/Expression.swift diff --git a/Sources/Common/Execution.swift b/Sources/Common/Execution.swift index 467918d..20fed16 100644 --- a/Sources/Common/Execution.swift +++ b/Sources/Common/Execution.swift @@ -17,12 +17,27 @@ open class ProgramExecution: CustomStringConvertible { public var scopes: Scopes = Scopes() + var error: Error? public init() {} open var description: String { return "Runtime:\nScopes: \(scopes)" } + + public func hasError() -> Bool { + return self.error != nil + } + + public func getError() -> Error? { + return self.error + } + + public func setError(error: Error) -> ProgramExecution { + let npe = self + npe.error = error + return npe + } } @@ -96,6 +111,15 @@ public struct Scopes: CustomStringConvertible { return s } + public func evaluate(identifier: Identifier) -> Result { + for scope in scopes { + if let vari = scope.lookup(identifier: identifier) { + return .Ok(vari.value) + } + } + return .Error(Error(withMessage: "Cannot find \(identifier) in scope.")) + } + public var count: Int { get { scopes.count diff --git a/Sources/Common/ProgramTypes.swift b/Sources/Common/ProgramTypes.swift index ba8e0e5..ba54de5 100644 --- a/Sources/Common/ProgramTypes.swift +++ b/Sources/Common/ProgramTypes.swift @@ -65,6 +65,12 @@ open class P4ValueBase: P4Value { } } +extension P4ValueBase: EvaluatableExpression { + public func evaluate(execution: ProgramExecution) -> Result { + return .Ok(self) + } +} + /// The type for a P4 struct public struct P4Struct: P4Type { public let name: String @@ -122,6 +128,7 @@ public class P4BooleanValue: P4ValueBase { } } + /// A P4 int type public struct P4Int: P4Type { public static func create() -> any P4Type { diff --git a/Sources/Common/Protocols.swift b/Sources/Common/Protocols.swift index 7f94285..a97d6e7 100644 --- a/Sources/Common/Protocols.swift +++ b/Sources/Common/Protocols.swift @@ -20,7 +20,7 @@ public protocol EvaluatableExpression { /// - Parameters /// - execution: The execution context in which to evaluate the expression /// - Returns: The value of expression - func evaluate(execution: ProgramExecution) -> P4Value + func evaluate(execution: ProgramExecution) -> Result } public protocol EvaluatableParserStatement { diff --git a/Sources/Common/Support.swift b/Sources/Common/Support.swift index 22b1f16..bcfd436 100644 --- a/Sources/Common/Support.swift +++ b/Sources/Common/Support.swift @@ -53,6 +53,13 @@ public enum Result: Equatable { } return nil } + + public func map(block: (OKT) -> Result) -> Result { + switch self { + case .Ok(let ok): return block(ok) + case .Error(let e): return .Error(e) + } + } } extension Result where OKT: CustomStringConvertible { diff --git a/Sources/Lang/Expressions.swift b/Sources/Lang/Expressions.swift new file mode 100644 index 0000000..e69de29 diff --git a/Sources/Lang/Parser.swift b/Sources/Lang/Parser.swift index 6c31366..9a8c16c 100644 --- a/Sources/Lang/Parser.swift +++ b/Sources/Lang/Parser.swift @@ -25,8 +25,65 @@ public struct LocalElement { } +public struct KeysetExpression { + public let key: EvaluatableExpression + public let next_state_name: String + public let next_state: ParserState? + + public init(withKey key: EvaluatableExpression, withNextStateName next_state_name: String) { + self.key = key + self.next_state_name = next_state_name + self.next_state = .none + } + public init( + withKey key: EvaluatableExpression, withNextStateName next_state_name: String, + withNextState next_state: ParserState + ) { + self.key = key + self.next_state_name = next_state_name + self.next_state = next_state + } + +} + +public struct ParserTransitionSelectExpression { + public let selector: EvaluatableExpression + public let keyset_expressions: [KeysetExpression] + + public init( + withSelector selector: EvaluatableExpression, withKeysetExpressions kses: [KeysetExpression] + ) { + self.selector = selector + self.keyset_expressions = kses + } + + public func append_checked_kse(kse: KeysetExpression) -> ParserTransitionSelectExpression { + var new_kse = self.keyset_expressions + new_kse.append(kse) + return ParserTransitionSelectExpression( + withSelector: self.selector, withKeysetExpressions: new_kse) + } + +} + public struct ParserTransitionStatement { - public init() {} + public let next_state_name: String? + public let transition_expression: ParserTransitionSelectExpression? + + public init() { + self.next_state_name = .none + self.transition_expression = .none + } + + public init(withTransitionExpression transition_expression: ParserTransitionSelectExpression) { + self.next_state_name = .none + self.transition_expression = transition_expression + } + + public init(withNextState next_state_name: String) { + self.next_state_name = next_state_name + self.transition_expression = .none + } } public struct VariableDeclarationStatement { @@ -36,16 +93,13 @@ public struct VariableDeclarationStatement { } } -public struct ExpressionStatement { - public init() {} -} - -public struct ParserState: Equatable, CustomStringConvertible { +public class ParserState: Equatable, CustomStringConvertible { public private(set) var state_name: String public private(set) var local_elements: [EvaluatableParserStatement] public private(set) var statements: [EvaluatableParserStatement] public private(set) var transition: ParserTransitionStatement? + public private(set) var next_state: ParserState? public var description: String { return "Name: \(state_name)" @@ -67,6 +121,37 @@ public struct ParserState: Equatable, CustomStringConvertible { self.statements = statements ?? Array() } + public func semantic_check(states: ParserStates) -> Bool { + guard let transition = transition else { + 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) + { + self.next_state = next_state + return true + } + + return false + } + /// (private) constructor (no transition) /// /// accept and reject are the only final states and they are constructed internally. @@ -76,24 +161,76 @@ public struct ParserState: Equatable, CustomStringConvertible { local_elements = Array() statements = Array() } + + public func direct_transition() -> Bool { + return + if let transition = self.transition, + transition.next_state_name != nil + { + true + } else { + false + } + } } public struct ParserStates { public var states: [ParserState] = Array() + + public func count() -> Int { + return states.count + } + + public func find(withName name: String) -> ParserState? { + for state in states { + if state.state_name == name { + return .some(state) + } + } + return .none + } + + public func semantic_check() -> Result<()> { + // Check whether all the states referred to in the transition statements are + // valid. + let errors = states.filter { state in + return !state.semantic_check(states: self) + }.map { state in + return Result<()>.Error(Error(withMessage: "State \(state) has invalid transition")) + } + + if !errors.isEmpty { + return errors[0] + } + + return .Ok(()) + } + + public init() { + self.states = Array() + } + + private init(withStates states: [ParserState]) { + self.states = states + } + + public func append(state: ParserState) -> ParserStates { + var new_states = self.states + new_states.append(state) + return ParserStates(withStates: new_states) + } } 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: [ParserState] = Array() - public var count: Int { - states.count - } + public var states: ParserStates public var name: Identifier public init(withName name: Identifier) { + self.states = ParserStates() self.name = name } @@ -102,40 +239,52 @@ public struct Parser: P4Type { } public func findStartState() -> ParserState? { - for state in states { + for state in states.states { if state.state_name == "start" { return state } } return .none } + + public func semantic_check() -> Result<()> { + return self.states.semantic_check() + } } public class ParserInstance: ProgramExecution { - private init(state: ParserState) { - self.state = state - super.init() + public var state: ParserState + + private init(state: ParserState) { + self.state = state + super.init() + } + + 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 static func create(_ parser: Parser) -> Result { - guard let start_state = parser.findStartState() else { - return Result.Error(Error(withMessage: "Could not find the start state")) - } - let new = ParserInstance(state: start_state) - - return Result.Ok(new) + 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) - public var state: ParserState + return Result.Ok(new) + } - public func transition(toNextState state: ParserState) -> ParserInstance { - let next = self - next.state = state - return next - } + 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)" - } -} \ No newline at end of file + public override var description: String { + return "Execution: \(super.description)\nCurrent State: \(state)" + } +} diff --git a/Sources/Lang/Program.swift b/Sources/Lang/Program.swift index 7025c4e..98c9044 100644 --- a/Sources/Lang/Program.swift +++ b/Sources/Lang/Program.swift @@ -17,6 +17,10 @@ import Common +public struct ExpressionStatement { + public init() {} +} + public struct Program { public var types: [P4Type] = Array() @@ -29,7 +33,6 @@ public struct Program { public func find_parser(withName name: Identifier) -> Result { for type in self.types { - print("type: \(type)") guard let parser = type as? Parser else { continue } diff --git a/Sources/Parser/Expression.swift b/Sources/Parser/Expression.swift new file mode 100644 index 0000000..c2d6125 --- /dev/null +++ b/Sources/Parser/Expression.swift @@ -0,0 +1,109 @@ +// 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 . + +// 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 +} + +extension Identifier: ParseableEvaluatableExpression { + static func parse( + node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree + ) -> Result { + + guard + let parser_statement_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(expression (identifier) @identifier)" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) + } + + let qr = parser_statement_query.execute(node: node, in: tree) + + guard let result = qr.next() else { + return .Ok(.none) + } + + return .Ok(Identifier(name: result.captures[0].node.text!)) + } +} + +extension P4BooleanValue: ParseableEvaluatableExpression { + static func parse( + node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree + ) -> Result { + + guard + let true_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(expression (true))" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) + } + + let true_qr = true_query.execute(node: node, in: tree) + + if true_qr.next() != nil { + return .Ok(P4BooleanValue(withValue: true)) + } + + guard + let false_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(expression (false))" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) + } + + let false_qr = false_query.execute(node: node, in: tree) + + if false_qr.next() != nil { + return .Ok(P4BooleanValue(withValue: false)) + } + return .Ok(.none) + } +} + +struct Expression { + public static func Parse(node: Node, inTree: MutableTree) -> 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) + { + return .Ok(parsed) + } + } + + return Result.Error(Error(withMessage: "Could not parse into expression.")) + } +} diff --git a/Sources/Parser/Parser.swift b/Sources/Parser/Parser.swift index e15db62..48c8a0f 100644 --- a/Sources/Parser/Parser.swift +++ b/Sources/Parser/Parser.swift @@ -229,10 +229,95 @@ public struct Parser { return Result.Ok(statements) } + static func TransitionKeysetExpression( + node: Node, inTree tree: MutableTree + ) -> Result<[KeysetExpression]> { + guard + let transition_selection_expression_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "((keysetExpression (expression) @ks) (colon) (identifier) @next-state)" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) + } + + let qr = transition_selection_expression_query.execute(node: node, in: tree) + + var kses: [KeysetExpression] = Array() + + 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) + }) + { + return .Error(e) + } + } + + return .Ok(kses) + } + + static func TransitionSelectExpression( + node: Node, inTree tree: MutableTree + ) -> Result { + guard + let transition_selection_expression_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(parserTransitionStatement (transition) (transitionSelectionExpression (selectExpression (select) (expression) @selector (selectBody) @body)))" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) + } + + let qr = transition_selection_expression_query.execute(node: node, in: tree) + + guard let query_result = qr.next() else { + return .Error(Error(withMessage: "Could not find transition select expression")) + } + + 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) + } + } + } + static func TransitionStatement( node: Node, inTree tree: MutableTree - ) -> ParserTransitionStatement? { - return ParserTransitionStatement() + ) -> Result { + guard + let next_state_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(parserTransitionStatement (transition) (transitionSelectionExpression (identifier) @next-state))" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) + } + + let qr = next_state_query.execute(node: node, in: tree) + + 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 TransitionSelectExpression(node: node, inTree: tree) } static func State(node: Node, inTree tree: MutableTree) -> Result { @@ -259,7 +344,7 @@ public struct Parser { guard !state_name_capture.isEmpty, !transition_capture.isEmpty, let parsed_state_name = state_name_capture[0].node.text, - let transition_statement = TransitionStatement( + case .Ok(let transition_statement) = TransitionStatement( node: transition_capture[0].node, inTree: tree) else { return Result.Error(Error(withMessage: "Could not parse a parser declaration")) @@ -297,7 +382,9 @@ public struct Parser { withTransition: transition_statement)) } } - static func Parser(withName name: Identifier, node: Node, inTree tree: MutableTree) -> Result { + static func Parser( + withName name: Identifier, node: Node, inTree tree: MutableTree + ) -> Result { guard let parser_state_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -312,12 +399,23 @@ public struct Parser { var parser = Lang.Parser(withName: name) // Build a state from each one listed. - for parser_states in parser_state_query.execute(node: node, in: tree) { - switch P4Parser.State(node: parser_states.nodes[0], inTree: tree) { - case Result.Ok(let state): parser.states.append(state) - case Result.Error(let error): return Result.Error(error) + let qr = parser_state_query.execute(node: node, in: tree) + let qr_value = qr.next()! + let captures = qr_value.captures(named: "parser-states") + + var error: Error? = .none + + // 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) + case Result.Error(let e): error = e } } + + if let error = error { + return .Error(error) + } return Result.Ok(parser) } @@ -354,7 +452,10 @@ public struct Parser { 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) { + switch Parser( + withName: Identifier(name: parser_declaration.nodes[0].text!), + node: parser_declaration.nodes[1], inTree: tree) + { case Result.Ok(let parser): program.types.append(parser) case Result.Error(let error): return Result.Error(error) } diff --git a/Sources/Runtime/Parser.swift b/Sources/Runtime/Parser.swift index 9b13e96..30d4af9 100644 --- a/Sources/Runtime/Parser.swift +++ b/Sources/Runtime/Parser.swift @@ -18,16 +18,7 @@ import Common import Lang - - -extension ParserTransitionStatement: EvaluatableParserTransitionStatement { - // TODO: Currently transitions to accept. - func transition(execution: ProgramExecution) -> (ParserState, ProgramExecution) { - return (accept, execution) - } -} - -extension ParserState: EvaluatableParserState { +extension ParserState: EvaluatableParserTransition { public func evaluate(execution: ProgramExecution) -> (ParserState, ProgramExecution) { var currentExecution = execution @@ -41,10 +32,34 @@ extension ParserState: EvaluatableParserState { currentExecution = statement.evaluate(execution: currentExecution) } - return if let transition = transition { - transition.transition(execution: currentExecution) - } else { - (reject, currentExecution) + 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) { + 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) + } + } + case .Error(let e): return (reject, execution.setError(error: e)) + } + return (reject, execution) } } diff --git a/Sources/Runtime/Program.swift b/Sources/Runtime/Program.swift index a594c90..5aac3cf 100644 --- a/Sources/Runtime/Program.swift +++ b/Sources/Runtime/Program.swift @@ -30,4 +30,11 @@ extension ExpressionStatement: EvaluatableParserStatement { public func evaluate(execution: ProgramExecution) -> ProgramExecution { return execution } +} + +// Variables are evaluatable because they can be looked up by identifiers. +extension Identifier: EvaluatableExpression { + public func evaluate(execution: Common.ProgramExecution) -> Result { + return execution.scopes.evaluate(identifier: self) + } } \ No newline at end of file diff --git a/Sources/Runtime/Protocols.swift b/Sources/Runtime/Protocols.swift index 098b62c..2a7c327 100644 --- a/Sources/Runtime/Protocols.swift +++ b/Sources/Runtime/Protocols.swift @@ -18,7 +18,7 @@ import Common import Lang -protocol EvaluatableParserState { +protocol EvaluatableParserTransition { func evaluate(execution: ProgramExecution) -> (ParserState, ProgramExecution) } diff --git a/Sources/Runtime/Runtime.swift b/Sources/Runtime/Runtime.swift index 70daa41..081b3c0 100644 --- a/Sources/Runtime/Runtime.swift +++ b/Sources/Runtime/Runtime.swift @@ -20,14 +20,15 @@ import Lang /// The runtime for a parser public class ParserRuntime: CustomStringConvertible { - var execution: ParserInstance + var parser: ParserInstance init(execution: ParserInstance) { - self.execution = execution + self.parser = execution } /// Create a parser runtime from a P4 program public static func create(program: Lang.Program) -> Result { + return switch program.starting_parser() { case .Ok(let parser): switch ParserInstance.create(parser) { @@ -40,12 +41,12 @@ public class ParserRuntime: CustomStringConvertible { /// Run the P4 parser on a given packet public func run(input: Packet) -> Result<(ParserState, ProgramExecution)> { - execution.scopes.enter() - return .Ok(execution.execute()) + parser.scopes.enter() + return .Ok(parser.execute()) } public var description: String { - return "Runtime:\nExecution: \(execution)" + return "Runtime:\nExecution: \(parser)" } } @@ -57,7 +58,7 @@ extension ParserInstance: Execution { // Evaluate until the state is either accept or reject. while state != accept && state != reject { - (state, execution) = self.state.evaluate(execution: execution) + (state, execution) = state.evaluate(execution: execution) } return (state, execution) } diff --git a/Tests/p4rseTests/ParserTests.swift b/Tests/p4rseTests/ParserTests.swift index 9a2978d..b47102b 100644 --- a/Tests/p4rseTests/ParserTests.swift +++ b/Tests/p4rseTests/ParserTests.swift @@ -15,80 +15,84 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +import Common +import Foundation +import Macros +import Runtime +import SwiftTreeSitter import Testing import TreeSitter -import SwiftTreeSitter import TreeSitterP4 -import Foundation -import Runtime -import Common - -import Macros @testable import Parser @Test func test_simple_parser() async throws { - let simple_parser_declaration = """ - parser main_parser() { - state start { - transition drop; - } - }; - """ + 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"))) + 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) - #expect(parser.states[0].state_name == "start") - #expect(parser.states[0].statements.count == 0) + #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() { - state - transition drop; - } - }; - """ - #expect(#RequireErrorResult(Error(withMessage: "Could not compile the P4 program"), Parser.Program(simple_parser_declaration))) + let simple_parser_declaration = """ + parser main_parser() { + state + transition start; + } + }; + """ + #expect( + #RequireErrorResult( + Error(withMessage: "Could not compile the P4 program"), + Parser.Program(simple_parser_declaration))) } @Test func test_simple_parser_with_statement() async throws { - let simple_parser_declaration = """ - parser main_parser() { - state start { - true; - transition drop; - } - }; - """ + let simple_parser_declaration = """ + parser main_parser() { + state start { + true; + transition start; + } + }; + """ - let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + 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) - #expect(parser.states[0].state_name == "start") - #expect(parser.states[0].statements.count == 1) + #expect(parser.states.count() == 1) + + let state = try! #require(parser.states.find(withName: "start")) + #expect(state.state_name == "start") + #expect(state.statements.count == 1) } @Test func test_simple_parser_with_instantiation() async throws { - let simple_parser_declaration = """ - parser main_parser() { - state start { - true; - transition drop; - } - }; - bool() main; - """ + let simple_parser_declaration = """ + parser main_parser() { + state start { + true; + transition start; + } + }; + bool() main; + """ - 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) - #expect(parser.states[0].state_name == "start") - #expect(parser.states[0].statements.count == 1) + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + #expect(#RequireOkResult(program.find_parser(withName: Identifier(name: "main_parser")))) } - diff --git a/Tests/p4rseTests/RuntimeTests.swift b/Tests/p4rseTests/RuntimeTests.swift index 63577cd..3bec0fc 100644 --- a/Tests/p4rseTests/RuntimeTests.swift +++ b/Tests/p4rseTests/RuntimeTests.swift @@ -28,6 +28,30 @@ import TreeSitterP4 @testable import Parser @Test func test_simple_runtime() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + true; + transition accept; + } + }; + """ + + 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) + } + + // We should be in the accept state. + #expect(state_result == Lang.accept) +} + +@Test func test_simple_runtime_to_accept() async throws { let simple_parser_declaration = """ parser main_parser() { state start { @@ -39,8 +63,19 @@ 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) + } + + // We should be in the accept state. + #expect(state_result == Lang.reject) } + @Test func test_simple_runtime_no_start_state() async throws { let simple_parser_declaration = """ parser main_parser() { @@ -89,7 +124,7 @@ import TreeSitterP4 } // We should be in the accept state. - #expect(state_result == Lang.accept) + #expect(state_result == Lang.reject) // There are two variables declared. #expect(scope.count == 2) @@ -100,3 +135,57 @@ import TreeSitterP4 #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() { + state start { + transition select (true) { + false: reject; + true: accept; + }; + } + }; + """ + + 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"))) + + #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) +} + +@Test func test_simple_parser_with_transition_select_expression_to_reject() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + transition select (false) { + false: reject; + true: accept; + }; + } + }; + """ + + 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"))) + + #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