From 050547b48578e44652a231c0c81576a54fa8aeb4 Mon Sep 17 00:00:00 2001 From: Andrei Tumbar Date: Wed, 28 Jan 2026 14:47:47 -0800 Subject: [PATCH 1/5] State Machine/State Include Specifiers --- compiler/lib/src/main/scala/ast/Ast.scala | 2 + .../src/main/scala/ast/AstTransformer.scala | 124 ++++++++++++++++++ .../lib/src/main/scala/ast/AstVisitor.scala | 2 + .../lib/src/main/scala/syntax/Parser.scala | 8 +- .../scala/transform/ResolveSpecInclude.scala | 28 ++++ .../State-Machine-Definitions.adoc | 1 + 6 files changed, 162 insertions(+), 3 deletions(-) diff --git a/compiler/lib/src/main/scala/ast/Ast.scala b/compiler/lib/src/main/scala/ast/Ast.scala index 79eba76ee..e932409ac 100644 --- a/compiler/lib/src/main/scala/ast/Ast.scala +++ b/compiler/lib/src/main/scala/ast/Ast.scala @@ -191,6 +191,7 @@ object Ast { final case class DefSignal(node: AstNode[Ast.DefSignal]) extends Node final case class DefState(node: AstNode[Ast.DefState]) extends Node final case class SpecInitialTransition(node: AstNode[Ast.SpecInitialTransition]) extends Node + final case class SpecInclude(node: AstNode[Ast.SpecInclude]) extends Node } /** Action definition */ @@ -241,6 +242,7 @@ object Ast { final case class SpecStateEntry(node: AstNode[Ast.SpecStateEntry]) extends Node final case class SpecStateExit(node: AstNode[Ast.SpecStateExit]) extends Node final case class SpecStateTransition(node: AstNode[Ast.SpecStateTransition]) extends Node + final case class SpecInclude(node: AstNode[Ast.SpecInclude]) extends Node } /** Initial state specifier */ diff --git a/compiler/lib/src/main/scala/ast/AstTransformer.scala b/compiler/lib/src/main/scala/ast/AstTransformer.scala index 53ef45d52..7de3ba2ed 100644 --- a/compiler/lib/src/main/scala/ast/AstTransformer.scala +++ b/compiler/lib/src/main/scala/ast/AstTransformer.scala @@ -68,6 +68,60 @@ trait AstTransformer { node: Ast.Annotated[AstNode[Ast.DefStateMachine]] ): ResultAnnotatedNode[Ast.DefStateMachine] = Right(default(in), node) + def defActionAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.DefAction]] + ): ResultAnnotatedNode[Ast.DefAction] = + Right(default(in), node) + + def defChoiceAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.DefChoice]] + ): ResultAnnotatedNode[Ast.DefChoice] = + Right(default(in), node) + + def defGuardAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.DefGuard]] + ): ResultAnnotatedNode[Ast.DefGuard] = + Right(default(in), node) + + def defSignalAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.DefSignal]] + ): ResultAnnotatedNode[Ast.DefSignal] = + Right(default(in), node) + + def defStateAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.DefState]] + ): ResultAnnotatedNode[Ast.DefState] = + Right(default(in), node) + + def specInitialTransitionAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.SpecInitialTransition]] + ): ResultAnnotatedNode[Ast.SpecInitialTransition] = + Right(default(in), node) + + def specStateEntryAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.SpecStateEntry]] + ): ResultAnnotatedNode[Ast.SpecStateEntry] = + Right(default(in), node) + + def specStateExitAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.SpecStateExit]] + ): ResultAnnotatedNode[Ast.SpecStateExit] = + Right(default(in), node) + + def specStateTransitionAnnotatedNode( + in: In, + node: Ast.Annotated[AstNode[Ast.SpecStateTransition]] + ): ResultAnnotatedNode[Ast.SpecStateTransition] = + Right(default(in), node) + def defStructAnnotatedNode( in: In, node: Ast.Annotated[AstNode[Ast.DefStruct]] @@ -315,6 +369,76 @@ trait AstTransformer { case e : Ast.ExprUnop => exprUnopNode(in, node, e) } + final def matchStateMember(in: In, member: Ast.StateMember): Result[Ast.StateMember] = { + def transform[T]( + result: ResultAnnotatedNode[T], + f: AstNode[T] => Ast.StateMember.Node + ) = { + for { pair <- result } yield { + val (out, (pre, node, post)) = pair + (out, Ast.StateMember(pre, f(node), post)) + } + } + val (pre, node, post) = member.node + node match { + case Ast.StateMember.DefChoice(node1) => + transform(defChoiceAnnotatedNode(in, (pre, node1, post)), Ast.StateMember.DefChoice(_)) + case Ast.StateMember.DefState(node1) => + transform(defStateAnnotatedNode(in, (pre, node1, post)), Ast.StateMember.DefState(_)) + case Ast.StateMember.SpecInitialTransition(node1) => + transform(specInitialTransitionAnnotatedNode(in, (pre, node1, post)), Ast.StateMember.SpecInitialTransition(_)) + case Ast.StateMember.SpecStateEntry(node1) => + transform(specStateEntryAnnotatedNode(in, (pre, node1, post)), Ast.StateMember.SpecStateEntry(_)) + case Ast.StateMember.SpecStateExit(node1) => + transform(specStateExitAnnotatedNode(in, (pre, node1, post)), Ast.StateMember.SpecStateExit(_)) + case Ast.StateMember.SpecStateTransition(node1) => + transform(specStateTransitionAnnotatedNode(in, (pre, node1, post)), Ast.StateMember.SpecStateTransition(_)) + case Ast.StateMember.SpecInclude(node1) => + transform(specIncludeAnnotatedNode(in, (pre, node1, post)), Ast.StateMember.SpecInclude(_)) + } + } + + final def matchStateMachineMember(in: In, member: Ast.StateMachineMember): Result[Ast.StateMachineMember] = { + def transform[T]( + result: ResultAnnotatedNode[T], + f: AstNode[T] => Ast.StateMachineMember.Node + ) = { + for { pair <- result } yield { + val (out, (pre, node, post)) = pair + (out, Ast.StateMachineMember(pre, f(node), post)) + } + } + val (pre, node, post) = member.node + node match { + case Ast.StateMachineMember.DefAbsType(node1) => + transform(defAbsTypeAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefAbsType(_)) + case Ast.StateMachineMember.DefAliasType(node1) => + transform(defAliasTypeAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefAliasType(_)) + case Ast.StateMachineMember.DefArray(node1) => + transform(defArrayAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefArray(_)) + case Ast.StateMachineMember.DefConstant(node1) => + transform(defConstantAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefConstant(_)) + case Ast.StateMachineMember.DefEnum(node1) => + transform(defEnumAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefEnum(_)) + case Ast.StateMachineMember.DefStruct(node1) => + transform(defStructAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefStruct(_)) + case Ast.StateMachineMember.DefAction(node1) => + transform(defActionAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefAction(_)) + case Ast.StateMachineMember.DefChoice(node1) => + transform(defChoiceAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefChoice(_)) + case Ast.StateMachineMember.DefGuard(node1) => + transform(defGuardAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefGuard(_)) + case Ast.StateMachineMember.DefSignal(node1) => + transform(defSignalAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefSignal(_)) + case Ast.StateMachineMember.DefState(node1) => + transform(defStateAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.DefState(_)) + case Ast.StateMachineMember.SpecInitialTransition(node1) => + transform(specInitialTransitionAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.SpecInitialTransition(_)) + case Ast.StateMachineMember.SpecInclude(node1) => + transform(specIncludeAnnotatedNode(in, (pre, node1, post)), Ast.StateMachineMember.SpecInclude(_)) + } + } + final def matchModuleMember(in: In, member: Ast.ModuleMember): Result[Ast.ModuleMember] = { def transform[T]( result: ResultAnnotatedNode[T], diff --git a/compiler/lib/src/main/scala/ast/AstVisitor.scala b/compiler/lib/src/main/scala/ast/AstVisitor.scala index 4b3d31ae7..ecf292449 100644 --- a/compiler/lib/src/main/scala/ast/AstVisitor.scala +++ b/compiler/lib/src/main/scala/ast/AstVisitor.scala @@ -227,6 +227,7 @@ trait AstVisitor { case Ast.StateMachineMember.DefState(node1) => defStateAnnotatedNode(in, (pre, node1, post)) case Ast.StateMachineMember.DefStruct(node1) => defStructAnnotatedNode(in, (pre, node1, post)) case Ast.StateMachineMember.SpecInitialTransition(node1) => specInitialTransitionAnnotatedNode(in, (pre, node1, post)) + case Ast.StateMachineMember.SpecInclude(node1) => specIncludeAnnotatedNode(in, (pre, node1, post)) } } @@ -236,6 +237,7 @@ trait AstVisitor { case Ast.StateMember.DefChoice(node1) => defChoiceAnnotatedNode(in, (pre, node1, post)) case Ast.StateMember.DefState(node1) => defStateAnnotatedNode(in, (pre, node1, post)) case Ast.StateMember.SpecInitialTransition(node1) => specInitialTransitionAnnotatedNode(in, (pre, node1, post)) + case Ast.StateMember.SpecInclude(node1) => specIncludeAnnotatedNode(in, (pre, node1, post)) case Ast.StateMember.SpecStateEntry(node1) => specStateEntryAnnotatedNode(in, (pre, node1, post)) case Ast.StateMember.SpecStateExit(node1) => specStateExitAnnotatedNode(in, (pre, node1, post)) case Ast.StateMember.SpecStateTransition(node1) => specStateTransitionAnnotatedNode(in, (pre, node1, post)) diff --git a/compiler/lib/src/main/scala/syntax/Parser.scala b/compiler/lib/src/main/scala/syntax/Parser.scala index e79f5d016..9d60d5c1e 100644 --- a/compiler/lib/src/main/scala/syntax/Parser.scala +++ b/compiler/lib/src/main/scala/syntax/Parser.scala @@ -888,11 +888,12 @@ object Parser extends Parsers { node(defSignal) ^^ (n => Ast.StateMachineMember.DefSignal(n)) | node(defState) ^^ (n => Ast.StateMachineMember.DefState(n)) | node(defStruct) ^^ (n => Ast.StateMachineMember.DefStruct(n)) | + node(specInclude) ^^ (n => Ast.StateMachineMember.SpecInclude(n)) | node(specInitialTransition) ^^ (n => Ast.StateMachineMember.SpecInitialTransition(n)) | failure("state machine member expected") } - private def stateMachineMembers: Parser[List[Ast.StateMachineMember]] = + def stateMachineMembers: Parser[List[Ast.StateMachineMember]] = annotatedElementSequence( stateMachineMemberNode, semi, @@ -900,18 +901,19 @@ object Parser extends Parsers { ) private def stateMemberNode: Parser[Ast.StateMember.Node] = { - node(defChoice) ^^ (n => Ast.StateMember.DefChoice(n)) | + node(defChoice) ^^ (n => Ast.StateMember.DefChoice(n)) | node(defState) ^^ (n => Ast.StateMember.DefState(n)) | node(specInitialTransition) ^^ (n => Ast.StateMember.SpecInitialTransition(n)) | node(specStateEntry) ^^ (n => Ast.StateMember.SpecStateEntry(n)) | node(specStateExit) ^^ (n => Ast.StateMember.SpecStateExit(n)) | + node(specInclude) ^^ (n => Ast.StateMember.SpecInclude(n)) | node(specStateTransition) ^^ (n => Ast.StateMember.SpecStateTransition(n)) | failure("state member expected") } - private def stateMembers: Parser[List[Ast.StateMember]] = + def stateMembers: Parser[List[Ast.StateMember]] = annotatedElementSequence(stateMemberNode, semi, Ast.StateMember(_)) def structTypeMember: Parser[Ast.StructTypeMember] = { diff --git a/compiler/lib/src/main/scala/transform/ResolveSpecInclude.scala b/compiler/lib/src/main/scala/transform/ResolveSpecInclude.scala index 35c96202d..d53da0474 100644 --- a/compiler/lib/src/main/scala/transform/ResolveSpecInclude.scala +++ b/compiler/lib/src/main/scala/transform/ResolveSpecInclude.scala @@ -207,6 +207,34 @@ object ResolveSpecInclude extends AstStateTransformer { } } + private def stateMember(a: Analysis, member: Ast.StateMember): Result[List[Ast.StateMember]] = { + val (_, node, _) = member.node + node match { + case Ast.StateMember.SpecInclude(node1) => resolveSpecInclude( + a, + node1, + Parser.stateMembers, + stateMember + ) + case _ => for { result <- matchStateMember(a, member) } + yield (result._1, List(result._2)) + } + } + + private def stateMachineMember(a: Analysis, member: Ast.StateMachineMember): Result[List[Ast.StateMachineMember]] = { + val (_, node, _) = member.node + node match { + case Ast.StateMachineMember.SpecInclude(node1) => resolveSpecInclude( + a, + node1, + Parser.stateMachineMembers, + stateMachineMember + ) + case _ => for { result <- matchStateMachineMember(a, member) } + yield (result._1, List(result._2)) + } + } + private def tuMember(a: Analysis, tum: Ast.TUMember) = moduleMember(a, tum) } diff --git a/docs/spec/Definitions/State-Machine-Definitions.adoc b/docs/spec/Definitions/State-Machine-Definitions.adoc index e209e53de..3a91323ae 100644 --- a/docs/spec/Definitions/State-Machine-Definitions.adoc +++ b/docs/spec/Definitions/State-Machine-Definitions.adoc @@ -25,6 +25,7 @@ and the terminating punctuation is a semicolon. A state machine member is one of the following: * A <> +* An <> * A <> * A <> * A <> From 052a095f7fe57176e0f0d0063d0855236532e185 Mon Sep 17 00:00:00 2001 From: Andrei Tumbar Date: Wed, 28 Jan 2026 17:04:34 -0800 Subject: [PATCH 2/5] Add tests and fix code --- .../scala/transform/ResolveSpecInclude.scala | 35 ++++++++++++ .../fpp-syntax/test/include-state-machine.fpp | 12 ++++ .../test/include-state-machine.ref.txt | 56 +++++++++++++++++++ compiler/tools/fpp-syntax/test/run | 5 ++ .../tools/fpp-syntax/test/subdir/state.fppi | 10 ++++ compiler/tools/fpp-syntax/test/update-ref | 1 + 6 files changed, 119 insertions(+) create mode 100644 compiler/tools/fpp-syntax/test/include-state-machine.fpp create mode 100644 compiler/tools/fpp-syntax/test/include-state-machine.ref.txt create mode 100644 compiler/tools/fpp-syntax/test/subdir/state.fppi diff --git a/compiler/lib/src/main/scala/transform/ResolveSpecInclude.scala b/compiler/lib/src/main/scala/transform/ResolveSpecInclude.scala index d53da0474..11f23446d 100644 --- a/compiler/lib/src/main/scala/transform/ResolveSpecInclude.scala +++ b/compiler/lib/src/main/scala/transform/ResolveSpecInclude.scala @@ -42,6 +42,41 @@ object ResolveSpecInclude extends AstStateTransformer { } } + override def defStateMachineAnnotatedNode( + a: Analysis, + node: Ast.Annotated[AstNode[Ast.DefStateMachine]] + ) = { + val (pre, node1, post) = node + val Ast.DefStateMachine(name, members) = node1.data + members match { + case None => Right((a, node)) + case Some(members) => { + for { result <- transformList(a, members, stateMachineMember) } + yield { + val (a1, members1) = result + val defStateMachine = Ast.DefStateMachine(name, Some(members1.flatten)) + val node2 = AstNode.create(defStateMachine, node1.id) + (a1, (pre, node2, post)) + } + } + } + } + + override def defStateAnnotatedNode( + a: Analysis, + node: Ast.Annotated[AstNode[Ast.DefState]] + ) = { + val (pre, node1, post) = node + val Ast.DefState(name, members) = node1.data + for { result <- transformList(a, members, stateMember) } + yield { + val (a1, members1) = result + val defStateMachine = Ast.DefState(name, members1.flatten) + val node2 = AstNode.create(defStateMachine, node1.id) + (a1, (pre, node2, post)) + } + } + override def defTopologyAnnotatedNode( a: Analysis, node: Ast.Annotated[AstNode[Ast.DefTopology]] diff --git a/compiler/tools/fpp-syntax/test/include-state-machine.fpp b/compiler/tools/fpp-syntax/test/include-state-machine.fpp new file mode 100644 index 000000000..987b29b2a --- /dev/null +++ b/compiler/tools/fpp-syntax/test/include-state-machine.fpp @@ -0,0 +1,12 @@ +state machine M {@ Action a1 + action a1 + + @ Action a2 + action a2 + + include "subdir/state.fppi" + + state S2 { + include "subdir/state.fppi" + } +} diff --git a/compiler/tools/fpp-syntax/test/include-state-machine.ref.txt b/compiler/tools/fpp-syntax/test/include-state-machine.ref.txt new file mode 100644 index 000000000..5f66b6743 --- /dev/null +++ b/compiler/tools/fpp-syntax/test/include-state-machine.ref.txt @@ -0,0 +1,56 @@ +def state machine + ident M + @ Action a1 + def action + ident a1 + @ Action a2 + def action + ident a2 + def state + ident S1 + spec state entry + action ident a1 + action ident a2 + spec state exit + action ident a1 + action ident a2 + @ Initial transition + spec initial + action ident a1 + action ident a2 + target qual ident S3 + @ Choice C + def choice + ident C + guard ident g1 + action ident a1 + action ident a2 + target qual ident S1 + action ident a2 + action ident a3 + target qual ident S2.S3 + def state + ident S2 + def state + ident S1 + spec state entry + action ident a1 + action ident a2 + spec state exit + action ident a1 + action ident a2 + @ Initial transition + spec initial + action ident a1 + action ident a2 + target qual ident S3 + @ Choice C + def choice + ident C + guard ident g1 + action ident a1 + action ident a2 + target qual ident S1 + action ident a2 + action ident a3 + target qual ident S2.S3 diff --git a/compiler/tools/fpp-syntax/test/run b/compiler/tools/fpp-syntax/test/run index d05b6a688..fdfd76db5 100755 --- a/compiler/tools/fpp-syntax/test/run +++ b/compiler/tools/fpp-syntax/test/run @@ -88,6 +88,11 @@ include_subdir() run_test -ia include-subdir } +include_state_machine() +{ + run_test -ia include-state-machine +} + include_topology() { run_test -ia include-topology diff --git a/compiler/tools/fpp-syntax/test/subdir/state.fppi b/compiler/tools/fpp-syntax/test/subdir/state.fppi new file mode 100644 index 000000000..0d92d7c07 --- /dev/null +++ b/compiler/tools/fpp-syntax/test/subdir/state.fppi @@ -0,0 +1,10 @@ +state S1 { + entry do { a1, a2 } + exit do { a1, a2 } + + @ Initial transition + initial do { a1, a2 } enter S3 + + @ Choice C + choice C { if g1 do { a1, a2 } enter S1 else do { a2, a3 } enter S2.S3 } +} diff --git a/compiler/tools/fpp-syntax/test/update-ref b/compiler/tools/fpp-syntax/test/update-ref index b39447258..4f7d7f37e 100755 --- a/compiler/tools/fpp-syntax/test/update-ref +++ b/compiler/tools/fpp-syntax/test/update-ref @@ -42,6 +42,7 @@ update -ia include-component update -ia include-constant-1 include-constant-1 update -ia include-module update -ia include-subdir +update -ia include-state-machine update -ia include-topology update -ia subdir/include-parent-dir update -ia syntax syntax-include-ast From a2410be743592946cb177ef6736d918c25ee0c17 Mon Sep 17 00:00:00 2001 From: Andrei Tumbar Date: Wed, 28 Jan 2026 21:50:13 -0800 Subject: [PATCH 3/5] Regen the spec --- docs/fpp-spec.html | 14 ++++++++++---- docs/fpp-users-guide.html | 8 ++++---- docs/index.html | 2 +- .../State-Definitions.adoc | 1 + 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/fpp-spec.html b/docs/fpp-spec.html index 2bc7ed7e8..34149d06a 100644 --- a/docs/fpp-spec.html +++ b/docs/fpp-spec.html @@ -4,7 +4,7 @@ - + The F Prime Prime (FPP) Language Specification, v3.1.0