From a2335a01ed0ddc2293e6a8d7b78909d1ccb448d7 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Tue, 24 Feb 2026 04:38:05 -0500 Subject: [PATCH] Initial Support For If Statements Signed-off-by: Will Hawkins --- Sources/P4Lang/Parser.swift | 9 - Sources/P4Lang/Statement.swift | 54 +++++ Sources/P4Parser/Expression.swift | 28 --- Sources/P4Parser/Parser.swift | 19 +- Sources/P4Parser/Statement.swift | 164 ++++++++++++++++ Sources/P4Runtime/Program.swift | 34 ++++ Tests/p4rseTests/RuntimeTests.swift | 71 ++++++- Tests/p4rseTests/ValueTypeParserTests.swift | 1 + tree-sitter-p4/test/corpus/statements.txt | 207 ++++++++++++++++++++ 9 files changed, 541 insertions(+), 46 deletions(-) create mode 100644 Sources/P4Lang/Statement.swift diff --git a/Sources/P4Lang/Parser.swift b/Sources/P4Lang/Parser.swift index 769b736..2717c8a 100644 --- a/Sources/P4Lang/Parser.swift +++ b/Sources/P4Lang/Parser.swift @@ -95,15 +95,6 @@ public struct ParserTransitionStatement { } } -public struct VariableDeclarationStatement { - public var initializer: EvaluatableExpression - public var identifier: Identifier - public init(identifier: Identifier, withInitializer initializer: EvaluatableExpression) { - self.identifier = identifier - self.initializer = initializer - } -} - public class ParserState: Equatable, CustomStringConvertible, Comparable { public private(set) var state_name: String diff --git a/Sources/P4Lang/Statement.swift b/Sources/P4Lang/Statement.swift new file mode 100644 index 0000000..c52abfc --- /dev/null +++ b/Sources/P4Lang/Statement.swift @@ -0,0 +1,54 @@ +// 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 + +public struct VariableDeclarationStatement { + public var initializer: EvaluatableExpression + public var identifier: Identifier + public init(identifier: Identifier, withInitializer initializer: EvaluatableExpression) { + self.identifier = identifier + self.initializer = initializer + } +} + +public struct ConditionalStatement { + public var condition: EvaluatableExpression + public var thenn: EvaluatableStatement + public var elss: EvaluatableStatement? + + public init(condition: EvaluatableExpression, withThen thenn: EvaluatableStatement) { + self.condition = condition + self.thenn = thenn + self.elss = .none + } + + public init(condition: EvaluatableExpression, withThen thenn: EvaluatableStatement, andElse elss: EvaluatableStatement) { + self.condition = condition + self.thenn = thenn + self.elss = elss + } +} + +public struct BlockStatement { + public var statements: [EvaluatableStatement] + + public init(_ statements: [EvaluatableStatement]) { + self.statements = statements + } + +} \ No newline at end of file diff --git a/Sources/P4Parser/Expression.swift b/Sources/P4Parser/Expression.swift index d40714a..ca5808d 100644 --- a/Sources/P4Parser/Expression.swift +++ b/Sources/P4Parser/Expression.swift @@ -178,31 +178,3 @@ struct Expression { return Result.Error(Error(withMessage: "Could not parse into expression.")) } } - -extension ExpressionStatement: ParseableStatement { - public static func Parse( - node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes - ) -> Result<(EvaluatableStatement?, LexicalScopes)> { - guard - let expression_statement_query = try? SwiftTreeSitter.Query( - language: p4lang, - data: String( - "(expressionStatement (expression) @expression)" - ).data(using: String.Encoding.utf8)!) - else { - return Result.Ok((.none, scopes)) - } - - let qr = expression_statement_query.execute(node: node, in: tree) - 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(), scopes)) - } - return Result.Ok((.none, scopes)) - } -} diff --git a/Sources/P4Parser/Parser.swift b/Sources/P4Parser/Parser.swift index 849ebcf..6802658 100644 --- a/Sources/P4Parser/Parser.swift +++ b/Sources/P4Parser/Parser.swift @@ -28,12 +28,11 @@ extension ParserAssignmentStatement: ParseableStatement { public static func Parse( node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes ) -> Result<(EvaluatableStatement?, LexicalScopes)> { - guard let parser_assignment_statement_query = try? SwiftTreeSitter.Query( language: p4lang, data: String( - "(assignmentStatement (expression) @lvalue (assignment) (expression) @value)" + "(assignmentStatement (expression) @lvalue (assignment) (expression) @value) @assignment" ).data(using: String.Encoding.utf8)!) else { return Result.Ok((.none, scopes)) @@ -44,6 +43,7 @@ extension ParserAssignmentStatement: ParseableStatement { return Result.Ok((.none, scopes)) } + let assignment_capture = parser_assignment_statement.captures(named: "assignment") let lvalue_capture = parser_assignment_statement.captures(named: "lvalue") let rvalue_capture = parser_assignment_statement.captures(named: "value") @@ -56,8 +56,15 @@ extension ParserAssignmentStatement: ParseableStatement { Error(withMessage: "Could not parse a parser assignment statement")) } - let rvalue_raw = rvalue_capture[0].node - let maybe_parsed_rvalue = Expression.Parse(node: rvalue_raw, inTree: tree, withScopes: scopes) + let rvalue_node = rvalue_capture[0].node + let lvalue_node = lvalue_capture[0].node + let assignment_node = assignment_capture[0].node + + if assignment_node.parent != node.parent { + return Result.Ok((.none, scopes)) + } + + let maybe_parsed_rvalue = Expression.Parse(node: rvalue_node, inTree: tree, withScopes: scopes) guard case Result.Ok(let rvalue) = maybe_parsed_rvalue else { return Result.Error(maybe_parsed_rvalue.error()!) } @@ -72,7 +79,7 @@ extension ParserAssignmentStatement: ParseableStatement { return Result.Ok( ( ParserAssignmentStatement( - withLValue: TypedIdentifier(name: lvalue_expression_raw, withType: lvalue_type), + withLValue: TypedIdentifier(name: lvalue_node.text!, withType: lvalue_type), withValue: rvalue ), scopes )) @@ -121,7 +128,7 @@ public struct Parser { ) -> Result<(EvaluatableStatement, LexicalScopes)> { let statementParsers: [ParseableStatement.Type] = [ ParserAssignmentStatement.self, ExpressionStatement.self, - VariableDeclarationStatement.self, + VariableDeclarationStatement.self, ConditionalStatement.self, BlockStatement.self, ] // Iterate through statement parsers and give each one a chance. diff --git a/Sources/P4Parser/Statement.swift b/Sources/P4Parser/Statement.swift index 64ea791..5902dfc 100644 --- a/Sources/P4Parser/Statement.swift +++ b/Sources/P4Parser/Statement.swift @@ -22,6 +22,142 @@ import SwiftTreeSitter import TreeSitterExtensions import TreeSitterP4 +extension BlockStatement: ParseableStatement { + public static func Parse( + node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes + ) -> Result<(EvaluatableStatement?, LexicalScopes)> { + // TODO: Make sure that this works. + // (And apply in other places!) + guard + let block_statement_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(statement . (blockStatement . (statements . ((statement) @astatement (semicolon))*) @statements) @block-statement) @statement" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Ok((.none, scopes)) + } + + let qr = block_statement_query.execute(node: node, in: tree) + guard let variable_declaration = qr.next() else { + return .Ok((.none, scopes)) + } + + let statement_capture = variable_declaration.captures(named: "statement") + let blockstatement_capture = variable_declaration.captures(named: "block-statement") + let astatement_capture = variable_declaration.captures(named: "astatement") + let statements_capture = variable_declaration.captures(named: "statements") + + if statement_capture.isEmpty || blockstatement_capture.isEmpty { + return .Error(Error(withMessage: "Could not parse a block statement")) + } + + let statement_node = statement_capture[0].node + let blockstatement_capture_node = blockstatement_capture[0].node + let statements_capture_node = statements_capture[0].node + /* + if statement_node.parent != node.parent + { + return .Ok((.none, scopes)) + } + */ + var statements: [EvaluatableStatement] = Array() + var parse_err: Error? = .none + + for statement in astatement_capture { + if let statement_node = statement.node.child( + at: 0) /* + let statement_node_parent = statement_node.parent, + statement_node_parent.parent == statements_capture_node + */ + { + switch P4Parser.Parser.Statements.Parse( + node: statement_node, inTree: tree, withScope: scopes.enter()) + { + case .Ok((let parsed_statement, _)): statements.append(parsed_statement) + case .Error(let e): + parse_err = e + break + } + } + } + + if let err = parse_err { + return .Error(err) + } + + return .Ok((BlockStatement(statements), scopes)) + } +} + +extension ConditionalStatement: ParseableStatement { + public static func Parse( + node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes + ) -> Result<(EvaluatableStatement?, LexicalScopes)> { + + guard let node_type = node.nodeType, + node_type == "conditionalStatement" + else { + return Result.Ok((.none, scopes)) + } + + let maybe_condition_expression = node.child(at: 2) + guard let condition_expression = maybe_condition_expression, + condition_expression.nodeType == "expression" + else { + return Result.Ok((.none, scopes)) + } + + let maybe_thens = node.child(at: 4) + guard let thens = maybe_thens, + thens.nodeType == "statement" + else { + return Result.Ok((.none, scopes)) + } + + guard + case .Ok(let condition) = Expression.Parse( + node: condition_expression, inTree: tree, withScopes: scopes.enter()) + else { + return Result.Error( + Error(withMessage: "Could not parse a conditional expression in a conditional statement")) + } + + guard + case .Ok((let thenns, _)) = Parser.Statements.Parse( + node: thens, inTree: tree, withScope: scopes.enter()) + else { + return Result.Error( + Error( + withMessage: + "Could not parse the then block in a conditional statement")) + } + + let optional_elss: Result<(any EvaluatableStatement, LexicalScopes)>? = + if let elss = node.child(at: 6) { + .some( + Parser.Statements.Parse( + node: elss, inTree: tree, withScope: scopes.enter())) + } else { + .none + } + + if let parsed_elss = optional_elss { + guard + case .Ok((let elss, _)) = parsed_elss + else { + return Result.Error( + Error( + withMessage: + "Could not parse the else block in a conditional statement")) + } + return .Ok( + (ConditionalStatement(condition: condition, withThen: thenns, andElse: elss), scopes)) + } + return .Ok((ConditionalStatement(condition: condition, withThen: thenns), scopes)) + } +} + extension VariableDeclarationStatement: ParseableStatement { public static func Parse( node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes @@ -86,3 +222,31 @@ extension VariableDeclarationStatement: ParseableStatement { } } } + +extension ExpressionStatement: ParseableStatement { + public static func Parse( + node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes + ) -> Result<(EvaluatableStatement?, LexicalScopes)> { + guard + let expression_statement_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(expressionStatement (expression) @expression)" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Ok((.none, scopes)) + } + + let qr = expression_statement_query.execute(node: node, in: tree) + 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(), scopes)) + } + return Result.Ok((.none, scopes)) + } +} diff --git a/Sources/P4Runtime/Program.swift b/Sources/P4Runtime/Program.swift index 905beaa..cfd4fc5 100644 --- a/Sources/P4Runtime/Program.swift +++ b/Sources/P4Runtime/Program.swift @@ -18,6 +18,16 @@ import Common import P4Lang +extension BlockStatement: EvaluatableStatement { + public func evaluate(execution: ProgramExecution) -> ProgramExecution { + var execution = execution + for s in self.statements { + execution = s.evaluate(execution: execution) + } + return execution + } +} + extension VariableDeclarationStatement: EvaluatableStatement { public func evaluate(execution: ProgramExecution) -> ProgramExecution { guard case .Ok(let initial_value) = self.initializer.evaluate(execution: execution) else { @@ -29,6 +39,30 @@ extension VariableDeclarationStatement: EvaluatableStatement { } } +extension ConditionalStatement: EvaluatableStatement { + public func evaluate(execution: ProgramExecution) -> ProgramExecution { + guard case .Ok(let initial_value) = self.condition.evaluate(execution: execution) else { + return execution.setError(error: Error(withMessage: "Could not evaluate \(self.condition)")) + } + if !initial_value.type().eq(rhs: P4Boolean.create()) { + return execution.setError(error: Error(withMessage: "Condition expression is not a Boolean")) + } + if initial_value.eq(rhs: P4BooleanValue.init(withValue: true)) { + let execution = execution.enter_scope() + var result = self.thenn.evaluate(execution: execution) + result = result.exit_scope() + return result + } else if let elss = self.elss { + let execution = execution.enter_scope() + var result = elss.evaluate(execution: execution) + result = result.exit_scope() + return result + } + return execution + } +} + + extension ExpressionStatement: EvaluatableStatement { public func evaluate(execution: ProgramExecution) -> ProgramExecution { return execution diff --git a/Tests/p4rseTests/RuntimeTests.swift b/Tests/p4rseTests/RuntimeTests.swift index 1d6e311..be7fd73 100644 --- a/Tests/p4rseTests/RuntimeTests.swift +++ b/Tests/p4rseTests/RuntimeTests.swift @@ -17,8 +17,8 @@ import Common import Foundation -import P4Lang import Macros +import P4Lang import P4Runtime import SwiftTreeSitter import Testing @@ -62,7 +62,6 @@ import TreeSitterP4 #expect(state_result == P4Lang.reject) } - @Test func test_simple_runtime_no_start_state() async throws { let simple_parser_declaration = """ parser main_parser() { @@ -93,7 +92,6 @@ import TreeSitterP4 }; """ - let program = try #UseOkResult(Program.Parse(simple_parser_declaration)) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) @@ -124,3 +122,70 @@ import TreeSitterP4 #expect(parser.states.count() == 1) #expect(state_result == P4Lang.reject) } + +@Test func test_simple_parser_with_conditional_statement() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool x = true; + string check = "Invalid"; + if (x) { + x = false; + check = "valid"; + }; + transition select (x) { + false: reject; + true: accept; + }; + } + }; + """ + + let program = try #UseOkResult(Program.Parse(simple_parser_declaration)) + let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, exec_result) = try! #UseOkResult(runtime.run()) + + #expect(parser.states.count() == 1) + #expect(state_result == P4Lang.reject) + + let x = try #UseOkResult(exec_result.scopes.lookup(identifier: Identifier(name: "x"))) + #expect(x.eq(rhs: P4BooleanValue(withValue: false))) + let check = try #UseOkResult(exec_result.scopes.lookup(identifier: Identifier(name: "check"))) + #expect(check.eq(rhs: P4StringValue(withValue: "\"valid\""))) +} + +@Test func test_simple_parser_with_conditional_statement_and_else() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool x = false; + string check = "Invalid"; + if (x) { + x = false; + check = "a"; + } else { + x = true; + check = "b"; + }; + transition select (x) { + false: reject; + true: accept; + }; + } + }; + """ + + let program = try #UseOkResult(Program.Parse(simple_parser_declaration)) + let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program)) + let (state_result, exec_result) = try! #UseOkResult(runtime.run()) + + #expect(parser.states.count() == 1) + #expect(state_result == P4Lang.accept) + + let x = try #UseOkResult(exec_result.scopes.lookup(identifier: Identifier(name: "x"))) + #expect(x.eq(rhs: P4BooleanValue(withValue: true))) + let check = try #UseOkResult(exec_result.scopes.lookup(identifier: Identifier(name: "check"))) + #expect(check.eq(rhs: P4StringValue(withValue: "\"b\""))) +} diff --git a/Tests/p4rseTests/ValueTypeParserTests.swift b/Tests/p4rseTests/ValueTypeParserTests.swift index 4d6a6fb..faac429 100644 --- a/Tests/p4rseTests/ValueTypeParserTests.swift +++ b/Tests/p4rseTests/ValueTypeParserTests.swift @@ -39,6 +39,7 @@ import TreeSitterP4 let simple_parser_declaration = """ parser main_parser() { state start { + string g = "Testing"; string where_to = "Testing"; where_to = true; transition reject; diff --git a/tree-sitter-p4/test/corpus/statements.txt b/tree-sitter-p4/test/corpus/statements.txt index 173d3f6..7a07923 100644 --- a/tree-sitter-p4/test/corpus/statements.txt +++ b/tree-sitter-p4/test/corpus/statements.txt @@ -173,3 +173,210 @@ parser simple() { (semicolon) ) +========================= +Simple Conditional Statement +========================= +parser simple() { + state start { + bool x = true; + if (x) { + x = false; + }; + transition accept; + } +}; + +--- +(p4program + (declaration + (parserDeclaration + (parserType + (parser) + (identifier) + ) + (parserStates + (parserState + (state) + (identifier) + (parserLocalElements + (parserLocalElement + (variableDeclaration + (typeRef + (baseType + (bool) + ) + ) + (identifier) + (assignment) + (expression + (true) + ) + ) + ) + (semicolon) + ) + (parserStatements + (parserStatement + (conditionalStatement + (if) + (expression + (identifier) + ) + (statement + (blockStatement + (statements + (statement + (assignmentStatement + (expression + (identifier) + ) + (assignment) + (expression + (false) + ) + ) + ) + (semicolon) + ) + ) + ) + ) + ) + (semicolon) + ) + (parserTransitionStatement + (transition) + (transitionSelectionExpression + (identifier) + ) + ) + (semicolon) + ) + ) + ) + ) + (semicolon) +) + +========================= +Simple Conditional Statement (with else) +========================= +parser simple() { + state start { + bool x = true; + int y = 0; + if (x) { + y = 1; + } else { + y = 2; + }; + transition accept; + } +}; + +--- +(p4program + (declaration + (parserDeclaration + (parserType + (parser) + (identifier) + ) + (parserStates + (parserState + (state) + (identifier) + (parserLocalElements + (parserLocalElement + (variableDeclaration + (typeRef + (baseType + (bool) + ) + ) + (identifier) + (assignment) + (expression + (true) + ) + ) + ) + (semicolon) + (parserLocalElement + (variableDeclaration + (typeRef + (baseType + (int) + ) + ) + (identifier) + (assignment) + (expression + (integer) + ) + ) + ) + (semicolon) + ) + (parserStatements + (parserStatement + (conditionalStatement + (if) + (expression + (identifier) + ) + (statement + (blockStatement + (statements + (statement + (assignmentStatement + (expression + (identifier) + ) + (assignment) + (expression + (integer) + ) + ) + ) + (semicolon) + ) + ) + ) + (else) + (statement + (blockStatement + (statements + (statement + (assignmentStatement + (expression + (identifier) + ) + (assignment) + (expression + (integer) + ) + ) + ) + (semicolon) + ) + ) + ) + ) + ) + (semicolon) + ) + (parserTransitionStatement + (transition) + (transitionSelectionExpression + (identifier) + ) + ) + (semicolon) + ) + ) + ) + ) + (semicolon) +) +