This repository has been archived by the owner on Jun 18, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(client): working basic renderer
- Loading branch information
1 parent
72068c9
commit 6f677f1
Showing
3 changed files
with
219 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,118 @@ | ||
module Lunarbox.Render where | ||
|
||
import Prelude | ||
import Data.FoldableWithIndex (foldrWithIndex) | ||
import Data.List as List | ||
import Lunarflow.Ast (AstF(..)) | ||
import Lunarflow.Geometry.Foreign (fitIntoBounds) | ||
import Data.Set as Set | ||
import Data.Tuple (Tuple(..)) | ||
import Debug.Trace (traceM) | ||
import Lunarflow.Ast (AstF(..), isVar) | ||
import Lunarflow.Geometry.Foreign (getRightBound) | ||
import Lunarflow.Geometry.Foreign as ForeignShape | ||
import Lunarflow.Geometry.Types as Shape | ||
import Lunarflow.Layout (Layout, LayoutF) | ||
import Matryoshka (Algebra, cata) | ||
import Run (Run) | ||
import Run.Reader (READER, local) | ||
import Lunarflow.Geometry.Utils (withPadding) | ||
import Lunarflow.Renderer.WithHeight (YLayout, YLayoutF, YMapSlice, getPosition) | ||
import Matryoshka (GAlgebra, para) | ||
import Run (Run, extract) | ||
import Run.Reader (READER, ask, local, runReader) | ||
|
||
type RenderContext | ||
= { doNotRender :: List.List Int | ||
= { doNotRender :: Set.Set Int | ||
, start :: Int | ||
, end :: Int | ||
, slice :: YMapSlice | ||
} | ||
|
||
type RenderM r | ||
= Run ( reader :: READER RenderContext | r ) | ||
|
||
-- | Prepare stuff for rendering inside a lambda. | ||
shiftContext :: Int -> RenderContext -> RenderContext | ||
shiftContext by ctx = ctx { doNotRender = ((+) by) <$> ctx.doNotRender } | ||
shiftContext by ctx = ctx { doNotRender = ((+) by) `Set.map` ctx.doNotRender } | ||
|
||
lineHeight :: Int | ||
lineHeight = 50 | ||
|
||
lineWidth :: Int | ||
lineWidth = 100 | ||
|
||
{-- | ||
So: | ||
- When we encounter a lambda, we draw the body and then the box around it | ||
- When we encouner a var, we check where it is in scope and draw until here | ||
--} | ||
render :: forall r. Layout -> RenderM r Shape.Shape | ||
render = cata algebra | ||
render :: forall r. YLayout -> RenderM r Shape.Shape | ||
render = para algebra | ||
where | ||
algebra :: Algebra LayoutF (RenderM r Shape.Shape) | ||
algebra (Lambda { args } body) = do | ||
bodyShape <- local (shiftContext $ List.length args) body | ||
algebra :: GAlgebra (Tuple YLayout) YLayoutF (RenderM r Shape.Shape) | ||
algebra (Lambda { args, heights } (Tuple _ body)) = do | ||
bodyShape <- local (_ { slice = heights } <<< (shiftContext $ List.length args)) body | ||
let | ||
bounds = fitIntoBounds bodyShape | ||
bounds = ForeignShape.bounds bodyShape | ||
pure | ||
$ Shape.group {} | ||
[ Shape.fromBounds { fill: "transparent", stroke: "red" } bounds | ||
[ Shape.rect { fill: "black", stroke: "red" } $ withPadding 20 bounds | ||
, bodyShape | ||
] | ||
|
||
algebra _ = pure mempty | ||
algebra (Var { position, index }) = do | ||
{ end, start, slice, doNotRender } <- ask | ||
if (Set.member index doNotRender) then | ||
pure mempty | ||
else do | ||
pure | ||
$ Shape.rect { fill: "yellow", stroke: "black" } | ||
{ x: 0 | ||
, y: getY position slice | ||
, height: lineHeight | ||
, width: max lineWidth start | ||
} | ||
|
||
algebra (Call position mkFunc@(Tuple functionLayout _) mkArg@(Tuple argumentLayout _)) = do | ||
function <- renderFn 0 mkFunc | ||
slice <- ask <#> _.slice | ||
traceM ("Call position " <> show position) | ||
let | ||
functionEnd = if isVar functionLayout then 0 else getRightBound function | ||
|
||
functionPosition = getPosition functionLayout | ||
argument <- renderFn functionEnd mkArg | ||
let | ||
argumentEnd = getRightBound argument | ||
pure | ||
$ Shape.group {} | ||
[ function | ||
, argument | ||
, Shape.rect | ||
{ fill: "green" | ||
, stroke: "black" | ||
} | ||
{ x: functionEnd | ||
, width: argumentEnd - functionEnd | ||
, height: lineHeight | ||
, y: getY functionPosition slice | ||
} | ||
, Shape.rect | ||
{ fill: "blue" | ||
, stroke: "black" | ||
} | ||
{ x: argumentEnd | ||
, width: lineWidth | ||
, height: lineHeight | ||
, y: getY position slice | ||
} | ||
] | ||
where | ||
renderFn start (Tuple ast m) = local (_ { start = start, end = start + lineWidth }) m | ||
|
||
getY :: Int -> YMapSlice -> Int | ||
getY position slice = | ||
lineHeight | ||
* foldrWithIndex | ||
(\index height result -> if position < index then result else result + height) | ||
0 | ||
slice | ||
|
||
-- | Run a computation in the Render monad | ||
runRenderM :: forall a. RenderM () a -> a | ||
runRenderM m = extract $ runReader { doNotRender: Set.empty, start: 0, end: 0, slice: mempty } m |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
module Lunarflow.Renderer.WithHeight | ||
( YLayoutF | ||
, YLayout | ||
, YMapSlice | ||
, withHeights | ||
, getPosition | ||
) where | ||
|
||
import Prelude | ||
import Data.Array (foldr) | ||
import Data.Array as Array | ||
import Data.Bifunctor (lmap) | ||
import Data.Foldable (sum) | ||
import Data.Functor.Mu (Mu) | ||
import Data.List (List, length) as List | ||
import Data.Map as Map | ||
import Data.Tuple (Tuple(..), snd, uncurry) | ||
import Lunarflow.Array (maxZip) | ||
import Lunarflow.Ast (AstF(..), call, lambda, var) | ||
import Lunarflow.Label (class Label) | ||
import Lunarflow.Layout (Layout, LayoutF) | ||
import Matryoshka (Algebra, cata, project) | ||
|
||
-- | The base functor for YLayouts. | ||
type YLayoutF | ||
= AstF { position :: Int, index :: Int } Int { position :: Int, args :: List.List Int, heights :: YMapSlice } | ||
|
||
-- | YLayouts are layouts which keep track of the maximum height of each line | ||
type YLayout | ||
= Mu YLayoutF | ||
|
||
-- TODO: make this a newtype with semigruop (& monoid?) instances | ||
-- | A slice is just an array where each element | ||
-- | represents the height of the line at the same (local) y index | ||
type YMapSlice | ||
= Array Int | ||
|
||
-- TODO: make this a newtype with semigruop & monoid instances. | ||
-- | Map from de brujin indices to slices | ||
type YMap | ||
= Map.Map Int YMapSlice | ||
|
||
-- | Add height data to a layout. | ||
withHeights :: | ||
Layout -> | ||
Tuple YLayout YMap | ||
withHeights = cata algebra | ||
where | ||
algebra :: Algebra LayoutF (Tuple YLayout YMap) | ||
algebra = case _ of | ||
Lambda { position, args } (Tuple body bodyMeasures) -> Tuple yLambda bodyMeasures | ||
where | ||
(Tuple slice remaining) = splitMap (List.length args) bodyMeasures | ||
|
||
measures = updateMap 0 position (sum slice) remaining | ||
|
||
yLambda = lambda { position, args, heights: slice } body | ||
Var { index, position } -> Tuple yVar yMap | ||
where | ||
yVar = var { index, position } | ||
|
||
yMap = updateMap index position 1 Map.empty | ||
Call position (Tuple function functionMeasures) (Tuple argument argumentMeasures) -> Tuple yCall yMap | ||
where | ||
yCall = call position function argument | ||
|
||
yMap = updateMap 0 position 1 $ mergeYMaps functionMeasures argumentMeasures | ||
|
||
-- | Update an ymap at an arbitrary index, position and value. | ||
updateMap :: (Label "index" => Int) -> (Label "position" => Int) -> (Label "value" => Int) -> YMap -> YMap | ||
updateMap index position value = mergeYMaps $ Map.singleton index $ Array.snoc (Array.replicate position 1) value | ||
|
||
-- | Pretty much a semigroup implementation for YMaps | ||
mergeYMaps :: YMap -> YMap -> YMap | ||
mergeYMaps = Map.unionWith mergeArrays | ||
|
||
-- | Pretty much a semigroup implementation for YMapSlice | ||
mergeArrays :: Array Int -> Array Int -> Array Int | ||
mergeArrays arr arr' = maxZip 1 1 arr arr' <#> uncurry max | ||
|
||
splitMap :: Int -> YMap -> Tuple YMapSlice YMap | ||
splitMap shiftBy yMap = Tuple slice remaining | ||
where | ||
slice = foldr mergeArrays [] $ snd <$> split.yes | ||
|
||
remaining = Map.fromFoldable $ lmap (_ - shiftBy) <$> split.no | ||
|
||
split = flip Array.partition arr \(Tuple x _) -> x < shiftBy | ||
|
||
arr :: Array _ | ||
arr = Map.toUnfoldable yMap | ||
|
||
-- | Get the position an YLayout is at. | ||
getPosition :: YLayout -> Int | ||
getPosition = | ||
project | ||
>>> case _ of | ||
Var { position } -> position | ||
Call position _ _ -> position | ||
Lambda { position } _ -> position |