Skip to content

Commit

Permalink
Sequence diagram: loop. #4
Browse files Browse the repository at this point in the history
  • Loading branch information
pylover committed Jun 25, 2021
1 parent 36c5c8d commit 58f189d
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 40 deletions.
11 changes: 1 addition & 10 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,10 @@ foo -> bar
```


## Tokenizer
- Semicolon
- Loop

## Sequence Diagram
- call
- loop
- if
- note: right left over
- comment
- actor
- parallel
- Parallel
- Include

## Class Diagram
Expand Down
46 changes: 26 additions & 20 deletions dial/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,46 +51,52 @@ def __call__(self, interpreter, token):
return self.nextstate


class Switch(Action):
def __init__(self, default=None, **kw):
self.default = default
self.cases = {k.rstrip('_'): v for k, v in kw.items()}
super().__init__()

def __call__(self, interpreter, token, *args):
action = self.cases.get(token.string, self.default)
return action(interpreter, token, *args)


class Goto(Action):
def __init__(self, callback=None, nextstate=None):
self.callback = callback
super().__init__(nextstate)

def __call__(self, interpreter, token):
if self.callback:
self.callback(interpreter)
def _call_callback(self, interpreter, token, *args, **kw):
if self.callback is None:
return

try:
self.callback(interpreter, *args, **kw)
except AttributeError as e:
raise BadAttribute(interpreter, token, '.'.join(e.args))

def __call__(self, interpreter, token, *args):
self._call_callback(interpreter, token, *args)
return super().__call__(interpreter, token)


class Consume(Action):
class Consume(Goto):
capture = [
NAME,
EVERYTHING,
MULTILINE,
]

def __init__(self, callback=None, nextstate=None):
self.callback = callback
super().__init__(nextstate)

def _call_callback(self, interpreter, token, *args, **kw):
if self.callback is None:
return

try:
self.callback(interpreter, *args, **kw)
except AttributeError as e:
raise BadAttribute(interpreter, token, '.'.join(e.args))
super().__init__(callback=callback, nextstate=nextstate)

def __call__(self, interpreter, token):
def __call__(self, interpreter, token, *args):
args = tuple(
i.string for i in interpreter.tokenstack if i.type in self.capture
)
interpreter.tokenstack.clear()
self._call_callback(interpreter, token, *args)

return super().__call__(interpreter, token)
return super().__call__(interpreter, token, *args)


class Ignore(Consume):
Expand All @@ -101,7 +107,7 @@ def __call__(self, interpreter, token):

class FinalConsume(Consume):
def __init__(self, callback):
super().__init__(callback, None)
super().__init__(callback=callback, nextstate=None)

def __call__(self, interpreter, token):
interpreter.more = False
Expand Down
74 changes: 64 additions & 10 deletions dial/sequence.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import abc

from .visible import Visible
from .interpreter import Interpreter, Consume, FinalConsume, New, Ignore, Goto
from .interpreter import Interpreter, Consume, FinalConsume, New, Ignore, \
Goto, Switch
from .token import *


Expand All @@ -9,15 +12,23 @@ def __init__(self, title, type_='module'):
self.type = type_


class Call(Visible, Interpreter, list):
caller = None
callee = None
class Item(Visible, Interpreter, list, metaclass=abc.ABCMeta):
text = None

def __init__(self, tokenizer):
super().__init__(tokenizer, 'call')
super().__init__(tokenizer, 'start')

@property
@abc.abstractmethod
def short_repr(self):
raise NotImplementedError()

def _complete(self, text=None):
self.text = text.strip() if text else None

def __repr__(self):
result = f'{self.caller} -> {self.callee}'
result = self._short_repr

if self.text:
result += f': {self.text}'

Expand All @@ -29,13 +40,22 @@ def __repr__(self):

return result.rstrip('\n')


class Call(Item):
caller = None
callee = None

@property
def _short_repr(self):
return f'{self.caller} -> {self.callee}'

def _complete(self, caller, callee, text=None):
self.caller = caller
self.callee = callee
self.text = text.strip() if text else None
super()._complete(text)

statemap = {
'call': {NAME: {RARROW: {NAME: Goto(nextstate='name -> name')}}},
'start': {NAME: {RARROW: {NAME: Goto(nextstate='name -> name')}}},
'name -> name': {
NEWLINE: FinalConsume(_complete),
EOF: FinalConsume(_complete),
Expand All @@ -47,6 +67,32 @@ def _complete(self, caller, callee, text=None):
}


class Loop(Item):
type_ = None

@property
def _short_repr(self):
return self.type_

def _complete(self, type_, text=None):
self.type_ = type_
super()._complete(text)

statemap = {
'start': {
NAME: Goto(nextstate='name'),
},
'name': {
NAME: Goto(nextstate='name'),
NEWLINE: FinalConsume(_complete),
COLON: Goto(nextstate=':'),
},
':': {EVERYTHING: {
NEWLINE: FinalConsume(_complete)
}}
}


class SequenceDiagram(Visible, Interpreter, list):
title = 'Untitled'

Expand Down Expand Up @@ -102,6 +148,9 @@ def _new_call(self, call):
self._ensuremodule(call.callee)
self.current.append(call)

def _new_loop(self, loop):
self.current.append(loop)

def _attr(self, attr, value):
value = value.strip()

Expand All @@ -126,12 +175,17 @@ def _module_attr(self, module, attr, value):
},
DEDENT: Ignore(callback=_dedent, nextstate='start'),
EOF: Ignore(nextstate='start'),
NAME: Goto(nextstate='name'),
NAME: Switch(
for_=New(Loop, callback=_new_loop, nextstate='start'),
while_=New(Loop, callback=_new_loop, nextstate='start'),
loop=New(Loop, callback=_new_loop, nextstate='start'),
default=Goto(nextstate='name')
),
},
'name': {
RARROW: New(Call, callback=_new_call, nextstate='start'),
COLON: Goto(nextstate='attr:'),
DOT: {NAME: {COLON: Goto(nextstate='mod.attr:')}}
DOT: {NAME: {COLON: Goto(nextstate='mod.attr:')}},
},
'attr:': {
EVERYTHING: {NEWLINE: Consume(_attr, nextstate='start')}
Expand Down
17 changes: 17 additions & 0 deletions tests/test_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@
from dial.interpreter import BadSyntax, BadAttribute


def test_sequence_loop():
d = SequenceDiagram(Tokenizer())
s = '''# Sequence
title: loop
for
foo -> bar: baz(*)
for: i in range(10)
foo -> bar: baz(i)
while: j > 0
foo -> bar: baz(j)
loop: over [1, 2, 3]
'''
d.parse(s)
assert repr(d) == s[:-1]


def test_sequence_comment():
d = SequenceDiagram(Tokenizer())
s = '''# Sequence
Expand Down

0 comments on commit 58f189d

Please sign in to comment.