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 // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // 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 { open class ProgramExecution: CustomStringConvertible {
public var scopes: VarValueScopes = VarValueScopes() public var scopes: VarValueScopes = VarValueScopes()
let initialValues: VarValueScopes? var globalValues: VarValueScopes?
var error: Error? var error: Error?
var debug: DebugLevel = DebugLevel.Error var debug: DebugLevel = DebugLevel.Error
var statement_interloper: StatementInterloper?
var expression_interloper: ExpressionInterloper?
init(copy: ProgramExecution) { init(copy: ProgramExecution) {
self.scopes = copy.scopes self.scopes = copy.scopes
self.initialValues = copy.initialValues self.globalValues = copy.globalValues
self.error = copy.error self.error = copy.error
self.debug = copy.debug self.debug = copy.debug
self.statement_interloper = copy.statement_interloper
self.expression_interloper = copy.expression_interloper
} }
public init() { public init() {
initialValues = .none globalValues = .none
} }
public init(withGlobalValues values: VarValueScopes) { public init(withGlobalValues values: VarValueScopes) {
initialValues = values globalValues = values
} }
open var description: String { open var description: String {
@@ -64,6 +71,26 @@ open class ProgramExecution: CustomStringConvertible {
return pe 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 { open func isDone() -> Bool {
return false return false
} }
@@ -101,9 +128,17 @@ open class ProgramExecution: CustomStringConvertible {
return new_pe return new_pe
} }
public func initial_values() -> VarValueScopes? { public func getGlobalValues() -> VarValueScopes {
return self.initialValues 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. /// 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 { public var description: String {
switch self { switch self {
case Result.Error(let e): case Result.Error(let e):
return e.msg return e.msg
case Result.Ok(let o): case Result.Ok(let o):
return "\(o)" return "Ok: \(o)"
}
}
}
extension Result: CustomStringConvertible {
public var description: String {
switch self {
case Result.Error(let e):
return e.msg
case Result.Ok(_):
return "Ok"
} }
} }
} }
+1 -1
View File
@@ -90,7 +90,7 @@ public struct ParameterList: CustomStringConvertible, Equatable {
public struct ArgumentList { public struct ArgumentList {
public let arguments: [Argument] public let arguments: [Argument]
public init(_ arguments: [Argument]) { public init(_ arguments: [Argument] = []) {
self.arguments = arguments self.arguments = arguments
} }
+62 -1
View File
@@ -32,7 +32,8 @@ public func Call<T>(
for (parameter, argument) in zip(params.parameters, args.arguments) { for (parameter, argument) in zip(params.parameters, args.arguments) {
let arg_idx = argument.index let arg_idx = argument.index
let arg_value = argument.argument 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 { guard case (.Ok(let argument_value), let updated_execution) = maybe_argument_value else {
return ( return (
.Error(Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)")), .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)) 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 { extension SelectExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) { 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): case (.Ok(let selector_value), let updated_execution):
for sce in self.case_expressions { for sce in self.case_expressions {
if case (.Ok(let kse), let updated_execution) = sce.key.evaluate( if case (.Ok(let kse), let updated_execution) = EvaluateExpression(sce.key, inExecution: updated_execution),
execution: updated_execution),
kse.eq(selector_value) 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 return result
} }
} }
@@ -210,12 +210,14 @@ public func binary_int_math_operator_checker(
extension BinaryOperatorExpression: EvaluatableExpression { extension BinaryOperatorExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) { public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution 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 { guard case (.Ok(let evaluated_left), let updated_execution) = maybe_evaluated_left else {
return maybe_evaluated_left 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 { guard case (.Ok(let evaluated_right), let updated_execution) = maybe_evaluated_right else {
return maybe_evaluated_right return maybe_evaluated_right
} }
@@ -231,12 +233,14 @@ extension BinaryOperatorExpression: EvaluatableExpression {
extension ArrayAccessExpression: EvaluatableExpression { extension ArrayAccessExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) { public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution 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 { guard case (.Ok(let name), let updated_execution) = maybe_name else {
return maybe_name 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 { guard case (.Ok(let indexor), let updated_execution) = maybe_indexor else {
return maybe_indexor return maybe_indexor
} }
@@ -265,7 +269,8 @@ extension ArrayAccessExpression: EvaluatableLValueExpression {
) -> Common.Result<(Common.VarValueScopes, P4Value)> { ) -> Common.Result<(Common.VarValueScopes, P4Value)> {
let updated_execution = execution 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 { guard case (.Ok(let value), let updated_execution) = maybe_value else {
return .Error( return .Error(
Error(withMessage: "\(self.name) cannot be evaluated: \(maybe_value.0.error()!)")) Error(withMessage: "\(self.name) cannot be evaluated: \(maybe_value.0.error()!)"))
@@ -275,7 +280,8 @@ extension ArrayAccessExpression: EvaluatableLValueExpression {
} }
// Now, get the indexor! // 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 { guard case (.Ok(let indexor_value), let updated_execution) = maybe_indexor_value else {
return Result.Error( return Result.Error(
Error(withMessage: "\(self.indexor) cannot be evaluated: \(maybe_indexor_value.0.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) { public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution 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 { guard case (.Ok(let strct), let updated_execution) = maybe_struct else {
return maybe_struct return maybe_struct
} }
@@ -375,7 +382,8 @@ extension FieldAccessExpression: EvaluatableLValueExpression {
let updated_execution = execution let updated_execution = execution
// First, evaluate strct_id and make sure that it names a struct. // 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 { guard case (.Ok(let value), let updated_execution) = maybe_value else {
return .Error( return .Error(
Error(withMessage: "\(self.strct) cannot be evaluated: \(maybe_value.0.error()!)")) Error(withMessage: "\(self.strct) cannot be evaluated: \(maybe_value.0.error()!)"))
@@ -444,7 +452,8 @@ extension FieldAccessExpression: EvaluatableLValueExpression {
extension KeysetExpression: EvaluatableExpression { extension KeysetExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) { 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 { public func type() -> P4Type {
+37 -33
View File
@@ -21,7 +21,8 @@ import P4Lang
extension ParserAssignmentStatement: EvaluatableStatement { extension ParserAssignmentStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
let updated_execution = execution 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 { guard case (.Ok(let value), let updated_execution) = result else {
return (ControlFlow.Error, execution.setError(error: result.0.error()!)) return (ControlFlow.Error, execution.setError(error: result.0.error()!))
} }
@@ -43,19 +44,23 @@ extension ParserStateDirectTransition: EvaluatableParserState {
) -> (any EvaluatableParserState, Common.ProgramExecution) { ) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope() var program = program.enter_scope()
for statement in statements {
let (control_flow, next_program) = statement.evaluate(execution: program) let (control_flow, next_execution) = ExecuteStatement(statements, handleResult: { (control_flow, execution) in
switch control_flow { return (control_flow, execution)
case .Next: program = next_program // Ok! }, inExecution: program)
case .Error: return (reject, next_program)
default:
return ( switch control_flow {
reject, case .Next: program = next_execution
next_program.setError( case .Error: return (reject, next_execution.exit_scope())
error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)")) 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()) let res = program.scopes.lookup(identifier: get_next_state())
if case .Ok(let value) = res { if case .Ok(let value) = res {
@@ -79,7 +84,6 @@ extension ParserStateDirectTransition: EvaluatableParserState {
} }
extension ParserStateNoTransition: EvaluatableParserState { extension ParserStateNoTransition: EvaluatableParserState {
public func execute( public func execute(
program: Common.ProgramExecution program: Common.ProgramExecution
) -> (any EvaluatableParserState, Common.ProgramExecution) { ) -> (any EvaluatableParserState, Common.ProgramExecution) {
@@ -96,28 +100,30 @@ extension ParserStateNoTransition: EvaluatableParserState {
} }
extension ParserStateSelectTransition: EvaluatableParserState { extension ParserStateSelectTransition: EvaluatableParserState {
public func execute( public func execute(
program: Common.ProgramExecution program: Common.ProgramExecution
) -> (any EvaluatableParserState, Common.ProgramExecution) { ) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope() var program = program.enter_scope()
// First, evaluate the statements. let (control_flow, next_execution) = ExecuteStatement(statements, handleResult: { (control_flow, execution) in
for statement in statements { return (control_flow, execution)
let (control_flow, next_program) = statement.evaluate(execution: program) }, inExecution: program)
switch control_flow {
case .Next: program = next_program // Ok!
case .Error: return (reject, next_program) switch control_flow {
default: case .Next: program = next_execution
return ( case .Error: return (reject, next_execution.exit_scope())
reject, default:
next_program.setError( return (
error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)")) 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): case (.Ok(let value), let program):
if value.type().dataType().eq(rhs: self) { if value.type().dataType().eq(rhs: self) {
return (value.dataValue() as! EvaluatableParserState, program.exit_scope()) return (value.dataValue() as! EvaluatableParserState, program.exit_scope())
@@ -156,10 +162,8 @@ extension Parser: LibraryCallable {
withValue: P4Value(reject, P4Type.ReadOnly(reject.type()))) withValue: P4Value(reject, P4Type.ReadOnly(reject.type())))
// Add initial values to the global scope // Add initial values to the global scope
if let initial = execution.initial_values() { for (name, value) in execution.getGlobalValues() {
for (name, value) in initial { execution = execution.declare(identifier: name, withValue: value)
execution = execution.declare(identifier: name, withValue: value)
}
} }
// First, add every state to the scope! // First, add every state to the scope!
+19 -8
View File
@@ -29,22 +29,22 @@ public struct ParserRuntime: CustomStringConvertible {
self.initialValues = .none self.initialValues = .none
} }
init(parser: Parser, withInitialValues initial: VarValueScopes?) { init(parser: Parser, withGlobalValues initial: VarValueScopes?) {
self.parser = parser self.parser = parser
self.initialValues = initial self.initialValues = initial
} }
/// Create a parser runtime from a P4 program /// Create a parser runtime from a P4 program
public static func create(program: P4Lang.Program) -> Result<ParserRuntime> { 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( public static func create(
program: P4Lang.Program, withInitialValues initial: VarValueScopes? program: P4Lang.Program, withGlobalValues initial: VarValueScopes?
) -> Result<ParserRuntime> { ) -> Result<ParserRuntime> {
return switch program.starting_parser() { return switch program.starting_parser() {
case .Ok(let 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) case .Error(let error): .Error(error)
} }
} }
@@ -52,12 +52,10 @@ public struct ParserRuntime: CustomStringConvertible {
/// Run a P4 parser with no arguments /// Run a P4 parser with no arguments
public func run() -> Result<(ParserState, ProgramExecution)> { public func run() -> Result<(ParserState, ProgramExecution)> {
return self.run(withArguments: ArgumentList([])) 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 = let pe =
if let initial = initialValues { if let initial = initialValues {
ProgramExecution(withGlobalValues: initial) ProgramExecution(withGlobalValues: initial)
@@ -65,6 +63,19 @@ public struct ParserRuntime: CustomStringConvertible {
ProgramExecution() 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) let (end_state, execution) = parser.call(execution: pe, arguments: arguments)
if let error = execution.getError() { if let error = execution.getError() {
return .Error(error) return .Error(error)
+25 -21
View File
@@ -20,29 +20,31 @@ import P4Lang
extension BlockStatement: EvaluatableStatement { extension BlockStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
var execution = execution print("I am going to evaluate a block statement?")
for s in self.statements { return ExecuteStatement(
let (control_flow, next_execution) = s.evaluate(execution: execution) self.statements,
switch control_flow { handleResult: { (cf, execution) in
case ControlFlow.Return(let value): return (ControlFlow.Return(value), next_execution) switch cf {
case ControlFlow.Next: execution = next_execution case ControlFlow.Return(let value): return (ControlFlow.Return(value), execution)
case ControlFlow.Error: return (ControlFlow.Error, next_execution) case ControlFlow.Next: return (cf, execution)
default: case ControlFlow.Error: return (ControlFlow.Error, execution)
return ( default:
ControlFlow.Next, return (
next_execution.setError( ControlFlow.Error,
error: Error(withMessage: "Invalid control flow \(control_flow) in block statement")) execution.setError(
) error: Error(withMessage: "Invalid control flow \(cf) in block statement"))
} )
} }
return (ControlFlow.Next, execution) },
inExecution: execution)
} }
} }
extension VariableDeclarationStatement: EvaluatableStatement { extension VariableDeclarationStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
guard 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 { else {
return ( return (
ControlFlow.Error, ControlFlow.Error,
@@ -58,8 +60,8 @@ extension VariableDeclarationStatement: EvaluatableStatement {
extension ConditionalStatement: EvaluatableStatement { extension ConditionalStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
guard guard
case (.Ok(let evaluated_condition), let execution) = self.condition.evaluate( //case (.Ok(let evaluated_condition), let execution) = self.condition.evaluate(execution: execution)
execution: execution) case (.Ok(let evaluated_condition), let execution) = EvaluateExpression(self.condition, inExecution: execution)
else { else {
return ( return (
ControlFlow.Error, ControlFlow.Error,
@@ -107,7 +109,8 @@ extension ExpressionStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
// Evaluate, there might be side effects! // 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 (.Ok(_), let updated_context): (ControlFlow.Next, updated_context)
case (.Error(let e), let updated_context): case (.Error(let e), let updated_context):
(ControlFlow.Next, updated_context.setError(error: e)) (ControlFlow.Next, updated_context.setError(error: e))
@@ -117,7 +120,8 @@ extension ExpressionStatement: EvaluatableStatement {
extension ReturnStatement: EvaluatableStatement { extension ReturnStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) { 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 (.Ok(let v), let execution): (ControlFlow.Return(v), execution)
case (.Error(let e), let execution): (ControlFlow.Error, execution.setError(error: e)) case (.Error(let e), let execution): (ControlFlow.Error, execution.setError(error: e))
} }
+7 -7
View File
@@ -49,7 +49,7 @@ import TreeSitterP4
]))) ])))
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
@@ -101,7 +101,7 @@ import TreeSitterP4
]))) ])))
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject) #expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
@@ -128,7 +128,7 @@ import TreeSitterP4
]))) ])))
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
@@ -155,7 +155,7 @@ import TreeSitterP4
]))) ])))
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
@@ -187,7 +187,7 @@ import TreeSitterP4
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
@@ -216,7 +216,7 @@ import TreeSitterP4
]))) ])))
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
@@ -249,7 +249,7 @@ import TreeSitterP4
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
+114
View File
@@ -0,0 +1,114 @@
// 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 Foundation
import Macros
import P4Runtime
import P4Lang
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_statement_interloper() async throws {
let simple_parser_declaration = """
parser main_parser() {
state starts {
bool where_to = false;
int va = 5;
transition accept;
}
state start {
bool where_to = true;
where_to = true;
transition select (where_to) {
false: reject;
true: starts;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
var statements_executed: [String] = Array()
let pe = ProgramExecution().setStatementInterloper({ (statement, cf, execution) in
statements_executed.append("\(statement)")
})
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: ArgumentList(), inExecution: pe))
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
#expect(statements_executed[0].hasPrefix("VariableDeclarationStatement"))
#expect(statements_executed[1].hasPrefix("ParserAssignmentStatement"))
// Moved into starts
#expect(statements_executed[2].hasPrefix("VariableDeclarationStatement"))
#expect(statements_executed[3].hasPrefix("VariableDeclarationStatement"))
}
@Test func test_expression_interloper() async throws {
let simple_parser_declaration = """
parser main_parser() {
state starts {
bool where_to = false;
int va = 5;
transition accept;
}
state start {
bool where_to = true;
where_to = true;
transition select (where_to) {
false: reject;
true: starts;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
var expressions_evaluated: [String] = Array()
let pe = ProgramExecution().setExpressionInterloper() { expression, result, execution in
print("Expression: \(expression)")
expressions_evaluated.append("\(expression)")
}
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: ArgumentList(), inExecution: pe))
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
#expect(expressions_evaluated[0].hasPrefix("Value: true of Boolean"))
#expect(expressions_evaluated[1].hasPrefix("Value: true of Boolean"))
#expect(expressions_evaluated[2].hasPrefix("where_to"))
#expect(expressions_evaluated[3].hasPrefix("Value: false of Boolean"))
#expect(expressions_evaluated[4].hasPrefix("KeysetExpression"))
#expect(expressions_evaluated[5].hasPrefix("Value: true of Boolean"))
#expect(expressions_evaluated[6].hasPrefix("KeysetExpression"))
#expect(expressions_evaluated[7].hasPrefix("SelectCaseExpression"))
#expect(expressions_evaluated[8].hasPrefix("SelectExpression"))
// Moved into starts
#expect(expressions_evaluated[9].hasPrefix("Value: false of Boolean"))
#expect(expressions_evaluated[10].hasPrefix("Value: 5 of Int"))
}
+8 -8
View File
@@ -57,7 +57,7 @@ import TreeSitterP4
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
} }
@@ -151,7 +151,7 @@ import TreeSitterP4
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject) #expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
} }
@@ -186,7 +186,7 @@ import TreeSitterP4
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
} }
@@ -220,7 +220,7 @@ import TreeSitterP4
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject) #expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
} }
@@ -266,7 +266,7 @@ import TreeSitterP4
]))) ])))
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
} }
@@ -302,7 +302,7 @@ import TreeSitterP4
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
} }
@@ -375,7 +375,7 @@ import TreeSitterP4
]))) ])))
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
} }
@@ -423,7 +423,7 @@ import TreeSitterP4
]))) ])))
let program = try #UseOkResult( let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations)) Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withInitialValues: test_values)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept) #expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
} }
+7 -2
View File
@@ -35,10 +35,15 @@ struct StringConvertible: CustomStringConvertible {
@Test func test_result_type_description_not_convertible() async throws { @Test func test_result_type_description_not_convertible() async throws {
let result: Result<NotStringConvertible> = Result.Ok(NotStringConvertible()); let result: Result<NotStringConvertible> = Result.Ok(NotStringConvertible());
#expect(result.description == "Ok") #expect("\(result)" == "Ok(Tests.NotStringConvertible())")
} }
@Test func test_result_type_description_convertible() async throws { @Test func test_result_type_description_convertible() async throws {
let result: Result<StringConvertible> = Result.Ok(StringConvertible()); let result: Result<StringConvertible> = Result.Ok(StringConvertible());
#expect(result.description == "CONVERTED") #expect("\(result)" == "Ok: CONVERTED")
}
@Test func test_result_type_p4value_convertible() async throws {
let result = Result.Ok(P4Value(P4IntValue(withValue: 5)))
#expect("\(result)" == "Ok: Value: 5 of Int type of type Int")
} }
+1 -2
View File
@@ -111,8 +111,7 @@ export default grammar({
// Parser statements // Parser statements
parserStatements: $ => repeat1($.parserStatement), parserStatements: $ => repeat1($.parserStatement),
parserStatement: $ => choice($.conditionalStatement, $.parserBlockStatement, $.expressionStatement, $.assignmentStatement, $.variableDeclaration), // Limited, so far. parserStatement: $ => choice($.conditionalStatement, $.expressionStatement, $.assignmentStatement, $.variableDeclaration), // Limited, so far.
parserBlockStatement: $ => seq(optional($.annotations), '{', $.parserStatements, '}'),
parserTransitionStatement: $ => seq($.transition, $.transitionSelectionExpression, $._semicolon), parserTransitionStatement: $ => seq($.transition, $.transitionSelectionExpression, $._semicolon),
// Expressions // Expressions