Skip to content

Commit 1bbeca0

Browse files
Merge pull request #281 from statebox/diagramv2
Diagramv2
2 parents 6309702 + cc4cc1b commit 1bbeca0

File tree

9 files changed

+227
-63
lines changed

9 files changed

+227
-63
lines changed

stbx-lang/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"postinstall": "spago install",
1515
"start": "spago run",
1616
"build": "spago build",
17+
"watch": "spago build --watch",
1718
"test": "spago test"
1819
},
1920
"license": "ISC",

stbx-lang/spago.dhall

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
, "debug"
66
, "effect"
77
, "halogen-petrinet-editor"
8+
, "memoize"
89
, "parsing"
910
, "psci-support"
1011
, "spec"
@@ -15,4 +16,4 @@
1516
./../packages.dhall
1617
, sources =
1718
[ "src/**/*.purs", "test/**/*.purs" ]
18-
}
19+
}

stbx-lang/src/Language/Statebox.purs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Language.Statebox.Wiring.AST (GElem(..)) as Wiring
1111
import Language.Statebox.Wiring.Parser as WiringParser
1212

1313
parseNet :: String -> Either ParseError (List Net.GElem)
14-
parseNet src = runParser src NetParser.graph1
14+
parseNet src = runParser src NetParser.net
1515

16-
parseWiring :: String -> Either ParseError (List Wiring.GElem)
17-
parseWiring src = runParser src WiringParser.graph1
16+
parseDiagram :: String -> Either ParseError (List Wiring.GElem)
17+
parseDiagram src = runParser src WiringParser.diagram

stbx-lang/src/Language/Statebox/Net/Parser.purs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import Language.Statebox.Hypergraph (NodeF(..), HyperEdgeF(..), GElemF(..))
1818
import Language.Statebox.Net.AST (Node(..), HyperEdge(..), LabelWithSpan, LabelWithSpanWithType, GElem(..))
1919
import Language.Statebox.Parser.Util (getPosition, hspaces, inside, isAlphaNum, someOf)
2020

21-
graph1 :: Parser String (List GElem)
22-
graph1 = (gElem `inside` hspaces) `sepEndBy` (semicolon <|> newlines)
21+
net :: Parser String (List GElem)
22+
net = (gElem `inside` hspaces) `sepEndBy` (semicolon <|> newlines)
2323
where
2424
newlines = skipMany1 (char '\n')
2525
semicolon = const unit <$> char ';'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module Language.Statebox.Wiring.Generator where
2+
3+
import Prelude
4+
import Data.Array (length, elemIndex)
5+
import Data.Maybe (maybe)
6+
import Data.Foldable (foldMap)
7+
import Data.Bitraversable (bitraverse)
8+
import Data.List (List)
9+
import Data.Traversable (traverse)
10+
import Data.Traversable.Accum.Internal (StateL(..), stateL)
11+
12+
import Language.Statebox.Hypergraph (HyperEdgeF(..), GElemF(..))
13+
import Language.Statebox.Wiring.AST (GElem, stripSpan)
14+
15+
16+
toIndexedGraph :: List GElem -> { graph :: List (GElemF List Int Unit), names :: Array String }
17+
toIndexedGraph ast = { graph: acc.value, names: acc.accum }
18+
where
19+
acc = traverse (bitraverse (stripSpan >>> lookupOrAdd) idStateL) ast # (_ `stateL` [])
20+
21+
lookupOrAdd :: v. Eq v => v -> StateL (Array v) Int
22+
lookupOrAdd v =
23+
StateL $ \vs -> elemIndex v vs # maybe { accum: vs <> [v], value: length vs + 1 }
24+
\i -> { accum: vs, value: i + 1 }
25+
26+
idStateL :: a s. a -> StateL s a
27+
idStateL value = StateL $ \accum -> { accum, value }
28+
29+
30+
type Edges a = Array { src :: a, tgt :: a }
31+
32+
getEdges :: a. List (GElemF List a Unit) -> Edges a
33+
getEdges = foldMap f
34+
where
35+
f (GHyperEdge (HyperEdge _ srcs tgts)) = foldMap (\src -> foldMap (\tgt -> [{ src, tgt }]) tgts) srcs
36+
f _ = []
Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,18 @@
11
module Language.Statebox.Wiring.Generator.Diagram where
22

33
import Prelude
4-
import Data.Array (length, elemIndex)
5-
import Data.Maybe (maybe)
6-
import Data.Foldable (foldMap)
7-
import Data.Bitraversable (bitraverse)
8-
import Data.Traversable (traverse)
9-
import Data.Traversable.Accum.Internal (StateL(..), stateL)
4+
import Data.Array (length)
105
import Data.List (List)
116

