{throwSyntaxError, extractAllCommentTokens} = require './helpers'
CoffeeScript 語言有許多可選的語法、隱含的語法和簡寫語法。這可能會極大地複雜化語法並增加產生的解析表。我們不讓解析器處理所有這些,而是使用此 Rewriter 對記號串流進行一系列的傳遞,將簡寫轉換為明確的長格式,加入隱含的縮排和括號,並大致整理一下。
{throwSyntaxError, extractAllCommentTokens} = require './helpers'
將附加的註解從一個記號移至另一個記號。
moveComments = (fromToken, toToken) ->
return unless fromToken.comments
if toToken.comments and toToken.comments.length isnt 0
unshiftedComments = []
for comment in fromToken.comments
if comment.unshift
unshiftedComments.push comment
else
toToken.comments.push comment
toToken.comments = unshiftedComments.concat toToken.comments
else
toToken.comments = fromToken.comments
delete fromToken.comments
建立一個產生的記號:由於使用隱含的語法而存在的記號。選擇性地讓此新記號取得來自另一個記號的附加註解。
generate = (tag, value, origin, commentsToken) ->
token = [tag, value]
token.generated = yes
token.origin = origin if origin
moveComments commentsToken, token if commentsToken
token
exports.Rewriter = class Rewriter
一次一個邏輯篩選器,以多重傳遞重寫記號串流。這當然可以改為透過串流進行一次傳遞,並使用一個大的有效率的切換,但這樣處理起來好多了。這些傳遞的順序很重要,必須先修正縮排,才能將隱含的括號包在程式碼區塊周圍。
rewrite: (@tokens) ->
將環境變數 DEBUG_TOKEN_STREAM
設為 true
以輸出記號除錯資訊。也將 DEBUG_REWRITTEN_TOKEN_STREAM
設為 true
以在由這個檔案重寫後輸出記號串流。
if process?.env?.DEBUG_TOKEN_STREAM
console.log 'Initial token stream:' if process.env.DEBUG_REWRITTEN_TOKEN_STREAM
console.log (t[0] + '/' + t[1] + (if t.comments then '*' else '') for t in @tokens).join ' '
@removeLeadingNewlines()
@closeOpenCalls()
@closeOpenIndexes()
@normalizeLines()
@tagPostfixConditionals()
@addImplicitBracesAndParens()
@rescueStowawayComments()
@addLocationDataToGeneratedTokens()
@enforceValidJSXAttributes()
@fixIndentationLocationData()
@exposeTokenDataToGrammar()
if process?.env?.DEBUG_REWRITTEN_TOKEN_STREAM
console.log 'Rewritten token stream:' if process.env.DEBUG_TOKEN_STREAM
console.log (t[0] + '/' + t[1] + (if t.comments then '*' else '') for t in @tokens).join ' '
@tokens
重寫記號串流,向前和向後查看一個記號。允許區塊的回傳值告訴我們在串流中向前(或向後)移動多少個記號,以確保在插入和移除記號時我們不會遺漏任何東西,而且串流的長度在我們腳下改變。
scanTokens: (block) ->
{tokens} = this
i = 0
i += block.call this, token, i, tokens while token = tokens[i]
true
detectEnd: (i, condition, action, opts = {}) ->
{tokens} = this
levels = 0
while token = tokens[i]
return action.call this, token, i if levels is 0 and condition.call this, token, i
if token[0] in EXPRESSION_START
levels += 1
else if token[0] in EXPRESSION_END
levels -= 1
if levels < 0
return if opts.returnOnNegativeLevel
return action.call this, token, i
i += 1
i - 1
前導換行符號會在語法中造成歧義,因此我們在此將它們傳送。
removeLeadingNewlines: ->
找出第一個非TERMINATOR
令牌的索引。
break for [tag], i in @tokens when tag isnt 'TERMINATOR'
return if i is 0
如果我們即將捨棄的令牌附有任何註解,請將它們向前移動到將成為新的第一個令牌的位置。
for leadingNewlineToken in @tokens[0...i]
moveComments leadingNewlineToken, @tokens[i]
捨棄所有前導換行符號令牌。
@tokens.splice 0, i
詞法分析器標記了方法呼叫的開括號。將其與配對的閉合括號匹配。
closeOpenCalls: ->
condition = (token, i) ->
token[0] in [')', 'CALL_END']
action = (token, i) ->
token[0] = 'CALL_END'
@scanTokens (token, i) ->
@detectEnd i + 1, condition, action if token[0] is 'CALL_START'
1
詞法分析器標記了索引操作呼叫的開方括號。將其與配對的閉合方括號匹配。
closeOpenIndexes: ->
startToken = null
condition = (token, i) ->
token[0] in [']', 'INDEX_END']
action = (token, i) ->
if @tokens.length >= i and @tokens[i + 1][0] is ':'
startToken[0] = '['
token[0] = ']'
else
token[0] = 'INDEX_END'
@scanTokens (token, i) ->
if token[0] is 'INDEX_START'
startToken = token
@detectEnd i + 1, condition, action
1
從i
開始使用pattern
匹配令牌串流中的標記。pattern
可能包含字串(等號)、字串陣列(其中之一)或 null(萬用字元)。傳回匹配的索引,如果沒有匹配,則傳回 -1。
indexOfTag: (i, pattern...) ->
fuzz = 0
for j in [0 ... pattern.length]
continue if not pattern[j]?
pattern[j] = [pattern[j]] if typeof pattern[j] is 'string'
return -1 if @tag(i + j + fuzz) not in pattern[j]
i + j + fuzz - 1
如果站在類似@<x>:
、<x>:
或<EXPRESSION_START><x>...<EXPRESSION_END>:
的東西前面,則傳回yes
。
looksObjectish: (j) ->
return yes if @indexOfTag(j, '@', null, ':') isnt -1 or @indexOfTag(j, null, ':') isnt -1
index = @indexOfTag j, EXPRESSION_START
if index isnt -1
end = null
@detectEnd index + 1, ((token) -> token[0] in EXPRESSION_END), ((token, i) -> end = i)
return yes if @tag(end + 1) is ':'
no
如果令牌的目前行包含同一個表達式層級上的標記元素,則傳回yes
。在LINEBREAKS
或包含平衡表達式的明確開始處停止搜尋。
findTagsBackwards: (i, tags) ->
backStack = []
while i >= 0 and (backStack.length or
@tag(i) not in tags and
(@tag(i) not in EXPRESSION_START or @tokens[i].generated) and
@tag(i) not in LINEBREAKS)
backStack.push @tag(i) if @tag(i) in EXPRESSION_END
backStack.pop() if @tag(i) in EXPRESSION_START and backStack.length
i -= 1
@tag(i) in tags
在令牌串流中尋找隱含呼叫和物件的跡象,並將它們新增。
addImplicitBracesAndParens: ->
在堆疊上追蹤目前的平衡深度(隱含和明確)。
stack = []
start = null
@scanTokens (token, i, tokens) ->
[tag] = token
[prevTag] = prevToken = if i > 0 then tokens[i - 1] else []
[nextTag] = nextToken = if i < tokens.length - 1 then tokens[i + 1] else []
stackTop = -> stack[stack.length - 1]
startIdx = i
輔助函式,用於在傳回取得新令牌時追蹤已使用和拼接的令牌數量。
forward = (n) -> i - startIdx + n
輔助函式
isImplicit = (stackItem) -> stackItem?[2]?.ours
isImplicitObject = (stackItem) -> isImplicit(stackItem) and stackItem?[0] is '{'
isImplicitCall = (stackItem) -> isImplicit(stackItem) and stackItem?[0] is '('
inImplicit = -> isImplicit stackTop()
inImplicitCall = -> isImplicitCall stackTop()
inImplicitObject = -> isImplicitObject stackTop()
隱含括號內未封閉的控制陳述式(例如類別宣告或 if 條件式)。
inImplicitControl = -> inImplicit() and stackTop()?[0] is 'CONTROL'
startImplicitCall = (idx) ->
stack.push ['(', idx, ours: yes]
tokens.splice idx, 0, generate 'CALL_START', '(', ['', 'implicit function call', token[2]], prevToken
endImplicitCall = ->
stack.pop()
tokens.splice i, 0, generate 'CALL_END', ')', ['', 'end of input', token[2]], prevToken
i += 1
startImplicitObject = (idx, {startsLine = yes, continuationLineIndent} = {}) ->
stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes, continuationLineIndent: continuationLineIndent]
val = new String '{'
val.generated = yes
tokens.splice idx, 0, generate '{', val, token, prevToken
endImplicitObject = (j) ->
j = j ? i
stack.pop()
tokens.splice j, 0, generate '}', '}', token, prevToken
i += 1
implicitObjectContinues = (j) =>
nextTerminatorIdx = null
@detectEnd j,
(token) -> token[0] is 'TERMINATOR'
(token, i) -> nextTerminatorIdx = i
returnOnNegativeLevel: yes
return no unless nextTerminatorIdx?
@looksObjectish nextTerminatorIdx + 1
如果下列任一項在引數/值中,請不要在下一行縮排時結束隱含呼叫/物件。
if (
(inImplicitCall() or inImplicitObject()) and tag in CONTROL_IN_IMPLICIT or
inImplicitObject() and prevTag is ':' and tag is 'FOR'
)
stack.push ['CONTROL', i, ours: yes]
return forward(1)
if tag is 'INDENT' and inImplicit()
if prevTag not in ['=>', '->', '[', '(', ',', '{', 'ELSE', '=']
while inImplicitCall() or inImplicitObject() and prevTag isnt ':'
if inImplicitCall()
endImplicitCall()
else
endImplicitObject()
stack.pop() if inImplicitControl()
stack.push [tag, i]
return forward(1)
明確表達式的直接開頭。
if tag in EXPRESSION_START
stack.push [tag, i]
return forward(1)
關閉明確封閉表達式內的所有隱含表達式。
if tag in EXPRESSION_END
while inImplicit()
if inImplicitCall()
endImplicitCall()
else if inImplicitObject()
endImplicitObject()
else
stack.pop()
start = stack.pop()
inControlFlow = =>
seenFor = @findTagsBackwards(i, ['FOR']) and @findTagsBackwards(i, ['FORIN', 'FOROF', 'FORFROM'])
controlFlow = seenFor or @findTagsBackwards i, ['WHILE', 'UNTIL', 'LOOP', 'LEADING_WHEN']
return no unless controlFlow
isFunc = no
tagCurrentLine = token[2].first_line
@detectEnd i,
(token, i) -> token[0] in LINEBREAKS
(token, i) ->
[prevTag, ,{first_line}] = tokens[i - 1] || []
isFunc = tagCurrentLine is first_line and prevTag in ['->', '=>']
returnOnNegativeLevel: yes
isFunc
辨識標準隱含呼叫,例如 f a、f() b、f? c、h[0] d 等。已新增支援左側的散佈點:f …a
if (tag in IMPLICIT_FUNC and token.spaced or
tag is '?' and i > 0 and not tokens[i - 1].spaced) and
(nextTag in IMPLICIT_CALL or
(nextTag is '...' and @tag(i + 2) in IMPLICIT_CALL and not @findTagsBackwards(i, ['INDEX_START', '['])) or
nextTag in IMPLICIT_UNSPACED_CALL and
not nextToken.spaced and not nextToken.newLine) and
not inControlFlow()
tag = token[0] = 'FUNC_EXIST' if tag is '?'
startImplicitCall i + 1
return forward(2)
將隱含縮排物件作為第一個參數的隱含呼叫。
f
a: b
c: d
當與下列控制結構在同一行時,請勿接受此類型的隱含呼叫,因為這可能會誤解類似
if f
a: 1
as
if f(a: 1)
的建構,而這可能總是無意的。此外,請勿在文字陣列或明確物件的第一行允許這樣做,因為這會產生語法歧義 (#5368)。
if tag in IMPLICIT_FUNC and
@indexOfTag(i + 1, 'INDENT') > -1 and @looksObjectish(i + 2) and
not @findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH',
'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL']) and
not ((s = stackTop()?[0]) in ['{', '['] and
not isImplicit(stackTop()) and
@findTagsBackwards(i, s))
startImplicitCall i + 1
stack.push ['INDENT', i + 2]
return forward(3)
隱含物件從這裡開始。
if tag is ':'
回到物件的(隱含)開頭。
s = switch
when @tag(i - 1) in EXPRESSION_END
[startTag, startIndex] = start
if startTag is '[' and startIndex > 0 and @tag(startIndex - 1) is '@' and not tokens[startIndex - 1].spaced
startIndex - 1
else
startIndex
when @tag(i - 2) is '@' then i - 2
else i - 1
startsLine = s <= 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine
我們是否只是繼續已宣告的物件?包括在明確的「{」之後縮排的情況。
if stackTop()
[stackTag, stackIdx] = stackTop()
stackNext = stack[stack.length - 2]
if (stackTag is '{' or
stackTag is 'INDENT' and stackNext?[0] is '{' and
not isImplicit(stackNext) and
@findTagsBackwards(stackIdx-1, ['{'])) and
(startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{') and
@tag(s - 1) not in UNFINISHED
return forward(1)
preObjectToken = if i > 1 then tokens[i - 2] else []
startImplicitObject(s, {startsLine: !!startsLine, continuationLineIndent: preObjectToken.continuationLineIndent})
return forward(2)
將所有封閉物件標記為 not sameLine
if tag in LINEBREAKS
for stackItem in stack by -1
break unless isImplicit stackItem
stackItem[2].sameLine = no if isImplicitObject stackItem
縮排持續行隱含物件結束時,結束縮排。
if tag is 'TERMINATOR' and token.endsContinuationLineIndentation
{preContinuationLineIndent} = token.endsContinuationLineIndentation
while inImplicitObject() and (implicitObjectIndent = stackTop()[2].continuationLineIndent)? and implicitObjectIndent > preContinuationLineIndent
endImplicitObject()
newLine = prevTag is 'OUTDENT' or prevToken.newLine
if tag in IMPLICIT_END or
(tag in CALL_CLOSERS and newLine) or
(tag in ['..', '...'] and @findTagsBackwards(i, ["INDEX_START"]))
while inImplicit()
[stackTag, stackIdx, {sameLine, startsLine}] = stackTop()
到達引數清單結束時,關閉隱式呼叫
if inImplicitCall() and prevTag isnt ',' or
(prevTag is ',' and tag is 'TERMINATOR' and not nextTag?)
endImplicitCall()
關閉隱式物件,例如:回傳 a: 1, b: 2 除非 true
else if inImplicitObject() and sameLine and
tag isnt 'TERMINATOR' and prevTag isnt ':' and
not (tag in ['POST_IF', 'FOR', 'WHILE', 'UNTIL'] and startsLine and implicitObjectContinues(i + 1))
endImplicitObject()
當行尾時關閉隱式物件,該行沒有以逗號結尾,且隱式物件沒有開始該行,或下一行看起來不像物件的延續。
else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and
not (startsLine and @looksObjectish(i + 1))
endImplicitObject()
else if inImplicitControl() and tokens[stackTop()[1]][0] is 'CLASS' and tag is 'TERMINATOR'
stack.pop()
else
break
如果逗號是最後一個字元,並且後面看起來不屬於該處,則關閉隱式物件。這用於尾隨逗號和呼叫,例如
x =
a: b,
c: d,
e = 2
和
f a, b: c, d: e, f, g: h: i, j
if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and not (@tag(i + 2) in ['FOROF', 'FORIN']) and
(nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2))
offset = if nextTag is 'OUTDENT' then 1 else 0
while inImplicitObject()
endImplicitObject i + offset
return forward(1)
確保僅在 JSX 屬性中使用字串和包裝的運算式。
enforceValidJSXAttributes: ->
@scanTokens (token, i, tokens) ->
if token.jsxColon
next = tokens[i + 1]
if next[0] not in ['STRING_START', 'STRING', '(']
throwSyntaxError 'expected wrapped or quoted JSX attribute', next[2]
return 1
並非所有代碼都會在經過剖析器的處理後存活下來。為避免註解遺失,請找出附在註定失敗的代碼上的註解,並將它們移至會存活下來的代碼。
rescueStowawayComments: ->
insertPlaceholder = (token, j, tokens, method) ->
tokens[method] generate 'TERMINATOR', '\n', tokens[j] unless tokens[j][0] is 'TERMINATOR'
tokens[method] generate 'JS', '', tokens[j], token
dontShiftForward = (i, tokens) ->
j = i + 1
while j isnt tokens.length and tokens[j][0] in DISCARDED
return yes if tokens[j][0] is 'INTERPOLATION_END'
j++
no
shiftCommentsForward = (token, i, tokens) ->
找出下一個存活的代碼,並將此代碼的註解附加到該代碼上,並加上一個標記,表示我們知道要在該代碼編譯之前輸出此類註解。(否則,註解會在附加的代碼之後輸出。)
j = i
j++ while j isnt tokens.length and tokens[j][0] in DISCARDED
unless j is tokens.length or tokens[j][0] in DISCARDED
comment.unshift = yes for comment in token.comments
moveComments token, tokens[j]
return 1
else # All following tokens are doomed!
j = tokens.length - 1
insertPlaceholder token, j, tokens, 'push'
產生的代碼新增到結尾,而不是內嵌,因此我們不會跳過。
return 1
shiftCommentsBackward = (token, i, tokens) ->
找出最後一個存活的代碼,並將此代碼的註解附加到該代碼上。
j = i
j-- while j isnt -1 and tokens[j][0] in DISCARDED
unless j is -1 or tokens[j][0] in DISCARDED
moveComments token, tokens[j]
return 1
else # All previous tokens are doomed!
insertPlaceholder token, 0, tokens, 'unshift'
我們新增了兩個代碼,因此向前移動以考量插入。
return 3
@scanTokens (token, i, tokens) ->
return 1 unless token.comments
ret = 1
if token[0] in DISCARDED
此代碼無法通過解析器的傳遞,因此我們需要救援其附加的代碼,並將它們重新分配到附近的代碼。未開始新行的註解可以向後移動到最後一個安全的代碼,而其他代碼應向前移動。
dummyToken = comments: []
j = token.comments.length - 1
until j is -1
if token.comments[j].newLine is no and token.comments[j].here is no
dummyToken.comments.unshift token.comments[j]
token.comments.splice j, 1
j--
if dummyToken.comments.length isnt 0
ret = shiftCommentsBackward dummyToken, i - 1, tokens
if token.comments.length isnt 0
shiftCommentsForward token, i, tokens
else unless dontShiftForward i, tokens
如果此代碼的任何註解開始一行(前一個換行符號和註解開頭之間只有空白),且這不是特殊 JS
代碼之一,則向前移動此註解以在下一有效代碼之前。Block.compileComments
也有邏輯,以確保「開始新行」註解遵循或在相對於註解附加到的代碼最近的換行符號之前,但該換行符號可能在 }
或 )
或其他我們真正希望此註解在之後輸出的已產生代碼中。因此,我們需要在這裡移動註解,避免此類已產生且已捨棄的代碼。
dummyToken = comments: []
j = token.comments.length - 1
until j is -1
if token.comments[j].newLine and not token.comments[j].unshift and
not (token[0] is 'JS' and token.generated)
dummyToken.comments.unshift token.comments[j]
token.comments.splice j, 1
j--
if dummyToken.comments.length isnt 0
ret = shiftCommentsForward dummyToken, i + 1, tokens
delete token.comments if token.comments?.length is 0
ret
將位置資料新增到重寫器產生的所有代碼。
addLocationDataToGeneratedTokens: ->
@scanTokens (token, i, tokens) ->
return 1 if token[2]
return 1 unless token.generated or token.explicit
if token.fromThen and token[0] is 'INDENT'
token[2] = token.origin[2]
return 1
if token[0] is '{' and nextLocation=tokens[i + 1]?[2]
{first_line: line, first_column: column, range: [rangeIndex]} = nextLocation
else if prevLocation = tokens[i - 1]?[2]
{last_line: line, last_column: column, range: [, rangeIndex]} = prevLocation
column += 1
else
line = column = 0
rangeIndex = 0
token[2] = {
first_line: line
first_column: column
last_line: line
last_column: column
last_line_exclusive: line
last_column_exclusive: column
range: [rangeIndex, rangeIndex]
}
return 1
OUTDENT
代碼應始終置於前一個代碼的最後一個字元,以便以 OUTDENT
代碼結尾的 AST 節點最終會得到與節點下最後一個「真實」代碼對應的位置。
fixIndentationLocationData: ->
@allComments ?= extractAllCommentTokens @tokens
findPrecedingComment = (token, {afterPosition, indentSize, first, indented}) =>
tokenStart = token[2].range[0]
matches = (comment) ->
if comment.outdented
return no unless indentSize? and comment.indentSize > indentSize
return no if indented and not comment.indented
return no unless comment.locationData.range[0] < tokenStart
return no unless comment.locationData.range[0] > afterPosition
yes
if first
lastMatching = null
for comment in @allComments by -1
if matches comment
lastMatching = comment
else if lastMatching
return lastMatching
return lastMatching
for comment in @allComments when matches comment by -1
return comment
null
@scanTokens (token, i, tokens) ->
return 1 unless token[0] in ['INDENT', 'OUTDENT'] or
(token.generated and token[0] is 'CALL_END' and not token.data?.closingTagNameToken) or
(token.generated and token[0] is '}')
isIndent = token[0] is 'INDENT'
prevToken = token.prevToken ? tokens[i - 1]
prevLocationData = prevToken[2]
addLocationDataToGeneratedTokens() 將縮排的位置資料設定為前一個代碼的位置資料,但為了偵測空「區塊」中的註解,我們想要尋找下一個代碼之前的註解。
useNextToken = token.explicit or token.generated
if useNextToken
nextToken = token
nextTokenIndex = i
nextToken = tokens[nextTokenIndex++] while (nextToken.explicit or nextToken.generated) and nextTokenIndex isnt tokens.length - 1
precedingComment = findPrecedingComment(
if useNextToken
nextToken
else
token
afterPosition: prevLocationData.range[0]
indentSize: token.indentSize
first: isIndent
indented: useNextToken
)
if isIndent
return 1 unless precedingComment?.newLine
例如,我們不希望在 if
條件的結尾處有一個隱含呼叫來包含後面的縮排註解。
return 1 if token.generated and token[0] is 'CALL_END' and precedingComment?.indented
prevLocationData = precedingComment.locationData if precedingComment?
token[2] =
first_line:
if precedingComment?
prevLocationData.first_line
else
prevLocationData.last_line
first_column:
if precedingComment?
if isIndent
0
else
prevLocationData.first_column
else
prevLocationData.last_column
last_line: prevLocationData.last_line
last_column: prevLocationData.last_column
last_line_exclusive: prevLocationData.last_line_exclusive
last_column_exclusive: prevLocationData.last_column_exclusive
range:
if isIndent and precedingComment?
[
prevLocationData.range[0] - precedingComment.indentSize
prevLocationData.range[1]
]
else
prevLocationData.range
return 1
由於我們的語法是 LALR(1),因此無法處理某些缺乏結尾分隔符號的單行表達式。Rewriter 會新增隱含區塊,因此不需要這樣做。為了保持語法簡潔明瞭,會移除表達式中的尾隨換行符,並新增空區塊的縮排記號。
normalizeLines: ->
starter = indent = outdent = null
leading_switch_when = null
leading_if_then = null
計算 THEN
標籤
ifThens = []
condition = (token, i) ->
token[1] isnt ';' and token[0] in SINGLE_CLOSERS and
not (token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE) and
not (token[0] is 'ELSE' and
(starter isnt 'THEN' or (leading_if_then or leading_switch_when))) and
not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>']) or
token[0] in CALL_CLOSERS and
(@tokens[i - 1].newLine or @tokens[i - 1][0] is 'OUTDENT')
action = (token, i) ->
ifThens.pop() if token[0] is 'ELSE' and starter is 'THEN'
@tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent
closeElseTag = (tokens, i) =>
tlen = ifThens.length
return i unless tlen > 0
lastThen = ifThens.pop()
[, outdentElse] = @indentation tokens[lastThen]
插入 OUTDENT
以關閉內部 IF
。
outdentElse[1] = tlen*2
tokens.splice(i, 0, outdentElse)
插入 OUTDENT
以關閉外部 IF
。
outdentElse[1] = 2
tokens.splice(i + 1, 0, outdentElse)
從結尾移除縮排。
@detectEnd i + 2,
(token, i) -> token[0] in ['OUTDENT', 'TERMINATOR']
(token, i) ->
if @tag(i) is 'OUTDENT' and @tag(i + 1) is 'OUTDENT'
tokens.splice i, 2
i + 2
@scanTokens (token, i, tokens) ->
[tag] = token
conditionTag = tag in ['->', '=>'] and
@findTagsBackwards(i, ['IF', 'WHILE', 'FOR', 'UNTIL', 'SWITCH', 'WHEN', 'LEADING_WHEN', '[', 'INDEX_START']) and
not (@findTagsBackwards i, ['THEN', '..', '...'])
if tag is 'TERMINATOR'
if @tag(i + 1) is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
tokens.splice i, 1, @indentation()...
return 1
if @tag(i + 1) in EXPRESSION_CLOSE
if token[1] is ';' and @tag(i + 1) is 'OUTDENT'
tokens[i + 1].prevToken = token
moveComments token, tokens[i + 1]
tokens.splice i, 1
return 0
if tag is 'CATCH'
for j in [1..2] when @tag(i + j) in ['OUTDENT', 'TERMINATOR', 'FINALLY']
tokens.splice i + j, 0, @indentation()...
return 2 + j
if tag in ['->', '=>'] and (@tag(i + 1) in [',', ']'] or @tag(i + 1) is '.' and token.newLine)
[indent, outdent] = @indentation tokens[i]
tokens.splice i + 1, 0, indent, outdent
return 1
if tag in SINGLE_LINERS and @tag(i + 1) isnt 'INDENT' and
not (tag is 'ELSE' and @tag(i + 1) is 'IF') and
not conditionTag
starter = tag
[indent, outdent] = @indentation tokens[i]
indent.fromThen = true if starter is 'THEN'
if tag is 'THEN'
leading_switch_when = @findTagsBackwards(i, ['LEADING_WHEN']) and @tag(i + 1) is 'IF'
leading_if_then = @findTagsBackwards(i, ['IF']) and @tag(i + 1) is 'IF'
ifThens.push i if tag is 'THEN' and @findTagsBackwards(i, ['IF'])
ELSE
標籤未關閉。
if tag is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
i = closeElseTag tokens, i
tokens.splice i + 1, 0, indent
@detectEnd i + 2, condition, action
tokens.splice i, 1 if tag is 'THEN'
return 1
return 1
將標籤後綴條件標記為此類,以便我們可以使用不同的優先順序來分析它們。
tagPostfixConditionals: ->
original = null
condition = (token, i) ->
[tag] = token
[prevTag] = @tokens[i - 1]
tag is 'TERMINATOR' or (tag is 'INDENT' and prevTag not in SINGLE_LINERS)
action = (token, i) ->
if token[0] isnt 'INDENT' or (token.generated and not token.fromThen)
original[0] = 'POST_' + original[0]
@scanTokens (token, i) ->
return 1 unless token[0] is 'IF'
original = token
@detectEnd i + 1, condition, action
return 1
對於具有額外資料的記號,我們希望透過將記號值包裝為 String() 物件,並將資料設定為該物件的屬性,讓語法可以看到該資料。然後,語法應負責為節點建構函式清除此資料:將記號值解開為原始字串,並分別傳遞任何預期的記號資料屬性
exposeTokenDataToGrammar: ->
@scanTokens (token, i) ->
if token.generated or (token.data and Object.keys(token.data).length isnt 0)
token[1] = new String token[1]
token[1][key] = val for own key, val of (token.data ? {})
token[1].generated = yes if token.generated
1
根據同一行上的另一個記號產生縮排記號。
indentation: (origin) ->
indent = ['INDENT', 2]
outdent = ['OUTDENT', 2]
if origin
indent.generated = outdent.generated = yes
indent.origin = outdent.origin = origin
else
indent.explicit = outdent.explicit = yes
[indent, outdent]
generate: generate
依據記號索引查詢標籤。
tag: (i) -> @tokens[i]?[0]
必須平衡的記號對清單。
BALANCED_PAIRS = [
['(', ')']
['[', ']']
['{', '}']
['INDENT', 'OUTDENT'],
['CALL_START', 'CALL_END']
['PARAM_START', 'PARAM_END']
['INDEX_START', 'INDEX_END']
['STRING_START', 'STRING_END']
['INTERPOLATION_START', 'INTERPOLATION_END']
['REGEX_START', 'REGEX_END']
]
我們嘗試修復的 BALANCED_PAIRS
反向對應,以便我們可以從任一端查詢項目。
exports.INVERSES = INVERSES = {}
表示平衡對開始/結束的記號。
EXPRESSION_START = []
EXPRESSION_END = []
for [left, right] in BALANCED_PAIRS
EXPRESSION_START.push INVERSES[right] = left
EXPRESSION_END .push INVERSES[left] = right
表示表達式子句結束的符號。
EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
如果後面跟著 IMPLICIT_CALL
,表示函式呼叫。
IMPLICIT_FUNC = ['IDENTIFIER', 'PROPERTY', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']
如果前面是 IMPLICIT_FUNC
,表示函式呼叫。
IMPLICIT_CALL = [
'IDENTIFIER', 'JSX_TAG', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN'
'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS'
'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS'
'DYNAMIC_IMPORT', 'IMPORT_META', 'NEW_TARGET'
'UNDEFINED', 'NULL', 'BOOL'
'UNARY', 'DO', 'DO_IIFE', 'YIELD', 'AWAIT', 'UNARY_MATH', 'SUPER', 'THROW'
'@', '->', '=>', '[', '(', '{', '--', '++'
]
IMPLICIT_UNSPACED_CALL = ['+', '-']
總是標記單行隱式呼叫結束的符號。
IMPLICIT_END = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY',
'LOOP', 'TERMINATOR']
未封閉結尾的區塊表達式的單行樣式。語法無法消除歧義,因此我們插入隱式縮排。
SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
結束一行的符號。
LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT']
換行後關閉開啟呼叫的符號。
CALL_CLOSERS = ['.', '?.', '::', '?::']
防止後續縮排結束隱式呼叫/物件的符號
CONTROL_IN_IMPLICIT = ['IF', 'TRY', 'FINALLY', 'CATCH', 'CLASS', 'SWITCH']
被解析器吞噬的符號,從不導致程式碼產生。您可以在 grammar.coffee
中發現這些符號,因為 o
函式的第二個引數不包含這些符號的 new
呼叫。STRING_START
不在這個清單中,因為它的 locationData
與變成 StringWithInterpolations
的節點相符,因此 addDataToNode
將 STRING_START
的符號附加到該節點。
DISCARDED = ['(', ')', '[', ']', '{', '}', ':', '.', '..', '...', ',', '=', '++', '--', '?',
'AS', 'AWAIT', 'CALL_START', 'CALL_END', 'DEFAULT', 'DO', 'DO_IIFE', 'ELSE',
'EXTENDS', 'EXPORT', 'FORIN', 'FOROF', 'FORFROM', 'IMPORT', 'INDENT', 'INDEX_SOAK',
'INTERPOLATION_START', 'INTERPOLATION_END', 'LEADING_WHEN', 'OUTDENT', 'PARAM_END',
'REGEX_START', 'REGEX_END', 'RETURN', 'STRING_END', 'THROW', 'UNARY', 'YIELD'
].concat IMPLICIT_UNSPACED_CALL.concat IMPLICIT_END.concat CALL_CLOSERS.concat CONTROL_IN_IMPLICIT
出現在行尾時,會抑制後續 TERMINATOR/INDENT 符號的符號
exports.UNFINISHED = UNFINISHED = ['\\', '.', '?.', '?::', 'UNARY', 'DO', 'DO_IIFE', 'MATH', 'UNARY_MATH', '+', '-',
'**', 'SHIFT', 'RELATION', 'COMPARE', '&', '^', '|', '&&', '||',
'BIN?', 'EXTENDS']