diff --git a/static/js/components/literate-programming.js b/static/js/components/literate-programming.js index 28ab03f..fc5e7b6 100644 --- a/static/js/components/literate-programming.js +++ b/static/js/components/literate-programming.js @@ -35,77 +35,10 @@ function getLiterateProgramWorkerCommands(name, count) { return commands; } +const javascriptJson = await (await fetch(`${TREEHOUSE_SITE}/static/syntax/javascript.json`)).text(); + class InputMode { - static JAVASCRIPT = compileSyntax({ - patterns: [ - { regex: /\/\/.*/, as: "comment" }, - { regex: /\/\*.*?\*\//ms, as: "comment" }, - { regex: /[A-Z_][a-zA-Z0-9_]*/, as: "keyword2" }, - { regex: /[a-zA-Z_][a-zA-Z0-9_]*(?=\()/, as: "function" }, - { regex: /[a-zA-Z_][a-zA-Z0-9_]*/, as: "identifier" }, - { regex: /0[bB][01_]+n?/, as: "literal" }, - { regex: /0[oO][0-7_]+n?/, as: "literal" }, - { regex: /0[xX][0-9a-fA-F_]+n?/, as: "literal" }, - { regex: /[0-9_]+n/, as: "literal" }, - { regex: /[0-9_]+(\.[0-9_]*([eE][-+]?[0-9_]+)?)?/, as: "literal" }, - { regex: /'(\\'|[^'])*'/, as: "string" }, - { regex: /"(\\"|[^"])*"/, as: "string" }, - { regex: /`(\\`|[^"])*`/, as: "string" }, - // TODO: RegExp literals? - { regex: /[+=/*^%<>!~|&\.?:-]+/, as: "operator" }, - { regex: /[,;]/, as: "punct" }, - ], - keywords: new Map([ - ["as", { into: "keyword1", onlyReplaces: "identifier" }], - ["async", { into: "keyword1", onlyReplaces: "identifier" }], - ["await", { into: "keyword1" }], - ["break", { into: "keyword1" }], - ["case", { into: "keyword1" }], - ["catch", { into: "keyword1" }], - ["class", { into: "keyword1" }], - ["const", { into: "keyword1" }], - ["continue", { into: "keyword1" }], - ["debugger", { into: "keyword1" }], - ["default", { into: "keyword1" }], - ["delete", { into: "keyword1" }], - ["do", { into: "keyword1" }], - ["else", { into: "keyword1" }], - ["export", { into: "keyword1" }], - ["extends", { into: "keyword1" }], - ["finally", { into: "keyword1" }], - ["for", { into: "keyword1" }], - ["from", { into: "keyword1", onlyReplaces: "identifier" }], - ["function", { into: "keyword1" }], - ["get", { into: "keyword1", onlyReplaces: "identifier" }], - ["if", { into: "keyword1" }], - ["import", { into: "keyword1" }], - ["in", { into: "keyword1" }], - ["instanceof", { into: "keyword1" }], - ["let", { into: "keyword1" }], - ["new", { into: "keyword1" }], - ["of", { into: "keyword1", onlyReplaces: "identifier" }], - ["return", { into: "keyword1" }], - ["set", { into: "keyword1", onlyReplaces: "identifier" }], - ["static", { into: "keyword1" }], - ["switch", { into: "keyword1" }], - ["throw", { into: "keyword1" }], - ["try", { into: "keyword1" }], - ["typeof", { into: "keyword1" }], - ["var", { into: "keyword1" }], - ["void", { into: "keyword1" }], - ["while", { into: "keyword1" }], - ["with", { into: "keyword1" }], - ["yield", { into: "keyword1" }], - - ["super", { into: "keyword2" }], - ["this", { into: "keyword2" }], - - ["false", { into: "literal" }], - ["true", { into: "literal" }], - ["undefined", { into: "literal" }], - ["null", { into: "literal" }], - ]), - }) + static JAVASCRIPT = compileSyntax(JSON.parse(javascriptJson)); constructor(frame) { this.frame = frame; diff --git a/static/js/components/literate-programming/highlight.js b/static/js/components/literate-programming/highlight.js index 59af6a1..a09b3b4 100644 --- a/static/js/components/literate-programming/highlight.js +++ b/static/js/components/literate-programming/highlight.js @@ -6,12 +6,15 @@ export function compileSyntax(def) { for (let pattern of def.patterns) { - // Remove g (global) flag as it would interfere with the lexis process. We only want to match - // the first token at the cursor. - let flags = pattern.regex.flags.replace("g", ""); - // Add d (indices) and y (sticky) flags so that we can tell where the matches start and end. - pattern.regex = new RegExp(pattern.regex, "y" + flags); + let flags = "dy"; + if (pattern.flags != null) { + if ("dotMatchesNewline" in pattern.flags) { + flags += "s"; + } + } + pattern.regex = new RegExp(pattern.regex, flags); } + def.keywords = new Map(Object.entries(def.keywords)); return def; } @@ -34,7 +37,18 @@ function tokenize(text, syntax) { let match; pattern.regex.lastIndex = i; if ((match = pattern.regex.exec(text)) != null) { - pushToken(tokens, pattern.is, match[0]); // TODO + if (typeof pattern.is == "object") { + let lastMatchEnd = i; + for (let i = 1; i < match.indices.length; ++i) { + let [start, end] = match.indices[i]; + if (match.indices[i] != null) { + pushToken(tokens, pattern.is.default, text.substring(lastMatchEnd, start)); + pushToken(tokens, pattern.is.captures[i], text.substring(start, end)); + } + } + } else { + pushToken(tokens, pattern.is, match[0]); + } i = pattern.regex.lastIndex; hadMatch = true; break; diff --git a/static/syntax/javascript.json b/static/syntax/javascript.json index 06dcde9..db48e73 100644 --- a/static/syntax/javascript.json +++ b/static/syntax/javascript.json @@ -17,9 +17,9 @@ { "regex": "0[xX][0-9a-fA-F_]+n?", "is": "literal" }, { "regex": "[0-9_]+n", "is": "literal" }, { "regex": "[0-9_]+(\\.[0-9_]*([eE][-+]?[0-9_]+)?)?", "is": "literal" }, - { "regex": "'(\\'|[^'])*'", "is": "string" }, - { "regex": "\"(\\\"|[^\"])*\"", "is": "string" }, - { "regex": "`(\\`|[^`])*`", "is": "string" }, + { "regex": "'(\\\\'|[^'])*'", "is": "string" }, + { "regex": "\"(\\\\\"|[^\"])*\"", "is": "string" }, + { "regex": "`(\\\\`|[^`])*`", "is": "string" }, { "regex": "[+=/*^%<>!~|&\\.?:-]+", "is": "operator" }, { "regex": "[,;]", "is": "punct" } ],