diff --git a/Sources/Common/Compiler.swift b/Sources/Common/Compiler.swift new file mode 100644 index 0000000..329a45a --- /dev/null +++ b/Sources/Common/Compiler.swift @@ -0,0 +1,19 @@ +// 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 . + +public typealias TypeScope = Scope +public typealias TypeScopes = Scopes diff --git a/Sources/Common/ProgramTypes.swift b/Sources/Common/ProgramTypes.swift index 3c098dc..bc9dbd2 100644 --- a/Sources/Common/ProgramTypes.swift +++ b/Sources/Common/ProgramTypes.swift @@ -117,6 +117,18 @@ public struct P4StructFields: Sequence, CustomStringConvertible, Equatable { public func count() -> Int { return self.fields.count } + + public func describe_with_values(values: [P4Value?]) -> String { + assert(values.count == self.count()) + return zip(self.fields, values).map() { (field, value) in + let actual_value = if let v = value { + v.description + } else { + "Unset" + } + return String("\(field): \(actual_value)") + }.joined(separator: "; ") + } } /// The type for a P4 struct @@ -159,7 +171,7 @@ public class P4StructValue: P4Value { } public var description: String { - return "Struct" + return "Struct: \(self.stype.fields.describe_with_values(values: self.values))" } public let stype: P4Struct @@ -169,7 +181,7 @@ public class P4StructValue: P4Value { self.init(withType: type, andInitializers: []) } - public init(withType type: P4Struct, andInitializers initializers: [P4Value]) { + public init(withType type: P4Struct, andInitializers initializers: [P4Value?]) { var values: [P4Value?] = Array(repeating: .none, count: type.fields.count()) for i in 0.. Result { + var updated_values = self.values + + for field_idx in 0.. EvaluatableExpression { + public func access(_ index: Int) -> P4Value { return self.value[index] } + public func set(index: Int, to: P4Value) -> Result { + // TODO: Check for OOB + var updated_values = self.value + updated_values[index] = to + return Result.Ok(P4ArrayValue(withType: self.vtype, withValue: updated_values)) + } + public func eq(rhs: P4Value) -> Bool { guard rhs as? P4ArrayValue != nil else { return false diff --git a/Sources/Common/Protocols.swift b/Sources/Common/Protocols.swift index 9d93d4d..affbe3e 100644 --- a/Sources/Common/Protocols.swift +++ b/Sources/Common/Protocols.swift @@ -40,3 +40,8 @@ public protocol P4Value: CustomStringConvertible { func type() -> any P4Type func eq(rhs: P4Value) -> Bool } + +public protocol EvaluatableLValueExpression: EvaluatableExpression { + func set(to: P4Value, inScopes scopes: ValueScopes, duringExecution execution: ProgramExecution) -> Result<(ValueScopes, P4Value)> + func check(to: EvaluatableExpression, inScopes scopes: TypeScopes) -> Result<()> +} diff --git a/Sources/P4Compiler/Expression.swift b/Sources/P4Compiler/Expression.swift index 71f0617..8f93d1e 100644 --- a/Sources/P4Compiler/Expression.swift +++ b/Sources/P4Compiler/Expression.swift @@ -27,6 +27,12 @@ protocol CompilableExpression { ) -> Result } +protocol CompilableLValueExpression { + static func compile_as_lvalue( + node: Node, withContext context: CompilerContext + ) -> Result +} + extension TypedIdentifier: CompilableExpression { static func compile( node: SwiftTreeSitter.Node, withContext context: CompilerContext @@ -47,6 +53,26 @@ extension TypedIdentifier: CompilableExpression { } } +extension TypedIdentifier: CompilableLValueExpression { + static func compile_as_lvalue( + node: SwiftTreeSitter.Node, withContext context: CompilerContext + ) -> Result { + + let expression = node.child(at: 0)! + #SkipUnlessNodeType( + node: expression, type: "identifier") + + let maybe_parsed_expression = TypedIdentifier.compile(node: node, withContext: context) + guard case .Ok(let maybe_typed_identifier) = maybe_parsed_expression else { + return .Error(maybe_parsed_expression.error()!) + } + + let typed_identifier_expression = maybe_typed_identifier as! TypedIdentifier + + return Result.Ok(typed_identifier_expression) + } +} + extension P4BooleanValue: CompilableExpression { static func compile( node: SwiftTreeSitter.Node, withContext context: CompilerContext @@ -130,12 +156,25 @@ struct Expression { struct LValue { public static func Compile( node: Node, withContext: CompilerContext - ) -> Result { - return if let node_text_value = node.text { - .Ok(Common.Identifier(name: node_text_value)) - } else { - .Error(Error(withMessage: "Could not parse an identifier for an LValue")) + ) -> Result { + + // Try to compile all the expressions that are LValuable! + + let lvalueParsers: [CompilableLValueExpression.Type] = [ + TypedIdentifier.self, FieldAccessExpression.self, ArrayAccessExpression.self, + ] + + for lvalue_parser in lvalueParsers { + switch lvalue_parser.compile_as_lvalue( + node: node, withContext: withContext) + { + case .Ok(.some(let parsed)): return .Ok(parsed) + case .Error(let e): return .Error(e) + default: continue + } } + + return Result.Error(Error(withMessage: "\(node.range): Could not parse into expression")) } } @@ -477,3 +516,41 @@ extension FieldAccessExpression: CompilableExpression { withField: P4StructFieldIdentifier(id: field_name, withType: field_type))) } } + +extension FieldAccessExpression: CompilableLValueExpression { + static func compile_as_lvalue( + node: SwiftTreeSitter.Node, withContext context: CompilerContext + ) -> Result { + let expression = node.child(at: 0)! + #SkipUnlessNodeType( + node: expression, type: "fieldAccessExpression") + + let maybe_parsed_expression = FieldAccessExpression.compile(node: node, withContext: context) + guard case .Ok(let maybe_field_access_expression) = maybe_parsed_expression else { + return .Error(maybe_parsed_expression.error()!) + } + + let field_access_expression = maybe_field_access_expression as! FieldAccessExpression + + return Result.Ok(field_access_expression) + } +} + +extension ArrayAccessExpression: CompilableLValueExpression { + static func compile_as_lvalue( + node: SwiftTreeSitter.Node, withContext context: CompilerContext + ) -> Result { + let expression = node.child(at: 0)! + #SkipUnlessNodeType( + node: expression, type: "arrayAccessExpression") + + let maybe_parsed_expression = ArrayAccessExpression.compile(node: node, withContext: context) + guard case .Ok(let maybe_array_access_expression) = maybe_parsed_expression else { + return .Error(maybe_parsed_expression.error()!) + } + + let array_access_expression = maybe_array_access_expression as! ArrayAccessExpression + + return Result.Ok(array_access_expression) + } +} \ No newline at end of file diff --git a/Sources/P4Compiler/Parser.swift b/Sources/P4Compiler/Parser.swift index 326b577..ad79633 100644 --- a/Sources/P4Compiler/Parser.swift +++ b/Sources/P4Compiler/Parser.swift @@ -22,65 +22,6 @@ import SwiftTreeSitter import TreeSitterExtensions import TreeSitterP4 -extension ParserAssignmentStatement: CompilableStatement { - public static func Compile( - node: Node, withContext context: CompilerContext - ) -> Result<(EvaluatableStatement, CompilerContext)> { - - #RequireNodeType( - node: node, type: "assignmentStatement", nice_type_name: "assignment statement") - - guard let lvalue_node = node.child(at: 0), - lvalue_node.nodeType == "expression" - else { - return Result.Error( - ErrorOnNode(node: node, withError: "Missing lvalue in assignment statement")) - } - - guard let rvalue_node = node.child(at: 2), - rvalue_node.nodeType == "expression" - else { - return Result.Error( - ErrorOnNode(node: node, withError: "Missing rvalue in assignment statement")) - } - - let maybe_parsed_rvalue = Expression.Compile( - node: rvalue_node, withContext: context) - guard case Result.Ok(let rvalue) = maybe_parsed_rvalue else { - return Result.Error(maybe_parsed_rvalue.error()!) - } - - let maybe_parsed_lvalue = LValue.Compile(node: lvalue_node, withContext: context) - guard case .Ok(let lvalue_identifier) = maybe_parsed_lvalue else { - return Result.Error(maybe_parsed_lvalue.error()!) - } - guard case Result.Ok(let lvalue_type) = context.names.lookup(identifier: lvalue_identifier) - else { - return Result.Error( - ErrorOnNode( - node: lvalue_node, - withError: "Cannot assign to variable \(lvalue_identifier) not in scope")) - } - - if rvalue.type().eq(rhs: lvalue_type) { - return Result.Ok( - ( - ParserAssignmentStatement( - withLValue: TypedIdentifier(name: lvalue_node.text!, withType: lvalue_type), - withValue: rvalue - ), context - )) - - } else { - return Result.Error( - ErrorOnNode( - node: node, - withError: - "Cannot assign value of type \(rvalue.type()) to \(lvalue_identifier) (with type \(lvalue_type))" - )) - } - } -} public struct Parser { public struct LocalElements { diff --git a/Sources/P4Compiler/Statement.swift b/Sources/P4Compiler/Statement.swift index eb8801d..e5c6478 100644 --- a/Sources/P4Compiler/Statement.swift +++ b/Sources/P4Compiler/Statement.swift @@ -246,3 +246,54 @@ extension ExpressionStatement: CompilableStatement { return Result.Ok((ExpressionStatement(), context)) } } + +extension ParserAssignmentStatement: CompilableStatement { + public static func Compile( + node: Node, withContext context: CompilerContext + ) -> Result<(EvaluatableStatement, CompilerContext)> { + + #RequireNodeType( + node: node, type: "assignmentStatement", nice_type_name: "assignment statement") + + guard let lvalue_node = node.child(at: 0), + lvalue_node.nodeType == "expression" + else { + return Result.Error( + ErrorOnNode(node: node, withError: "Missing lvalue in assignment statement")) + } + + guard let rvalue_node = node.child(at: 2), + rvalue_node.nodeType == "expression" + else { + return Result.Error( + ErrorOnNode(node: node, withError: "Missing rvalue in assignment statement")) + } + + let maybe_parsed_rvalue = Expression.Compile( + node: rvalue_node, withContext: context) + guard case Result.Ok(let rvalue) = maybe_parsed_rvalue else { + return Result.Error(maybe_parsed_rvalue.error()!) + } + + let maybe_parsed_lvalue = LValue.Compile(node: lvalue_node, withContext: context) + guard case .Ok(let lvalue_identifier) = maybe_parsed_lvalue else { + return Result.Error(maybe_parsed_lvalue.error()!) + } + + let check_result = lvalue_identifier.check(to: rvalue, inScopes: context.names) + guard case .Ok(_) = check_result else { + return Result.Error( + ErrorOnNode( + node: lvalue_node, + withError: "\(check_result.error()!)")) + } + + return Result.Ok( + ( + ParserAssignmentStatement( + withLValue: lvalue_identifier, + withValue: rvalue + ), context + )) + } +} diff --git a/Sources/P4Lang/Parser.swift b/Sources/P4Lang/Parser.swift index 6a26739..99101e6 100644 --- a/Sources/P4Lang/Parser.swift +++ b/Sources/P4Lang/Parser.swift @@ -26,10 +26,10 @@ public struct LocalElement { } public struct ParserAssignmentStatement { - public let lvalue: TypedIdentifier + public let lvalue: EvaluatableLValueExpression public let value: EvaluatableExpression - public init(withLValue lvalue: TypedIdentifier, withValue value: EvaluatableExpression) { + public init(withLValue lvalue: EvaluatableLValueExpression, withValue value: EvaluatableExpression) { self.lvalue = lvalue self.value = value } diff --git a/Sources/P4Runtime/Expressions.swift b/Sources/P4Runtime/Expressions.swift index 9a134ef..2afb127 100644 --- a/Sources/P4Runtime/Expressions.swift +++ b/Sources/P4Runtime/Expressions.swift @@ -93,6 +93,30 @@ extension TypedIdentifier: EvaluatableExpression { } } +// Variables are evaluatable because they can be looked up by identifiers. +extension TypedIdentifier: EvaluatableLValueExpression { + public func set( + to: any Common.P4Value, inScopes scopes: Common.ValueScopes, duringExecution execution: ProgramExecution + ) -> Common.Result<(Common.ValueScopes, P4Value)> { + if case .Error(let e) = scopes.lookup(identifier: self) { + return .Error(e) + } + + return .Ok((scopes.set(identifier: self, withValue: to), to)) + } + + public func check(to: any Common.EvaluatableExpression, inScopes scopes: Common.TypeScopes) -> Result<()> { + guard case .Ok(let type) = scopes.lookup(identifier: self) else { + return .Error(Error(withMessage: "Cannot assign to identifier not in scope")) + } + + if !type.eq(rhs: to.type()) { + return .Error(Error(withMessage: "Cannot assign value with type \(to.type()) to identifier \(self) with type \(type)")) + } + return .Ok(()) + } +} + public func binary_equal_operator_evaluator(left: P4Value, right: P4Value) -> P4Value { if left.eq(rhs: right) { return P4BooleanValue(withValue: true) @@ -141,7 +165,7 @@ extension ArrayAccessExpression: EvaluatableExpression { } let accessed = array.access(indexor_int.access()) - return accessed.evaluate(execution: execution) + return .Ok(accessed) } public func type() -> any Common.P4Type { @@ -149,6 +173,56 @@ extension ArrayAccessExpression: EvaluatableExpression { } } +extension ArrayAccessExpression: EvaluatableLValueExpression { + public func set( + to: any Common.P4Value, inScopes scopes: Common.ValueScopes, duringExecution execution: ProgramExecution + ) -> Common.Result<(Common.ValueScopes, P4Value)> { + // For purposes of documentation, assume the field access expression we are evaluating is + // (strct_id)[indexor] = new_value + // where strct_id expands to + // (identifier.field_id1.field_id2...).field_id = new_field_value + + // First, evaluate strct_id and make sure that it names a struct. + let maybe_value = self.name.evaluate(execution: execution) + guard case .Ok(let value) = maybe_value else { + return Result.Error(Error(withMessage: "\(self.name) cannot be evaluated: \(maybe_value.error()!)")) + } + guard let array_value = value as? P4ArrayValue else { + return Result.Error(Error(withMessage: "\(self.name) does not identify a struct")) + } + + // Now, get the indexor! + let maybe_indexor_value = self.indexor.evaluate(execution: execution) + guard case .Ok(let indexor_value) = maybe_indexor_value else { + return Result.Error(Error(withMessage: "\(self.indexor) cannot be evaluated: \(maybe_indexor_value.error()!)")) + } + guard let indexor_int = indexor_value as? P4IntValue else { + return Result.Error(Error(withMessage: "\(self.indexor) cannot be used to index an array")) + } + + // Now we have an array and an index! + + // Update field_id of that structure and get the new structure value. + let set_result = array_value.set(index: indexor_int.access(), to: to) + guard case .Ok(let new_array_value) = set_result else { + return .Error(set_result.error()!) + } + + let array_lvalue = self.name as! EvaluatableLValueExpression + return array_lvalue.set(to: new_array_value, inScopes: scopes, duringExecution: execution) + } + + public func check( + to: any Common.EvaluatableExpression, inScopes scopes: Common.TypeScopes + ) -> Common.Result<()> { + + if !self.type.value_type().eq(rhs: to.type()) { + return .Error(Error(withMessage: "Cannot assign value of type \(to.type()) to array with values of type \(self.name.type())")) + } + return .Ok(()) + } +} + extension FieldAccessExpression: EvaluatableExpression { public func evaluate(execution: Common.ProgramExecution) -> Common.Result { let maybe_struct = self.strct.evaluate(execution: execution) @@ -172,3 +246,48 @@ extension FieldAccessExpression: EvaluatableExpression { return self.field.type } } + +extension FieldAccessExpression: EvaluatableLValueExpression { + public func set( + to: any Common.P4Value, inScopes scopes: Common.ValueScopes, duringExecution execution: ProgramExecution + ) -> Common.Result<(Common.ValueScopes, P4Value)> { + // For purposes of documentation, assume the field access expression we are evaluating is + // (strct_id).field_id = new_field_value + // where strct_id expands to + // (identifier.field_id1.field_id2...).field_id = new_field_value + + // First, evaluate strct_id and make sure that it names a struct. + let maybe_value = self.strct.evaluate(execution: execution) + guard case .Ok(let value) = maybe_value else { + return Result.Error(Error(withMessage: "\(self.strct) cannot be evaluated: \(maybe_value.error()!)")) + } + + guard let struct_value = value as? P4StructValue else { + return Result.Error(Error(withMessage: "\(self.strct) does not identify a struct")) + } + + // Now we know that struct_id identifies a structure value. + + // Update field_id of that structure and get the new structure value. + let set_result = struct_value.set(field: self.field, to: to) + guard case .Ok(let new_struct_value) = set_result else { + return .Error(set_result.error()!) + } + + // That new structure value should be assignable to the lvalue that is strct_id. + // We use recursion here -- ultimately finding our way to a TypedIdentifier that + // will update the scope. Pretty cool! + let struct_lvalue = self.strct as! EvaluatableLValueExpression + return struct_lvalue.set(to: new_struct_value, inScopes: scopes, duringExecution: execution) + } + + public func check( + to: any Common.EvaluatableExpression, inScopes scopes: Common.TypeScopes + ) -> Common.Result<()> { + + if !self.field.type.eq(rhs:to.type()) { + return .Error(Error(withMessage: "Cannot assign value of type \(to.type()) to field with type \(self.field.type)")) + } + return .Ok(()) + } +} \ No newline at end of file diff --git a/Sources/P4Runtime/Parser.swift b/Sources/P4Runtime/Parser.swift index 93cf585..43bc6f8 100644 --- a/Sources/P4Runtime/Parser.swift +++ b/Sources/P4Runtime/Parser.swift @@ -24,9 +24,13 @@ extension ParserAssignmentStatement: EvaluatableStatement { guard case Result.Ok(let value) = result else { return execution.setError(error: result.error()!) } - let updated_scopes = execution.scopes.set(identifier: self.lvalue, withValue: value) - execution.scopes = updated_scopes + let maybe_updated_scopes = self.lvalue.set( + to: value, inScopes: execution.scopes, duringExecution: execution) + guard case Result.Ok(let updated_scopes) = maybe_updated_scopes else { + return execution.setError(error: maybe_updated_scopes.error()!) + } + execution.scopes = updated_scopes.0 return execution }