summaryrefslogtreecommitdiff
path: root/src/declarations.ts
blob: 057111625c18785fdc67e39ad1948388fb1dfc2b (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
import { EvalActions, evalExpr } from './eval'
import { Expr, Selector, SelectorComp, parseDeclarations } from './parser'
import { match, matchString } from './utils/adt'

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

export interface DeclarationEval {
  selector: Selector
  properties: Array<readonly [string, string]>
}

export const evaluateDeclaration = async (
  { selector, properties }: Declaration,
  actions: EvalActions,
): Promise<DeclarationEval> => {
  if (properties.size === 0) return { selector, properties: [] }

  const props = await Promise.all(
    [...properties.entries()].map(async ([key, expr]) => {
      // Ignore errors?
      const result = await evalExpr(expr, actions).catch(e => console.warn(e))
      return [key, result ?? ''] as const
    }),
  )

  return { selector, properties: props }
}

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 = (expr: Expr): Declaration | undefined => {
  let selector: Selector | undefined
  const properties: Map<string, Expr> = new Map()
  let isInstance = false

  match(expr, {
    Selector: sel => {
      selector = sel
    },
    Call: ({ name, args }) => {
      matchString(name, {
        instance: () => {
          isInstance = true
          const [sel, map] = args

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

          match(map, {
            Call: ({ name, args }) => {
              if (name !== 'map') return
              for (const arg of args) {
                match(arg, {
                  Pair: ({ key, value }) => properties.set(key, value),
                  _: _ => {},
                })
              }
            },
          })
        },
        _: () => {
          throw new Error(`weird function in cssx-chi9ldren: ${name}`)
        },
      })
    },
    _: () => {},
  })

  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, isInstance }
}

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

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