import {ExternalTokenizer, ContextTracker} from "@lezer/lr" import { DirectiveEnd, DocEnd, blockEnd, eof, sequenceStartMark, sequenceContinueMark, explicitMapStartMark, explicitMapContinueMark, mapStartMark, mapContinueMark, flowMapMark, Literal, QuotedLiteral, Anchor, Alias, Tag, BlockLiteralHeader, BlockLiteralContent, BracketL, BraceL, Colon, FlowSequence, FlowMapping } from "./parser.terms.js" const type_Top = 0, // Top document level type_Seq = 1, // Block sequence type_Map = 2, // Block mapping type_Flow = 3, // Inside flow content type_Lit = 4 // Block literal with explicit indentation class Context { constructor(parent, depth, type) { this.parent = parent this.depth = depth this.type = type this.hash = (parent ? parent.hash + parent.hash << 8 : 0) + depth + (depth << 4) + type } } Context.top = new Context(null, -1, type_Top) function findColumn(input, pos) { for (let col = 0, p = pos - input.pos - 1;; p--, col++) { let ch = input.peek(p) if (isBreakSpace(ch) || ch == -1) return col } } function isNonBreakSpace(ch) { return ch == 32 || ch == 9 } function isBreakSpace(ch) { return ch == 10 || ch == 13 } function isSpace(ch) { return isNonBreakSpace(ch) || isBreakSpace(ch) } function isSep(ch) { return ch < 0 || isSpace(ch) } export const indentation = new ContextTracker({ start: Context.top, reduce(context, term) { return context.type == type_Flow && (term == FlowSequence || term == FlowMapping) ? context.parent : context }, shift(context, term, stack, input) { if (term == sequenceStartMark) return new Context(context, findColumn(input, input.pos), type_Seq) if (term == mapStartMark || term == explicitMapStartMark) return new Context(context, findColumn(input, input.pos), type_Map) if (term == blockEnd) return context.parent if (term == BracketL || term == BraceL) return new Context(context, 0, type_Flow) if (term == BlockLiteralContent && context.type == type_Lit) return context.parent if (term == BlockLiteralHeader) { let indent = /[1-9]/.exec(input.read(input.pos, stack.pos)) if (indent) return new Context(context, context.depth + (+indent[0]), type_Lit) } return context }, hash(context) { return context.hash } }) function three(input, ch, off = 0) { return input.peek(off) == ch && input.peek(off + 1) == ch && input.peek(off + 2) == ch && isSep(input.peek(off + 3)) } export const newlines = new ExternalTokenizer((input, stack) => { if (input.next == -1 && stack.canShift(eof)) return input.acceptToken(eof) let prev = input.peek(-1) if ((isBreakSpace(prev) || prev < 0) && stack.context.type != type_Flow) { if (three(input, 45 /* '-' */)) { if (stack.canShift(blockEnd)) input.acceptToken(blockEnd) else return input.acceptToken(DirectiveEnd, 3) } if (three(input, 46 /* '.' */)) { if (stack.canShift(blockEnd)) input.acceptToken(blockEnd) else return input.acceptToken(DocEnd, 3) } let depth = 0 while (input.next == 32 /* ' ' */) { depth++; input.advance() } if ((depth < stack.context.depth || depth == stack.context.depth && stack.context.type == type_Seq && (input.next != 45 /* '-' */ || !isSep(input.peek(1)))) && // Not blank input.next != -1 && !isBreakSpace(input.next) && input.next != 35 /* '#' */) input.acceptToken(blockEnd, -depth) } }, {contextual: true}) export const blockMark = new ExternalTokenizer((input, stack) => { if (stack.context.type == type_Flow) { if (input.next == 63 /* '?' */) { input.advance() if (isSep(input.next)) input.acceptToken(flowMapMark) } return } if (input.next == 45 /* '-' */) { input.advance() if (isSep(input.next)) input.acceptToken(stack.context.type == type_Seq && stack.context.depth == findColumn(input, input.pos - 1) ? sequenceContinueMark : sequenceStartMark) } else if (input.next == 63 /* '?' */) { input.advance() if (isSep(input.next)) input.acceptToken(stack.context.type == type_Map && stack.context.depth == findColumn(input, input.pos - 1) ? explicitMapContinueMark : explicitMapStartMark) } else { let start = input.pos // Scan over a potential key to see if it is followed by a colon. for (;;) { if (isNonBreakSpace(input.next)) { if (input.pos == start) return input.advance() } else if (input.next == 33 /* '!' */) { readTag(input) } else if (input.next == 38 /* '&' */) { readAnchor(input) } else if (input.next == 42 /* '*' */) { readAnchor(input) break } else if (input.next == 39 /* "'" */ || input.next == 34 /* '"' */) { if (readQuoted(input, true)) break return } else if (input.next == 91 /* '[' */ || input.next == 123 /* '{' */) { if (!scanBrackets(input)) return break } else { readPlain(input, true, false, 0) break } } while (isNonBreakSpace(input.next)) input.advance() if (input.next == 58 /* ':' */) { if (input.pos == start && stack.canShift(Colon)) return let after = input.peek(1) if (isSep(after)) input.acceptTokenTo(stack.context.type == type_Map && stack.context.depth == findColumn(input, start) ? mapContinueMark : mapStartMark, start) } } }, {contextual: true}) function uriChar(ch) { return ch > 32 && ch < 127 && ch != 34 && ch != 37 && ch != 44 && ch != 60 && ch != 62 && ch != 92 && ch != 94 && ch != 96 && ch != 123 && ch != 124 && ch != 125 } function hexChar(ch) { return ch >= 48 && ch <= 57 || ch >= 97 && ch <= 102 || ch >= 65 && ch <= 70 } function readUriChar(input, quoted) { if (input.next == 37 /* '%' */) { input.advance() if (hexChar(input.next)) input.advance() if (hexChar(input.next)) input.advance() return true } else if (uriChar(input.next) || quoted && input.next == 44 /* ',' */) { input.advance() return true } return false } function readTag(input) { input.advance() // ! if (input.next == 60 /* '<' */) { input.advance() for (;;) { if (!readUriChar(input, true)) { if (input.next == 62 /* '>' */) input.advance() break } } } else { while (readUriChar(input, false)) {} } } function readAnchor(input) { input.advance() while (!isSep(input.next) && charTag(input.tag) != "f") input.advance() } function readQuoted(input, scan) { let quote = input.next, lineBreak = false, start = input.pos input.advance() for (;;) { let ch = input.next if (ch < 0) break input.advance() if (ch == quote) { if (ch == 39 /* "'" */) { if (input.next == 39) input.advance() else break } else { break } } else if (ch == 92 /* "\\" */ && quote == 34 /* '"' */) { if (input.next >= 0) input.advance() } else if (isBreakSpace(ch)) { if (scan) return false lineBreak = true } else if (scan && input.pos >= start + 1024) { return false } } return !lineBreak } function scanBrackets(input) { for (let stack = [], end = input.pos + 1024;;) { if (input.next == 91 /* '[' */ || input.next == 123 /* '{' */) { stack.push(input.next) input.advance() } else if (input.next == 39 /* "'" */ || input.next == 34 /* '"' */) { if (!readQuoted(input, true)) return false } else if (input.next == 93 /* ']' */ || input.next == 125 /* '}' */) { if (stack[stack.length - 1] != input.next - 2) return false stack.pop() input.advance() if (!stack.length) return true } else if (input.next < 0 || input.pos > end || isBreakSpace(input.next)) { return false } else { input.advance() } } } // "Safe char" info for char codes 33 to 125. s: safe, i: indicator, f: flow indicator const charTable = "iiisiiissisfissssssssssssisssiiissssssssssssssssssssssssssfsfssissssssssssssssssssssssssssfif" function charTag(ch) { if (ch < 33) return "u" if (ch > 125) return "s" return charTable[ch - 33] } function isSafe(ch, inFlow) { let tag = charTag(ch) return tag != "u" && !(inFlow && tag == "f") } function readPlain(input, scan, inFlow, indent) { if (charTag(input.next) == "s" || (input.next == 63 /* '?' */ || input.next == 58 /* ':' */ || input.next == 45 /* '-' */) && isSafe(input.peek(1), inFlow)) { input.advance() } else { return false } let start = input.pos for (;;) { let next = input.next, off = 0, lineIndent = indent + 1 while (isSpace(next)) { if (isBreakSpace(next)) { if (scan) return false lineIndent = 0 } else { lineIndent++ } next = input.peek(++off) } let safe = next >= 0 && (next == 58 /* ':' */ ? isSafe(input.peek(off + 1), inFlow) : next == 35 /* '#' */ ? input.peek(off - 1) != 32 /* ' ' */ : isSafe(next, inFlow)) if (!safe || !inFlow && lineIndent <= indent || lineIndent == 0 && !inFlow && (three(input, 45, off) || three(input, 46, off))) break if (scan && charTag(next) == "f") return false for (let i = off; i >= 0; i--) input.advance() if (scan && input.pos > start + 1024) return false } return true } export const literals = new ExternalTokenizer((input, stack) => { if (input.next == 33 /* '!' */) { readTag(input) input.acceptToken(Tag) } else if (input.next == 38 /* '&' */ || input.next == 42 /* '*' */) { let token = input.next == 38 ? Anchor : Alias readAnchor(input) input.acceptToken(token) } else if (input.next == 39 /* "'" */ || input.next == 34 /* '"' */) { readQuoted(input, false) input.acceptToken(QuotedLiteral) } else if (readPlain(input, false, stack.context.type == type_Flow, stack.context.depth)) { input.acceptToken(Literal) } }) export const blockLiteral = new ExternalTokenizer((input, stack) => { let indent = stack.context.type == type_Lit ? stack.context.depth : -1, upto = input.pos scan: for (;;) { let depth = 0, next = input.next while (next == 32 /* ' ' */) next = input.peek(++depth) if (!depth && (three(input, 45, depth) || three(input, 46, depth))) break if (!isBreakSpace(next)) { if (indent < 0) indent = Math.max(stack.context.depth + 1, depth) if (depth < indent) break } for (;;) { if (input.next < 0) break scan let isBreak = isBreakSpace(input.next) input.advance() if (isBreak) continue scan upto = input.pos } } input.acceptTokenTo(BlockLiteralContent, upto) })