-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplaceholders.lsc
More file actions
72 lines (60 loc) · 2.67 KB
/
placeholders.lsc
File metadata and controls
72 lines (60 loc) · 2.67 KB
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
import t from '../types'
import { getFunctionParent } from '../helpers/blocks'
isIndexedPlaceholderPath(phPath) -> (phPath.node.index)~looseNotEq(null)
isSpreadPlaceholderPath(phPath) -> phPath.parentPath?.node.type == "SpreadElement"
isNonIndexedPlaceholderPath(phPath) -> (phPath.node.index)~looseEq(null) and (not phPath~isSpreadPlaceholderPath())
registerPlaceholder(functions, path) ->
fnPath = path~getFunctionParent()
if !fnPath || !fnPath.node || !fnPath.node.params:
throw path.buildCodeFrameError("Placeholders cannot be used outside functions.")
if fnPath.node.params.length:
throw path.buildCodeFrameError("Placeholders cannot be used in functions with arguments.")
if path.parentPath?.node?.type == "SpreadElement":
if path.node?.index:
throw path.buildCodeFrameError("Cannot use indices with spread placeholders.")
let fnInfo = functions.get(fnPath)
if fnInfo:
fnInfo.placeholders.push(path)
else:
functions.set(fnPath, { functionPath: fnPath, placeholders: [ path ] })
liftPlaceholders(fnPath, phPaths) ->
// Collate placeholders
ixPhPaths = phPaths.filter(isIndexedPlaceholderPath)
nixPhPaths = phPaths.filter(isNonIndexedPlaceholderPath)
spreadPhPaths = phPaths.filter(isSpreadPlaceholderPath)
if ixPhPaths.length > 0 and nixPhPaths.length > 0:
throw fnPath.buildCodeFrameError("Cannot mix indexed and non-indexed placeholders in a function.")
// Formulate args
args = []
let restArg = undefined
maxArg = if ixPhPaths.length > 0:
// Indexed case
Math.max(...ixPhPaths.map(x -> x.node.index)) + 1
else if nixPhPaths.length > 0: 1
else: 0
// Generate unique identifiers for args
for let i = 0; i < maxArg; i++: args.push(fnPath.scope.generateUidIdentifier("arg"))
if spreadPhPaths.length > 0:
now restArg = fnPath.scope.generateUidIdentifier("arg")
args.push(t.restElement(restArg))
// Replace placeholders with references to args
for elem phPath in nixPhPaths: phPath.replaceWith(args[0])
for elem phPath in ixPhPaths: phPath.replaceWith(args[phPath.node.index])
for elem phPath in spreadPhPaths: phPath.replaceWith(restArg)
// Replace function definition arg list
fnNode = fnPath.node
fnNode.params = args
fnPath.replaceWith(fnNode)
// Collect all functions with placeholders into a Map, one entry per function
findFunctionsWithPlaceholders(programPath) ->
fns = new Map()
programPath.traverse({
LscPlaceholderExpression(path): void ->
registerPlaceholder(fns, path, programPath)
})
fns
export transformAllPlaceholders(programPath) ->
fns = programPath~findFunctionsWithPlaceholders()
fns.forEach( fnInfo ->
fnInfo.functionPath~liftPlaceholders(fnInfo.placeholders)
)