compiler, runtime, testing, common: Centralize Execution/Evaluation

Centralize the execution of statements and evaluation of expressions
so that the user can specify a debugging "callback" after every
execution/evaluation.

The callback can be used for myriad things, but it seems likely that
it will be useful for implementing:
1. Testing
2. Debugger

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-04-20 05:48:09 -04:00
parent c6f086f67f
commit d33066c543
13 changed files with 346 additions and 115 deletions
+41 -6
View File
@@ -15,25 +15,32 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
public typealias StatementInterloper = (EvaluatableStatement, ControlFlow, ProgramExecution) -> Void
public typealias ExpressionInterloper = (EvaluatableExpression, Result<P4Value>, ProgramExecution) -> Void
open class ProgramExecution: CustomStringConvertible {
public var scopes: VarValueScopes = VarValueScopes()
let initialValues: VarValueScopes?
var globalValues: VarValueScopes?
var error: Error?
var debug: DebugLevel = DebugLevel.Error
var statement_interloper: StatementInterloper?
var expression_interloper: ExpressionInterloper?
init(copy: ProgramExecution) {
self.scopes = copy.scopes
self.initialValues = copy.initialValues
self.globalValues = copy.globalValues
self.error = copy.error
self.debug = copy.debug
self.statement_interloper = copy.statement_interloper
self.expression_interloper = copy.expression_interloper
}
public init() {
initialValues = .none
globalValues = .none
}
public init(withGlobalValues values: VarValueScopes) {
initialValues = values
globalValues = values
}
open var description: String {
@@ -64,6 +71,26 @@ open class ProgramExecution: CustomStringConvertible {
return pe
}
public func getStatementInterloper() -> StatementInterloper? {
return self.statement_interloper
}
public func setStatementInterloper(_ interloper: @escaping StatementInterloper) -> ProgramExecution {
let pe = ProgramExecution(copy: self)
pe.statement_interloper = interloper
return pe
}
public func getExpressionInterloper() -> ExpressionInterloper? {
return self.expression_interloper
}
public func setExpressionInterloper(_ interloper: @escaping ExpressionInterloper) -> ProgramExecution {
let pe = ProgramExecution(copy: self)
pe.expression_interloper = interloper
return pe
}
open func isDone() -> Bool {
return false
}
@@ -101,9 +128,17 @@ open class ProgramExecution: CustomStringConvertible {
return new_pe
}
public func initial_values() -> VarValueScopes? {
return self.initialValues
public func getGlobalValues() -> VarValueScopes {
return self.globalValues ?? VarValueScopes()
}
public func setGlobalValues(_ global_values: VarValueScopes) -> ProgramExecution {
let new_pe = ProgramExecution(copy: self)
new_pe.globalValues = global_values
return new_pe
}
}
/// A scope that resolves variable identifiers to their values.
+2 -13
View File
@@ -117,24 +117,13 @@ public enum Result<OKT>: Equatable {
}
}
extension Result where OKT: CustomStringConvertible {
extension Result: CustomStringConvertible where OKT: CustomStringConvertible {
public var description: String {
switch self {
case Result.Error(let e):
return e.msg
case Result.Ok(let o):
return "\(o)"
}
}
}
extension Result: CustomStringConvertible {
public var description: String {
switch self {
case Result.Error(let e):
return e.msg
case Result.Ok(_):
return "Ok"
return "Ok: \(o)"
}
}
}
+1 -1
View File
@@ -90,7 +90,7 @@ public struct ParameterList: CustomStringConvertible, Equatable {
public struct ArgumentList {
public let arguments: [Argument]
public init(_ arguments: [Argument]) {
public init(_ arguments: [Argument] = []) {
self.arguments = arguments
}
+62 -1
View File
@@ -32,7 +32,8 @@ public func Call<T>(
for (parameter, argument) in zip(params.parameters, args.arguments) {
let arg_idx = argument.index
let arg_value = argument.argument
let maybe_argument_value = arg_value.evaluate(execution: called_execution)
//let maybe_argument_value = arg_value.evaluate(execution: called_execution)
let maybe_argument_value = EvaluateExpression(arg_value, inExecution: called_execution)
guard case (.Ok(let argument_value), let updated_execution) = maybe_argument_value else {
return (
.Error(Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)")),
@@ -82,3 +83,63 @@ public func Call<T>(
}
return (.Ok(call_result), updated_execution.replaceScopes(inout_scopes))
}
public typealias ExecuteStatementResultHandler = (ControlFlow, ProgramExecution) -> (ControlFlow, ProgramExecution)
public func ExecuteStatement(
_ statements: [EvaluatableStatement], handleResult handler: ExecuteStatementResultHandler,
inExecution execution: ProgramExecution,
) -> (ControlFlow, ProgramExecution) {
var debugger: StatementInterloper? = .none
var hasDebugInterloper = false
if let found_deb = execution.getStatementInterloper() {
debugger = found_deb
hasDebugInterloper = true
}
var execution = execution
for s in statements {
let (control_flow, next_execution) = s.evaluate(execution: execution)
if hasDebugInterloper {
debugger!(s, control_flow, next_execution)
}
switch handler(control_flow, next_execution) {
case (ControlFlow.Next, let handled_next_execution): execution = handled_next_execution
case (ControlFlow.Return(let value), let handled_next_execution):
return (ControlFlow.Return(value), handled_next_execution)
case (let handled_control_flow, let handled_next_execution):
return (handled_control_flow, handled_next_execution)
}
}
return (ControlFlow.Next, execution)
}
public func ExecuteStatement(
_ statement: EvaluatableStatement, handleResult handler: ExecuteStatementResultHandler,
inExecution execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
return ExecuteStatement([statement], handleResult: handler, inExecution: execution)
}
public func EvaluateExpression(
_ expression: EvaluatableExpression, inExecution execution: ProgramExecution,
) -> (Result<P4Value>, ProgramExecution) {
var debugger: ExpressionInterloper? = .none
var hasDebugInterloper = false
if let found_deb = execution.getExpressionInterloper() {
debugger = found_deb
hasDebugInterloper = true
}
let (result, execution) = expression.evaluate(execution: execution)
if hasDebugInterloper {
debugger!(expression, result, execution)
}
return (result, execution)
}
+22 -13
View File
@@ -30,14 +30,14 @@ extension SelectCaseExpression: EvaluatableExpression {
extension SelectExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
switch self.selector.evaluate(execution: execution) {
switch EvaluateExpression(self.selector, inExecution: execution) {
case (.Ok(let selector_value), let updated_execution):
for sce in self.case_expressions {
if case (.Ok(let kse), let updated_execution) = sce.key.evaluate(
execution: updated_execution),
if case (.Ok(let kse), let updated_execution) = EvaluateExpression(sce.key, inExecution: updated_execution),
kse.eq(selector_value)
{
let result = sce.evaluate(execution: updated_execution)
//let result = sce.evaluate(execution: updated_execution)
let result = EvaluateExpression(sce, inExecution: updated_execution)
return result
}
}
@@ -210,12 +210,14 @@ public func binary_int_math_operator_checker(
extension BinaryOperatorExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution
let maybe_evaluated_left = self.left.evaluate(execution: updated_execution)
//let maybe_evaluated_left = self.left.evaluate(execution: updated_execution)
let maybe_evaluated_left = EvaluateExpression(self.left, inExecution: updated_execution)
guard case (.Ok(let evaluated_left), let updated_execution) = maybe_evaluated_left else {
return maybe_evaluated_left
}
let maybe_evaluated_right = self.right.evaluate(execution: updated_execution)
//let maybe_evaluated_right = self.right.evaluate(execution: updated_execution)
let maybe_evaluated_right = EvaluateExpression(self.right, inExecution: updated_execution)
guard case (.Ok(let evaluated_right), let updated_execution) = maybe_evaluated_right else {
return maybe_evaluated_right
}
@@ -231,12 +233,14 @@ extension BinaryOperatorExpression: EvaluatableExpression {
extension ArrayAccessExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution
let maybe_name = self.name.evaluate(execution: updated_execution)
//let maybe_name = self.name.evaluate(execution: updated_execution)
let maybe_name = EvaluateExpression(self.name, inExecution: updated_execution)
guard case (.Ok(let name), let updated_execution) = maybe_name else {
return maybe_name
}
let maybe_indexor = self.indexor.evaluate(execution: updated_execution)
//let maybe_indexor = self.indexor.evaluate(execution: updated_execution)
let maybe_indexor = EvaluateExpression(self.indexor, inExecution: updated_execution)
guard case (.Ok(let indexor), let updated_execution) = maybe_indexor else {
return maybe_indexor
}
@@ -265,7 +269,8 @@ extension ArrayAccessExpression: EvaluatableLValueExpression {
) -> Common.Result<(Common.VarValueScopes, P4Value)> {
let updated_execution = execution
let maybe_value = self.name.evaluate(execution: updated_execution)
//let maybe_value = self.name.evaluate(execution: updated_execution)
let maybe_value = EvaluateExpression(self.name, inExecution: updated_execution)
guard case (.Ok(let value), let updated_execution) = maybe_value else {
return .Error(
Error(withMessage: "\(self.name) cannot be evaluated: \(maybe_value.0.error()!)"))
@@ -275,7 +280,8 @@ extension ArrayAccessExpression: EvaluatableLValueExpression {
}
// Now, get the indexor!
let maybe_indexor_value = self.indexor.evaluate(execution: updated_execution)
//let maybe_indexor_value = self.indexor.evaluate(execution: updated_execution)
let maybe_indexor_value = EvaluateExpression(self.indexor, inExecution: updated_execution)
guard case (.Ok(let indexor_value), let updated_execution) = maybe_indexor_value else {
return Result.Error(
Error(withMessage: "\(self.indexor) cannot be evaluated: \(maybe_indexor_value.0.error()!)")
@@ -341,7 +347,8 @@ extension FieldAccessExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution
let maybe_struct = self.strct.evaluate(execution: updated_execution)
//let maybe_struct = self.strct.evaluate(execution: updated_execution)
let maybe_struct = EvaluateExpression(self.strct, inExecution: updated_execution)
guard case (.Ok(let strct), let updated_execution) = maybe_struct else {
return maybe_struct
}
@@ -375,7 +382,8 @@ extension FieldAccessExpression: EvaluatableLValueExpression {
let updated_execution = execution
// First, evaluate strct_id and make sure that it names a struct.
let maybe_value = self.strct.evaluate(execution: updated_execution)
//let maybe_value = self.strct.evaluate(execution: updated_execution)
let maybe_value = EvaluateExpression(self.strct, inExecution: updated_execution)
guard case (.Ok(let value), let updated_execution) = maybe_value else {
return .Error(
Error(withMessage: "\(self.strct) cannot be evaluated: \(maybe_value.0.error()!)"))
@@ -444,7 +452,8 @@ extension FieldAccessExpression: EvaluatableLValueExpression {
extension KeysetExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
return self.key.evaluate(execution: execution)
//return self.key.evaluate(execution: execution)
return EvaluateExpression(self.key, inExecution: execution)
}
public func type() -> P4Type {
+37 -33
View File
@@ -21,7 +21,8 @@ import P4Lang
extension ParserAssignmentStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
let updated_execution = execution
let result = self.value.evaluate(execution: updated_execution)
//let result = self.value.evaluate(execution: updated_execution)
let result = EvaluateExpression(self.value, inExecution: updated_execution)
guard case (.Ok(let value), let updated_execution) = result else {
return (ControlFlow.Error, execution.setError(error: result.0.error()!))
}
@@ -43,19 +44,23 @@ extension ParserStateDirectTransition: EvaluatableParserState {
) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope()
for statement in statements {
let (control_flow, next_program) = statement.evaluate(execution: program)
switch control_flow {
case .Next: program = next_program // Ok!
case .Error: return (reject, next_program)
default:
return (
reject,
next_program.setError(
error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)"))
)
}
let (control_flow, next_execution) = ExecuteStatement(statements, handleResult: { (control_flow, execution) in
return (control_flow, execution)
}, inExecution: program)
switch control_flow {
case .Next: program = next_execution
case .Error: return (reject, next_execution.exit_scope())
default:
return (
reject,
next_execution.exit_scope().setError(
error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)"))
)
}
let res = program.scopes.lookup(identifier: get_next_state())
if case .Ok(let value) = res {
@@ -79,7 +84,6 @@ extension ParserStateDirectTransition: EvaluatableParserState {
}
extension ParserStateNoTransition: EvaluatableParserState {
public func execute(
program: Common.ProgramExecution
) -> (any EvaluatableParserState, Common.ProgramExecution) {
@@ -96,28 +100,30 @@ extension ParserStateNoTransition: EvaluatableParserState {
}
extension ParserStateSelectTransition: EvaluatableParserState {
public func execute(
program: Common.ProgramExecution
) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope()
// First, evaluate the statements.
for statement in statements {
let (control_flow, next_program) = statement.evaluate(execution: program)
switch control_flow {
case .Next: program = next_program // Ok!
case .Error: return (reject, next_program)
default:
return (
reject,
next_program.setError(
error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)"))
)
}
let (control_flow, next_execution) = ExecuteStatement(statements, handleResult: { (control_flow, execution) in
return (control_flow, execution)
}, inExecution: program)
switch control_flow {
case .Next: program = next_execution
case .Error: return (reject, next_execution.exit_scope())
default:
return (
reject,
next_execution.exit_scope().setError(
error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)"))
)
}
switch self.selectExpression.evaluate(execution: program) {
//switch self.selectExpression.evaluate(execution: program) {
switch EvaluateExpression(self.selectExpression, inExecution: program) {
case (.Ok(let value), let program):
if value.type().dataType().eq(rhs: self) {
return (value.dataValue() as! EvaluatableParserState, program.exit_scope())
@@ -156,10 +162,8 @@ extension Parser: LibraryCallable {
withValue: P4Value(reject, P4Type.ReadOnly(reject.type())))
// Add initial values to the global scope
if let initial = execution.initial_values() {
for (name, value) in initial {
execution = execution.declare(identifier: name, withValue: value)
}
for (name, value) in execution.getGlobalValues() {
execution = execution.declare(identifier: name, withValue: value)
}
// First, add every state to the scope!
+19 -8
View File
@@ -29,22 +29,22 @@ public struct ParserRuntime: CustomStringConvertible {
self.initialValues = .none
}
init(parser: Parser, withInitialValues initial: VarValueScopes?) {
init(parser: Parser, withGlobalValues initial: VarValueScopes?) {
self.parser = parser
self.initialValues = initial
}
/// Create a parser runtime from a P4 program
public static func create(program: P4Lang.Program) -> Result<ParserRuntime> {
return ParserRuntime.create(program: program, withInitialValues: .none)
return ParserRuntime.create(program: program, withGlobalValues: .none)
}
public static func create(
program: P4Lang.Program, withInitialValues initial: VarValueScopes?
program: P4Lang.Program, withGlobalValues initial: VarValueScopes?
) -> Result<ParserRuntime> {
return switch program.starting_parser() {
case .Ok(let parser):
.Ok(P4Runtime.ParserRuntime(parser: parser, withInitialValues: initial))
.Ok(P4Runtime.ParserRuntime(parser: parser, withGlobalValues: initial))
case .Error(let error): .Error(error)
}
}
@@ -52,12 +52,10 @@ public struct ParserRuntime: CustomStringConvertible {
/// Run a P4 parser with no arguments
public func run() -> Result<(ParserState, ProgramExecution)> {
return self.run(withArguments: ArgumentList([]))
}
/// Run the P4 parser on a given packet
public func run(withArguments arguments: ArgumentList) -> Result<(ParserState, ProgramExecution)>
{
public func run(withArguments arguments: ArgumentList) -> Result<(ParserState, ProgramExecution)> {
let pe =
if let initial = initialValues {
ProgramExecution(withGlobalValues: initial)
@@ -65,6 +63,19 @@ public struct ParserRuntime: CustomStringConvertible {
ProgramExecution()
}
return self.run(withArguments: arguments, inExecution: pe)
}
/// Run the P4 parser on a given packet
public func run(withArguments arguments: ArgumentList, inExecution pe: ProgramExecution) -> Result<(ParserState, ProgramExecution)>
{
let pe = if let globals = initialValues {
pe.setGlobalValues(globals)
} else {
pe
}
let (end_state, execution) = parser.call(execution: pe, arguments: arguments)
if let error = execution.getError() {
return .Error(error)
+25 -21
View File
@@ -20,29 +20,31 @@ import P4Lang
extension BlockStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
var execution = execution
for s in self.statements {
let (control_flow, next_execution) = s.evaluate(execution: execution)
switch control_flow {
case ControlFlow.Return(let value): return (ControlFlow.Return(value), next_execution)
case ControlFlow.Next: execution = next_execution
case ControlFlow.Error: return (ControlFlow.Error, next_execution)
default:
return (
ControlFlow.Next,
next_execution.setError(
error: Error(withMessage: "Invalid control flow \(control_flow) in block statement"))
)
}
}
return (ControlFlow.Next, execution)
print("I am going to evaluate a block statement?")
return ExecuteStatement(
self.statements,
handleResult: { (cf, execution) in
switch cf {
case ControlFlow.Return(let value): return (ControlFlow.Return(value), execution)
case ControlFlow.Next: return (cf, execution)
case ControlFlow.Error: return (ControlFlow.Error, execution)
default:
return (
ControlFlow.Error,
execution.setError(
error: Error(withMessage: "Invalid control flow \(cf) in block statement"))
)
}
},
inExecution: execution)
}
}
extension VariableDeclarationStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
guard
case (.Ok(let initial_value), let execution) = self.initializer.evaluate(execution: execution)
//case (.Ok(let initial_value), let execution) = self.initializer.evaluate(execution: execution)
case (.Ok(let initial_value), let execution) = EvaluateExpression(self.initializer, inExecution: execution)
else {
return (
ControlFlow.Error,
@@ -58,8 +60,8 @@ extension VariableDeclarationStatement: EvaluatableStatement {
extension ConditionalStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
guard
case (.Ok(let evaluated_condition), let execution) = self.condition.evaluate(
execution: execution)
//case (.Ok(let evaluated_condition), let execution) = self.condition.evaluate(execution: execution)
case (.Ok(let evaluated_condition), let execution) = EvaluateExpression(self.condition, inExecution: execution)
else {
return (
ControlFlow.Error,
@@ -107,7 +109,8 @@ extension ExpressionStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
// Evaluate, there might be side effects!
return switch self.expression.evaluate(execution: execution) {
//return switch self.expression.evaluate(execution: execution) {
return switch EvaluateExpression(self.expression, inExecution: execution) {
case (.Ok(_), let updated_context): (ControlFlow.Next, updated_context)
case (.Error(let e), let updated_context):
(ControlFlow.Next, updated_context.setError(error: e))
@@ -117,7 +120,8 @@ extension ExpressionStatement: EvaluatableStatement {
extension ReturnStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
return switch self.value.evaluate(execution: execution) {
//return switch self.value.evaluate(execution: execution) {
return switch EvaluateExpression(self.value, inExecution: execution) {
case (.Ok(let v), let execution): (ControlFlow.Return(v), execution)
case (.Error(let e), let execution): (ControlFlow.Error, execution.setError(error: e))
}