summaryrefslogtreecommitdiff
path: root/src/declarations.ts
blob: 5725846c1a4de8ad9c0740d111581358efb23870 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { EvalActions, EvalValue, evalExpr } from './eval'
import { Expr, Selector, SelectorComp, parseDeclarations } from './parser'
import { match, matchString } from './utils/adt'

export interface Declaration {
  selector: Selector
  properties: Map<string, EvalValue>
  children: Array<Declaration>
  isInstance: boolean
}

const instanceCountMap = new Map<string, number>()
const getUniqueInstanceId = (id: string) => {
  const instanceCount = instanceCountMap.get(id) ?? 0
  instanceCountMap.set(id, instanceCount + 1)
  return `${id}--index-${instanceCount}`
}

export const toDeclaration =
  (actions: EvalActions) =>
  async (expr: Expr): Promise<Declaration | undefined> => {
    let selector: Selector | undefined
    const properties: Map<string, EvalValue> = new Map()
    const children: Array<Declaration> = []
    let isInstance = false

    await match(expr, {
      Selector: async sel => {
        selector = sel
      },
      Call: async ({ name, args }) => {
        return matchString(name, {
          h: async () => {
            const [sel, map, childreExpr] = args

            // Selector
            match(sel, {
              Selector: sel => {
                selector = sel
              },
              _: _ => {},
            })

            const props = await evalExpr(map, actions)
            match(props, {
              Map: props => {
                for (const [key, value] of Object.entries(props)) {
                  properties.set(key, value)
                }
              },
              _: _ => {},
            })

            const childrenExprs = await match<
              Promise<Array<Declaration | undefined>>,
              EvalValue
            >(await evalExpr(childreExpr, actions), {
              Lazy: async exprs =>
                Promise.all(exprs.map(toDeclaration(actions))),
              _: async _ => [],
            })

            children.push(
              ...(childrenExprs.filter(Boolean) as Array<Declaration>),
            )
          },
          instance: async () => {
            isInstance = true
            const [sel, map] = args

            // Selector
            match(sel, {
              Selector: sel => {
                selector = sel
              },
              _: _ => {},
            })

            const props = await evalExpr(map, actions)
            match(props, {
              Map: props => {
                for (const [key, value] of Object.entries(props)) {
                  properties.set(key, value)
                }
              },
              _: _ => {},
            })
          },
          _: async () => {
            throw new Error(`weird function in cssx-chi9ldren: ${name}`)
          },
        })
      },
      _: async () => {},
    })

    if (!selector) return undefined

    if (isInstance) {
      const baseId = selector.id
      selector.id = getUniqueInstanceId(selector.id)
      selector.selectors.push(SelectorComp.Attr(['data-instance', baseId]))
    }

    return { selector, properties, children, isInstance }
  }

export const expressionsToDeclrs = async (
  exprs: Array<Expr>,
  actions: EvalActions,
): Promise<Array<Declaration>> => {
  const declrs = await Promise.all(exprs.map(toDeclaration(actions)))
  return declrs.filter(declr => !!declr) as Array<Declaration>
}

export const extractDeclaration = async (
  input: string,
  actions: EvalActions,
): Promise<Array<Declaration>> => {
  const exprs = parseDeclarations(input)
  return expressionsToDeclrs(exprs, actions)
}