Search code examples
pythonsyntax-errordslstate-machinetextx

TextX state machine model not getting parsed properly


I am trying to build a state machine for microwave oven. I am designing it such that it has only two functionalities as of now : bake and clean. It will have 3 states: idle, baking and cleaning. While transitioning into a state, the state machine will perform actions. The actions are commands like run the timer, lock the door, turn on light inside, etc. I am trying to group many of such commands into a command group and pass that command group to the actions. Events are user performed events events like start baking, start cleaning, etc.

My command grouping logic is returning syntax errors. I am stuck on how to make sense out of the error and how to fix the issue.

PFB my meta model and oven model.

Meta-model:

StateMachine:
    'events'
        events+=Event
    'end'

    'commands'
        commands+=Command
    'end'

    ('commandgroups'
        commandGroupBlocks+=CommandGroupBlock        
    'end')? 

    states+=State
;

Keyword:
    'end' | 'events' | 'state' | 'actions' | 'commandgroups' | 'group' | 'groupend'
;

Event:
    name=SMID code=ID
;

Command:
    name=SMID code=ID
;

CommandGroupBlock:
    'group' name=SMID
        groupCommands+=[Command|SMID]       
    'groupend'
;

State:
    'state' name=ID
        ('actions' '{' actions+=[CommandGroupBlock|Command] '}')?
        (transitions+=Transition)?
    'end'
;

Transition:
    event=[Event|SMID] '=>' to_state=[State]
;

SMID:
    !Keyword ID
;

Comment:
    /\/\*(.|\n)*?\*\//
;

Oven model:

events
  bakeOn            E_BKON
  cleanOn           E_CLON
  cleanOff          E_CLOF
end

commands
  timerInitial      C_TMIN  
  timerRunning      C_TMRN
  lockDoor          C_D1LK
  unlockDoor        C_D1UL
  magnetronOn       C_MGON
  magnetronOff      C_MGOF
  internalLightOn   C_ILON
  internalLightOff  C_ILOF
  turnTableOn       C_TTON
  turnTableOff      C_TTOF
  cleanerOn         C_CLON    
  cleanerOff        C_CLOF
end

commandgroups
  group resetCommands
    unlockDoor
    timerInitial
    magnetronOff
    internalLightOff
    turnTableOff
    cleanerOff
  groupend
  group commonActiveCommands
    lockDoor  
    internalLightOn
    timerRunning  
  groupend
end

state idle
  actions {resetCommands}
  bakeOn => baking
  cleanOn => cleaning
end

state baking
  actions {commonActiveCommand magnetronOn turnTableOn}
end

state cleaning
  actions {commonActiveCommand cleanerOn turnTableOff}
  cleanOff => idle
end

The error i am getting is :

Traceback (most recent call last):
  File "state_machine.py", line 70, in <module>
    model = meta_model.model_from_file(sys.argv[1])
  File "/workspace/dsl-state-machine-oven/demo/lib/python3.8/site-packages/textx/metamodel.py", line 661, in model_from_file
    return self.internal_model_from_file(
  File "/workspace/dsl-state-machine-oven/demo/lib/python3.8/site-packages/textx/metamodel.py", line 709, in internal_model_from_file
    model = self._parser_blueprint.clone().get_model_from_str(
  File "/workspace/dsl-state-machine-oven/demo/lib/python3.8/site-packages/textx/model.py", line 372, in get_model_from_str
    self.parse(model_str, file_name=file_name)
  File "/workspace/dsl-state-machine-oven/demo/lib/python3.8/site-packages/arpeggio/__init__.py", line 1525, in parse
    self.parse_tree = self._parse()
  File "/workspace/dsl-state-machine-oven/demo/lib/python3.8/site-packages/textx/model.py", line 332, in _parse
    raise TextXSyntaxError(message=e.message,
textx.exceptions.TextXSyntaxError: /workspace/dsl-state-machine-oven/oven.sm:39:25: Expected ID => 'etCommands*}   bakeOn'

Please help me out with the issue.


Solution

  • You were close. The problem is that you are trying to reference either a command or command group from the state actions. But the syntax is not correct.

    State:
        'state' name=ID
            ('actions' '{' actions+=[CommandGroupBlock|Command] '}')?
            (transitions+=Transition)?
        'end'
    ;
    

    Part actions+=[CommandGroupBlock|Command] would mean match one or more CommandGroupBlock using Command to match the name.

    What you actually wanted is:

    GroupOrCommand: Command | CommandGroupBlock;
    
    State:
        'state' name=ID
            ('actions' '{' actions+=[GroupOrCommand|SMID] '}')?
            (transitions+=Transition)?
        'end'
    ;
    
    

    Here we introduced a new abstract rule CommandOrGroup and then we are linking to this rule using SMID to parse names.

    BTW, there is also a typo in the model: commonActiveCommand -> commonActiveCommands.