12-
import Language.Statebox.Hypergraph (HyperEdgeF(..), GElemF(..))
13-
import Language.Statebox.Wiring.AST (GElem, stripSpan)
7+
import Language.Statebox.Wiring.Generator (toIndexedGraph, getEdges)
8+
import Language.Statebox.Wiring.AST (GElem)
149
import Statebox.Core.Types (Diagram)
1510

1611
toDiagramWithName :: String -> List GElem -> Diagram
1712
toDiagramWithName name ast =
18-
{ name, width, pixels, names: acc.accum }
13+
{ name, width, pixels, names }
1914
where
20-
acc = traverse (bitraverse (stripSpan >>> lookupOrAdd) idStateL) ast # (_ `stateL` [])
21-
getEdges (GHyperEdge (HyperEdge _ srcs targs)) = foldMap (\src -> foldMap (\targ -> [{ src, targ }]) targs) srcs
22-
getEdges _ = []
23-
edges = foldMap getEdges acc.value
15+
{ graph, names } = toIndexedGraph ast
16+
edges = getEdges graph
2417
width = length edges
25-
pixels = (edges <#> _.src) <> (edges <#> _.targ)
26-
27-
lookupOrAdd :: v. Eq v => v -> StateL (Array v) Int
28-
lookupOrAdd v =
29-
StateL $ \vs -> elemIndex v vs # maybe { accum: vs <> [v], value: length vs + 1 }
30-
\i -> { accum: vs, value: i + 1 }
31-
32-
idStateL :: a s. a -> StateL s a
33-
idStateL value = StateL $ \accum -> { accum, value }
18+
pixels = (edges <#> _.src) <> (edges <#> _.tgt)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
module Language.Statebox.Wiring.Generator.DiagramV2 where
2+
3+
import Prelude
4+
import Data.Array (zipWith, take, drop, concat, length, (..), (!!), uncons, elemIndex, filter)
5+
import Data.Char (fromCharCode, toCharCode)
6+
import Data.Foldable (class Foldable, maximum, intercalate, foldMap, fold, notElem)
7+
import Data.FoldableWithIndex (foldMapWithIndex)
8+
import Data.FunctorWithIndex (mapWithIndex)
9+
import Data.List (List)
10+
import Data.Map (Map, fromFoldableWith, lookup, union, toUnfoldable)
11+
import Data.Map.Internal (keys)
12+
import Data.Maybe (maybe, fromMaybe)
13+
import Data.String.CodeUnits (singleton)
14+
import Data.TraversableWithIndex (mapAccumLWithIndex)
15+
import Data.Tuple (snd)
16+
import Data.Tuple.Nested ((/\), type (/\))
17+
import Data.Function.Memoize (memoize, class Tabulate)
18+
import Statebox.Core.Types (Diagram)
19+
20+
import Language.Statebox.Wiring.Generator (Edges, toIndexedGraph, getEdges)
21+
import Language.Statebox.Wiring.AST (GElem)
22+
23+
-- | A kdmoncat-compatible diagram in source code representation.
24+
type DiagramV2 =
25+
{ pixels :: String
26+
, context :: String
27+
}
28+
29+
fromDiagramAst :: List GElem -> DiagramV2
30+
fromDiagramAst ast = fromEdges (_ - 1) name edges
31+
where
32+
{ graph, names } = toIndexedGraph ast
33+
edges = getEdges graph
34+
name id = names !! (id - 1) # fromMaybe "?"
35+
36+
fromDiagram :: Diagram -> DiagramV2
37+
fromDiagram { width, pixels, names } = fromEdges (_ - 1) name edges
38+
where
39+
rows = chunks width pixels
40+
edges = concat $ zipWith (zipWith (\src tgt -> { src, tgt })) rows (drop 1 rows)
41+
name id = names !! (id - 1) # fromMaybe "?"
42+
43+
fromEdges :: a. Ord a => Tabulate a => (a -> Int) -> (a -> String) -> Edges a -> DiagramV2
44+
fromEdges fromEnum name edges = { pixels, context }
45+
where
46+
pixels = (0 .. height) <#> row # intercalate "\n"
47+
context = [nodeTypes, swapTypes] # intercalate "\n"
48+
49+
predecessors :: Map a (Array a)
50+
predecessors = edges <#> (\{ src, tgt } -> tgt /\ [src]) # mfromFoldable
51+
52+
successors :: Map a (Array a)
53+
successors = edges <#> (\{ src, tgt } -> src /\ [tgt]) # mfromFoldable
54+
55+
inputs :: Map a (Array Int)
56+
inputs = edges # mapWithIndex (\i { tgt } -> tgt /\ [i]) # mfromFoldable
57+
58+
outputs :: Map a (Array Int)
59+
outputs = edges # mapWithIndex (\i { src } -> src /\ [i]) # mfromFoldable
60+
61+
level :: a -> Int
62+
level = memoize \a -> mlookup a predecessors <#> level # maximum # maybe 0 (_ + 1)
63+
64+
nodes :: List a
65+
nodes = (successors `union` predecessors) # keys
66+
67+
grouped :: Array (Array a)
68+
grouped = nodes <#> (\id -> level id /\ [id]) # mfromFoldable # toUnfoldable <#> snd
69+
70+
width = length grouped
71+
height = grouped <#> length # maximum # fromMaybe 0
72+
73+
typeStr :: a -> Map a (Array a) -> (a -> String) -> String
74+
typeStr a m f = mlookup a m <#> f # intercalate " "
75+
76+
pixel :: a -> String
77+
pixel a = nextChar 'A' (fromEnum a)
78+
79+
nodeType :: a -> String
80+
nodeType a = name a <> "@" <> pixel a <> ": " <> typeStr a predecessors (\b -> name b <> "_" <> name a)
81+
<> " -> " <> typeStr a successors (\b -> name a <> "_" <> name b)
82+
nodeTypes :: String
83+
nodeTypes = map nodeType nodes # intercalate "\n"
84+
85+
row :: Int -> String
86+
row y = grouped # foldMapWithIndex \x g ->
87+
((g !! y) # maybe (if x > 0 && x < width - 1 then nextChar 'n' (x - 1) else " ") pixel) <>
88+
if x < width - 1 then nextChar 'a' x else ""
89+
90+
swapTypes :: String
91+
swapTypes = grouped
92+
# uncons
93+
<#> (\{ head, tail } -> mapAccumLWithIndex mkSwap (levelOutputs head) tail)
94+
# maybe "" (_.value >>> intercalate "\n")
95+
96+
mkSwap :: Int -> Array Int -> Array a -> { accum :: Array Int, value :: String }
97+
mkSwap i edgeIds as = { accum: levelOutputs as <> rest, value }
98+
where
99+
value = nextChar 'a' i <> ": [" <> intercalate " " order <> "]\n" <>
100+
nextChar 'n' i <> ": [" <> intercalate " " ((1 ..< (length rest + 1)) <#> show) <> "]"
101+
ids = foldMap (\a -> mlookup a inputs) as
102+
order = (ids <> rest) <#> \id -> elemIndex id edgeIds # maybe "?" ((_ + 1) >>> show)
103+
rest = filter (\id -> id `notElem` ids) edgeIds
104+
105+
levelOutputs :: Array a -> Array Int
106+
levelOutputs = foldMap (\a -> mlookup a outputs)
107+
108+
mfromFoldable :: f k v. Foldable f => Ord k => Monoid v => f (k /\ v) -> Map k v
109+
mfromFoldable = fromFoldableWith (flip (<>))
110+
111+
mlookup :: k v. Ord k => Monoid v => k -> Map k v -> v
112+
mlookup k = lookup k >>> fold
113+
114+
nextChar :: Char -> Int -> String
115+
nextChar c i = fromCharCode (toCharCode c + i) # maybe "?" singleton
116+
117+
rangeEx :: Int -> Int -> Array Int
118+
rangeEx x y = if y > x then x .. (y - 1) else []
119+
120+
infix 8 rangeEx as ..<
121+
122+
chunks :: a. Int -> Array a -> Array (Array a)
123+
chunks _ [] = []
124+
chunks n xs = [take n xs] <> (chunks n $ drop n xs)

stbx-lang/src/Language/Statebox/Wiring/Parser.purs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import Language.Statebox.Hypergraph (NodeF(..), HyperEdgeF(..), GElemF(..))
1818
import Language.Statebox.Wiring.AST (Node(..), HyperEdge(..), LabelWithSpan, GElem(..))
1919
import Language.Statebox.Parser.Util (getPosition, hspaces, inside, isAlphaNum, someOf)
2020

21-
graph1 :: Parser String (List GElem)
22-
graph1 = (gElem `inside` hspaces) `sepEndBy` (semicolon <|> newlines)
21+
diagram :: Parser String (List GElem)
22+
diagram = (gElem `inside` hspaces) `sepEndBy` (semicolon <|> newlines)
2323
where
2424
newlines = skipMany1 (char '\n')
2525
semicolon = const unit <$> char ';'

stbx-lang/test/Wiring.purs

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,63 @@ import Data.Bifunctor (lmap)
55
import Data.List as List
66
import Data.List (List)
77
import Data.String (trim)
8-
import Language.Statebox as Statebox
8+
import Language.Statebox as Stbx
99
import Language.Statebox.Hypergraph (HyperEdgeF(..), GElemF(..))
1010
import Language.Statebox.Wiring.AST (Label, stripSpan)
11-
import Language.Statebox.Wiring.Generator.Diagram (toDiagramWithName)
11+
import Language.Statebox.Wiring.Generator.Diagram (toDiagramWithName) as DiagramV1
12+
import Language.Statebox.Wiring.Generator.DiagramV2 as DiagramV2
1213
import Statebox.Core.Types (Diagram)
13-
import Test.Spec (Spec, describe, it)
14-
import Test.Spec.Assertions (shouldEqual)
14+
import Test.Spec (Spec, describe, it)
15+
import Test.Spec.Assertions (shouldEqual)
16+
17+
import Debug.Trace (spy)
1518

1619
spec :: Spec Unit
1720
spec = do
1821
describe "Statebox wiring compiler" do
19-
it "should parse wirings correctly" do
20-
let ast = Statebox.parseWiring wiring1src
21-
let diagram1 = toDiagramWithName "dummy" <$> ast
22-
(ast # map (map (lmap stripSpan))) `shouldEqual` pure wiring1expected
23-
diagram1 `shouldEqual` pure diagram1expected
24-
25-
wiring1src :: String
26-
wiring1src = trim """
27-
a1 -> b1, b2
28-
b1 -> d1
29-
b2 -> c1
30-
c1 -> d1
31-
"""
32-
33-
wiring1expected :: List (GElemF List Label Unit)
34-
wiring1expected = mkAst
35-
[ mkEdge ["a1"] ["b1", "b2"]
36-
, mkEdge ["b1"] ["d1"]
37-
, mkEdge ["b2"] ["c1"]
38-
, mkEdge ["c1"] ["d1"]
39-
]
40-
41-
diagram1expected :: Diagram
42-
diagram1expected =
43-
{ name: "dummy"
44-
, width: 5
45-
, pixels: [ 1, 1, 2, 3, 5, 2, 3, 4, 5, 4 ]
46-
, names: [ "a1", "b1", "b2", "d1", "c1" ]
47-
}
22+
let
23+
diagramSrc :: String
24+
diagramSrc = trim """
25+
a1 -> b1, b2
26+
b1 -> d1
27+
b2 -> c1
28+
c1 -> d1
29+
"""
30+
31+
diagramAstExpected :: List (GElemF List Label Unit)
32+
diagramAstExpected = mkAst
33+
[ mkEdge ["a1"] ["b1", "b2"]
34+
, mkEdge ["b1"] ["d1"]
35+
, mkEdge ["b2"] ["c1"]
36+
, mkEdge ["c1"] ["d1"]
37+
]
38+
39+
diagramV1Expected :: Diagram
40+
diagramV1Expected =
41+
{ name: "diagramV1"
42+
, width: 5
43+
, pixels: [ 1, 1, 2, 3, 5, 2, 3, 4, 5, 4 ]
44+
, names: [ "a1", "b1", "b2", "d1", "c1" ]
45+
}
46+
47+
it "should parse a diagram correctly" do
48+
let
49+
diagramAstWithSpans = Stbx.parseDiagram diagramSrc
50+
diagramAst = diagramAstWithSpans # map (map (lmap stripSpan))
51+
diagramAst `shouldEqual` pure diagramAstExpected
52+
53+
it "should compile a diagram AST to the corresponding Diagram (v1)" do
54+
let
55+
diagramAstWithSpans = Stbx.parseDiagram diagramSrc -- TODO we don't want spans here
56+
diagramV1 = DiagramV1.toDiagramWithName "diagramV1" <$> diagramAstWithSpans
57+
diagramV1 `shouldEqual` pure diagramV1Expected
58+
59+
it "should convert a Diagram (v1) to its correspondingDiagramV2 (kdmoncat)" do
60+
let
61+
diagramAstWithSpans = Stbx.parseDiagram diagramSrc -- TODO we don't want spans here
62+
diagramV2 = DiagramV2.fromDiagram $ diagramV1Expected
63+
diagramV2' = DiagramV2.fromDiagramAst <$> diagramAstWithSpans
64+
pure diagramV2 `shouldEqual` diagramV2'
4865

4966
--------------------------------------------------------------------------------
5067
-- graph DSL

0 commit comments

Comments
 (0)