b687454389
By adding an expected type to the compilation context, it is now possible for type checking to occur on keyset expressions and return statements at the moment that they are being compiled. Previously, it was necessary to tentatively compile them and then typecheck afterward. Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
425 lines
14 KiB
Swift
425 lines
14 KiB
Swift
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
import Common
|
|
import P4Lang
|
|
|
|
extension SelectCaseExpression: EvaluatableExpression {
|
|
public func evaluate(execution: Common.ProgramExecution) -> Common.Result<any Common.P4Value> {
|
|
return execution.scopes.lookup(identifier: next_state_identifier)
|
|
}
|
|
|
|
public func type() -> any Common.P4Type {
|
|
return ParserState()
|
|
}
|
|
}
|
|
|
|
extension SelectExpression: EvaluatableExpression {
|
|
public func evaluate(execution: Common.ProgramExecution) -> Common.Result<any Common.P4Value> {
|
|
switch self.selector.evaluate(execution: execution) {
|
|
case .Ok(let selector_value):
|
|
for sce in self.select_expressions {
|
|
if case .Ok(let kse) = sce.key.evaluate(execution: execution),
|
|
kse.eq(rhs: selector_value)
|
|
{
|
|
let result = sce.evaluate(execution: execution)
|
|
return result
|
|
}
|
|
}
|
|
return .Error(Error(withMessage: "No key matched the selector"))
|
|
case .Error(let e): return .Error(e)
|
|
}
|
|
}
|
|
|
|
public func type() -> any Common.P4Type {
|
|
return ParserState()
|
|
}
|
|
}
|
|
|
|
// Variables are evaluatable because they can be looked up by identifiers.
|
|
extension TypedIdentifier: EvaluatableExpression {
|
|
public func type() -> any Common.P4Type {
|
|
return self.type
|
|
}
|
|
|
|
public func evaluate(execution: Common.ProgramExecution) -> Result<P4Value> {
|
|
return execution.scopes.lookup(identifier: self)
|
|
}
|
|
}
|
|
|
|
// Variables are evaluatable because they can be looked up by identifiers.
|
|
extension TypedIdentifier: EvaluatableLValueExpression {
|
|
public func set(
|
|
to: any Common.P4Value, inScopes scopes: Common.VarValueScopes,
|
|
duringExecution execution: ProgramExecution
|
|
) -> Common.Result<(Common.VarValueScopes, 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.VarTypeScopes
|
|
) -> 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 {
|
|
return Map(input: left.eq(rhs: right)) { input in
|
|
P4BooleanValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_lt_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
return Map(input: left.lt(rhs: right)) { input in
|
|
P4BooleanValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_lte_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
return Map(input: left.lte(rhs: right)) { input in
|
|
P4BooleanValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_gt_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
return Map(input: left.gt(rhs: right)) { input in
|
|
P4BooleanValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_gte_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
return Map(input: left.gte(rhs: right)) { input in
|
|
P4BooleanValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_and_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
let bleft = left as! P4BooleanValue
|
|
let bright = right as! P4BooleanValue
|
|
return Map(input: bleft.access() && bright.access()) { input in
|
|
P4BooleanValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_or_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
let bleft = left as! P4BooleanValue
|
|
let bright = right as! P4BooleanValue
|
|
return Map(input: bleft.access() || bright.access()) { input in
|
|
P4BooleanValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_add_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
let ileft = left as! P4IntValue
|
|
let iright = right as! P4IntValue
|
|
return Map(input: ileft.access() + iright.access()) { input in
|
|
P4IntValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_subtract_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
let ileft = left as! P4IntValue
|
|
let iright = right as! P4IntValue
|
|
return Map(input: ileft.access() - iright.access()) { input in
|
|
P4IntValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_multiply_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
let ileft = left as! P4IntValue
|
|
let iright = right as! P4IntValue
|
|
return Map(input: ileft.access() * iright.access()) { input in
|
|
P4IntValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
public func binary_divide_operator_evaluator(left: P4Value, right: P4Value) -> P4Value {
|
|
let ileft = left as! P4IntValue
|
|
let iright = right as! P4IntValue
|
|
return Map(input: ileft.access() / iright.access()) { input in
|
|
P4IntValue(withValue: input)
|
|
}
|
|
}
|
|
|
|
// swift-format-ignore
|
|
public typealias BinaryOperatorChecker = (EvaluatableExpression, EvaluatableExpression) -> Result<()>
|
|
|
|
public func binary_and_or_operator_checker(
|
|
left: EvaluatableExpression, right: EvaluatableExpression
|
|
) -> Result<()> {
|
|
// Check that both are Boolean-typed things!
|
|
if !(left.type().eq(rhs: P4Boolean()) && right.type().eq(rhs: P4Boolean())) {
|
|
return .Error(Error(withMessage: "And/Or on operands with non-bool type is not allowed"))
|
|
}
|
|
return .Ok(())
|
|
}
|
|
|
|
public func binary_int_math_operator_checker(
|
|
left: EvaluatableExpression, right: EvaluatableExpression
|
|
) -> Result<()> {
|
|
// Check that both are int-typed things!
|
|
if !(left.type().eq(rhs: P4Int()) && right.type().eq(rhs: P4Int())) {
|
|
return .Error(
|
|
Error(withMessage: "Mathematical operation on operands with non-int type is not allowed"))
|
|
}
|
|
return .Ok(())
|
|
}
|
|
|
|
extension BinaryOperatorExpression: EvaluatableExpression {
|
|
public func evaluate(execution: Common.ProgramExecution) -> Common.Result<any Common.P4Value> {
|
|
let maybe_evaluated_left = self.left.evaluate(execution: execution)
|
|
guard case Result.Ok(let evaluated_left) = maybe_evaluated_left else {
|
|
return maybe_evaluated_left
|
|
}
|
|
|
|
let maybe_evaluated_right = self.right.evaluate(execution: execution)
|
|
guard case Result.Ok(let evaluated_right) = maybe_evaluated_right else {
|
|
return maybe_evaluated_right
|
|
}
|
|
|
|
return Result.Ok(self.evaluator.2(evaluated_left, evaluated_right))
|
|
}
|
|
|
|
public func type() -> any Common.P4Type {
|
|
return self.evaluator.1
|
|
}
|
|
}
|
|
|
|
extension ArrayAccessExpression: EvaluatableExpression {
|
|
public func evaluate(execution: Common.ProgramExecution) -> Common.Result<any Common.P4Value> {
|
|
let maybe_name = self.name.evaluate(execution: execution)
|
|
guard case Result.Ok(let name) = maybe_name else {
|
|
return maybe_name
|
|
}
|
|
|
|
let maybe_indexor = self.indexor.evaluate(execution: execution)
|
|
guard case Result.Ok(let indexor) = maybe_indexor else {
|
|
return maybe_indexor
|
|
}
|
|
|
|
guard let indexor_int = indexor as? P4IntValue else {
|
|
return Result.Error(Error(withMessage: "\(indexor) cannot index an array"))
|
|
}
|
|
|
|
guard let array = name as? P4ArrayValue else {
|
|
return Result.Error(Error(withMessage: "\(name) does not name an array"))
|
|
}
|
|
let accessed = array.access(indexor_int.access())
|
|
|
|
return .Ok(accessed)
|
|
}
|
|
|
|
public func type() -> any Common.P4Type {
|
|
return self.type.value_type()
|
|
}
|
|
}
|
|
|
|
extension ArrayAccessExpression: EvaluatableLValueExpression {
|
|
public func set(
|
|
to: any Common.P4Value, inScopes scopes: Common.VarValueScopes,
|
|
duringExecution execution: ProgramExecution
|
|
) -> Common.Result<(Common.VarValueScopes, 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.VarTypeScopes
|
|
) -> 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<any Common.P4Value> {
|
|
let maybe_struct = self.strct.evaluate(execution: execution)
|
|
guard case Result.Ok(let strct) = maybe_struct else {
|
|
return maybe_struct
|
|
}
|
|
|
|
guard let struct_strct = strct as? P4StructValue else {
|
|
return Result.Error(Error(withMessage: "\(strct) does not identify a struct"))
|
|
}
|
|
|
|
// TODO: Create a default value?
|
|
guard let value = struct_strct.get(field: self.field) else {
|
|
return .Error(Error(withMessage: "Missing value"))
|
|
}
|
|
|
|
return .Ok(value)
|
|
}
|
|
|
|
public func type() -> any Common.P4Type {
|
|
return self.field.type
|
|
}
|
|
}
|
|
|
|
extension FieldAccessExpression: EvaluatableLValueExpression {
|
|
public func set(
|
|
to: any Common.P4Value, inScopes scopes: Common.VarValueScopes,
|
|
duringExecution execution: ProgramExecution
|
|
) -> Common.Result<(Common.VarValueScopes, 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.VarTypeScopes
|
|
) -> 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(())
|
|
}
|
|
}
|
|
|
|
extension KeysetExpression: EvaluatableExpression {
|
|
public func evaluate(execution: Common.ProgramExecution) -> Result<P4Value> {
|
|
return self.key.evaluate(execution: execution)
|
|
}
|
|
|
|
public func type() -> P4Type {
|
|
return self.key.type()
|
|
}
|
|
}
|
|
|
|
extension FunctionCall: EvaluatableExpression {
|
|
public func evaluate(execution: Common.ProgramExecution) -> Common.Result<any Common.P4Value> {
|
|
|
|
guard let body = self.callee.body else {
|
|
return .Error(Error(withMessage: "No body for called function (\(self.callee.name))"))
|
|
}
|
|
|
|
// Put the arguments into scope
|
|
|
|
var called_execution = execution.enter_scope()
|
|
for (parameter, argument) in zip(self.callee.params.parameters, arguments.arguments) {
|
|
let arg_idx = argument.index
|
|
let arg_value = argument.argument
|
|
let maybe_argument_value = arg_value.evaluate(execution: called_execution)
|
|
guard case .Ok(let argument_value) = maybe_argument_value else {
|
|
return .Error(Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)"))
|
|
}
|
|
called_execution = called_execution.declare(
|
|
identifier: parameter.name, withValue: argument_value)
|
|
}
|
|
|
|
let (control_flow, _) = body.evaluate(execution: called_execution)
|
|
|
|
return switch control_flow {
|
|
case ControlFlow.Return(let value):
|
|
if let value = value {
|
|
.Ok(value)
|
|
} else {
|
|
.Error(Error(withMessage: "No value returned from called function (\(self.callee.name))"))
|
|
}
|
|
default:
|
|
.Error(Error(withMessage: "No value returned from called function (\(self.callee.name))"))
|
|
}
|
|
}
|
|
|
|
public func type() -> any Common.P4Type {
|
|
return self.callee.tipe
|
|
}
|
|
}
|