diff --git a/.gitignore b/.gitignore
index 8165bcb..8e76096 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,11 +4,5 @@ package-lock.json
build
npm-debug.log
log.html
-tree-sitter-hcl.wasm
.env
.DS_Store
-
-fuzz/fuzzer
-fuzz/*.o
-fuzz/*.a
-fuzz/fuzz-*.log
diff --git a/README.md b/README.md
index 926382e..42390aa 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
tree-sitter grammar for the [HCL](https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md) language
+## Try It Out
+
+Try the parser in the [playground](https://michahoffmann.github.io/tree-sitter-hcl/)
+
## Example
Highlighting `example/example.hcl`:
@@ -30,3 +34,6 @@ Total parses: 1892; successful parses: 1892; failed parses: 0; success percentag
See the [fuzzing repo for this parser](https://github.com/MichaHoffmann/tree-sitter-hcl-fuzz)
+## Attributions
+
+Pages were copied from https://github.com/m-novikov/tree-sitter-sql
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..43479e8
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,83 @@
+
+
+
+ ${" ".repeat(
+ indentLevel,
+ )}${fieldName}
${displayName} [${
+ start.row
+ }, ${start.column}] - [${end.row}, ${end.column}])`;
+ finishedRow = true;
+ }
+
+ if (cursor.gotoFirstChild()) {
+ visitedChildren = false;
+ indentLevel++;
+ } else {
+ visitedChildren = true;
+ }
+ }
+ }
+ if (finishedRow) {
+ row += "
";
+ rows.push(row);
+ }
+
+ cursor.delete();
+ cluster.update(rows);
+ treeRows = rows;
+ isRendering--;
+ handleCursorMovement();
+ }
+
+ function runTreeQuery(_, startRow, endRow) {
+ if (endRow == null) {
+ const viewport = codeEditor.getViewport();
+ startRow = viewport.from;
+ endRow = viewport.to;
+ }
+
+ codeEditor.operation(() => {
+ const marks = codeEditor.getAllMarks();
+ marks.forEach(m => m.clear());
+
+ if (tree && query) {
+ const captures = query.captures(
+ tree.rootNode,
+ { row: startRow, column: 0 },
+ { row: endRow, column: 0 },
+ );
+ let lastNodeId;
+ for (const { name, node } of captures) {
+ if (node.id === lastNodeId) continue;
+ lastNodeId = node.id;
+ const { startPosition, endPosition } = node;
+ codeEditor.markText(
+ { line: startPosition.row, ch: startPosition.column },
+ { line: endPosition.row, ch: endPosition.column },
+ {
+ inclusiveLeft: true,
+ inclusiveRight: true,
+ css: `color: ${colorForCaptureName(name)}`,
+ },
+ );
+ }
+ }
+ });
+ }
+
+ function handleQueryChange() {
+ if (query) {
+ query.delete();
+ query.deleted = true;
+ query = null;
+ }
+
+ queryEditor.operation(() => {
+ queryEditor.getAllMarks().forEach(m => m.clear());
+ if (!queryCheckbox.checked) return;
+
+ const queryText = queryEditor.getValue();
+
+ try {
+ query = parser.getLanguage().query(queryText);
+ let match;
+
+ let row = 0;
+ queryEditor.eachLine(line => {
+ while ((match = CAPTURE_REGEX.exec(line.text))) {
+ queryEditor.markText(
+ { line: row, ch: match.index },
+ { line: row, ch: match.index + match[0].length },
+ {
+ inclusiveLeft: true,
+ inclusiveRight: true,
+ css: `color: ${colorForCaptureName(match[1])}`,
+ },
+ );
+ }
+ row++;
+ });
+ } catch (error) {
+ const startPosition = queryEditor.posFromIndex(error.index);
+ const endPosition = {
+ line: startPosition.line,
+ ch: startPosition.ch + (error.length || Infinity),
+ };
+
+ if (error.index === queryText.length) {
+ if (startPosition.ch > 0) {
+ startPosition.ch--;
+ } else if (startPosition.row > 0) {
+ startPosition.row--;
+ startPosition.column = Infinity;
+ }
+ }
+
+ queryEditor.markText(startPosition, endPosition, {
+ className: "query-error",
+ inclusiveLeft: true,
+ inclusiveRight: true,
+ attributes: { title: error.message },
+ });
+ }
+ });
+
+ runTreeQuery();
+ saveQueryState();
+ }
+
+ function handleCursorMovement() {
+ if (isRendering) return;
+
+ const selection = codeEditor.getDoc().listSelections()[0];
+ let start = { row: selection.anchor.line, column: selection.anchor.ch };
+ let end = { row: selection.head.line, column: selection.head.ch };
+ if (
+ start.row > end.row ||
+ (start.row === end.row && start.column > end.column)
+ ) {
+ let swap = end;
+ end = start;
+ start = swap;
+ }
+ const node = tree.rootNode.namedDescendantForPosition(start, end);
+ if (treeRows) {
+ if (treeRowHighlightedIndex !== -1) {
+ const row = treeRows[treeRowHighlightedIndex];
+ if (row)
+ treeRows[treeRowHighlightedIndex] = row.replace(
+ "highlighted",
+ "plain",
+ );
+ }
+ treeRowHighlightedIndex = treeRows.findIndex(row =>
+ row.includes(`data-id=${node.id}`),
+ );
+ if (treeRowHighlightedIndex !== -1) {
+ const row = treeRows[treeRowHighlightedIndex];
+ if (row)
+ treeRows[treeRowHighlightedIndex] = row.replace(
+ "plain",
+ "highlighted",
+ );
+ }
+ cluster.update(treeRows);
+ const lineHeight = cluster.options.item_height;
+ const scrollTop = outputContainerScroll.scrollTop;
+ const containerHeight = outputContainerScroll.clientHeight;
+ const offset = treeRowHighlightedIndex * lineHeight;
+ if (scrollTop > offset - 20) {
+ $(outputContainerScroll).animate({ scrollTop: offset - 20 }, 150);
+ } else if (scrollTop < offset + lineHeight + 40 - containerHeight) {
+ $(outputContainerScroll).animate(
+ { scrollTop: offset - containerHeight + 40 },
+ 150,
+ );
+ }
+ }
+ }
+
+ function handleCreateIssue() {
+ const queryText = codeEditor.getValue();
+ const outputText = outputContainer.innerText;
+ const title = `Error parsing SQL`;
+ const body = `Error when parsing the following SQL:
+\`\`\`
+${queryText}
+\`\`\`
+Error:
+\`\`\`
+${outputText}
+\`\`\``;
+ const queryParams = `title=${encodeURIComponent(
+ title,
+ )}&body=${encodeURIComponent(body)}`;
+ const url = `https://github.com/m-novikov/tree-sitter-sql/issues/new?${queryParams}`;
+ window.open(url);
+ }
+
+ function handleTreeClick(event) {
+ if (event.target.tagName === "A") {
+ event.preventDefault();
+ const [startRow, startColumn, endRow, endColumn] =
+ event.target.dataset.range.split(",").map(n => parseInt(n));
+ codeEditor.focus();
+ codeEditor.setSelection(
+ { line: startRow, ch: startColumn },
+ { line: endRow, ch: endColumn },
+ );
+ }
+ }
+
+ function handleLoggingChange() {
+ if (loggingCheckbox.checked) {
+ parser.setLogger((message, lexing) => {
+ if (lexing) {
+ console.log(" ", message);
+ } else {
+ console.log(message);
+ }
+ });
+ } else {
+ parser.setLogger(null);
+ }
+ }
+
+ function handleQueryEnableChange() {
+ if (queryCheckbox.checked) {
+ queryContainer.style.visibility = "";
+ queryContainer.style.position = "";
+ } else {
+ queryContainer.style.visibility = "hidden";
+ queryContainer.style.position = "absolute";
+ }
+ handleQueryChange();
+ }
+
+ function treeEditForEditorChange(change) {
+ const oldLineCount = change.removed.length;
+ const newLineCount = change.text.length;
+ const lastLineLength = change.text[newLineCount - 1].length;
+
+ const startPosition = { row: change.from.line, column: change.from.ch };
+ const oldEndPosition = { row: change.to.line, column: change.to.ch };
+ const newEndPosition = {
+ row: startPosition.row + newLineCount - 1,
+ column:
+ newLineCount === 1
+ ? startPosition.column + lastLineLength
+ : lastLineLength,
+ };
+
+ const startIndex = codeEditor.indexFromPos(change.from);
+ let newEndIndex = startIndex + newLineCount - 1;
+ let oldEndIndex = startIndex + oldLineCount - 1;
+ for (let i = 0; i < newLineCount; i++) newEndIndex += change.text[i].length;
+ for (let i = 0; i < oldLineCount; i++)
+ oldEndIndex += change.removed[i].length;
+
+ return {
+ startIndex,
+ oldEndIndex,
+ newEndIndex,
+ startPosition,
+ oldEndPosition,
+ newEndPosition,
+ };
+ }
+
+ function colorForCaptureName(capture) {
+ const id = query.captureNames.indexOf(capture);
+ return COLORS_BY_INDEX[id % COLORS_BY_INDEX.length];
+ }
+
+ function storageGetItem(lookupKey) {
+ try {
+ return localStorage.getItem(lookupKey);
+ } catch {
+ return null;
+ }
+ }
+
+ function storageSetItem(lookupKey, value) {
+ try {
+ return localStorage.setIem(lookupKey, value);
+ } catch {}
+ }
+
+ function loadState() {
+ const language = storageGetItem("language");
+ const sourceCode = storageGetItem("sourceCode");
+ const query = storageGetItem("query");
+ const queryEnabled = storageGetItem("queryEnabled");
+ if (language != null && sourceCode != null && query != null) {
+ queryInput.value = query;
+ codeInput.value = sourceCode;
+ queryCheckbox.checked = queryEnabled === "true";
+ }
+ }
+
+ function saveState() {
+ storageSetItem("sourceCode", codeEditor.getValue());
+ saveQueryState();
+ }
+
+ function saveQueryState() {
+ storageSetItem("queryEnabled", queryCheckbox.checked);
+ storageSetItem("query", queryEditor.getValue());
+ }
+
+ function debounce(func, wait, immediate) {
+ var timeout;
+ return function () {
+ var context = this,
+ args = arguments;
+ var later = function () {
+ timeout = null;
+ if (!immediate) func.apply(context, args);
+ };
+ var callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) func.apply(context, args);
+ };
+ }
+})();
+
diff --git a/docs/tree-sitter-hcl.wasm b/docs/tree-sitter-hcl.wasm
new file mode 100755
index 0000000..f59ca3c
Binary files /dev/null and b/docs/tree-sitter-hcl.wasm differ
diff --git a/docs/vendor/tree-sitter.js b/docs/vendor/tree-sitter.js
new file mode 100644
index 0000000..81ee7f7
--- /dev/null
+++ b/docs/vendor/tree-sitter.js
@@ -0,0 +1 @@
+var Module=void 0!==Module?Module:{},TreeSitter=function(){var e,t="object"==typeof window?{currentScript:window.document.currentScript}:null;class Parser{constructor(){this.initialize()}initialize(){throw new Error("cannot construct a Parser before calling `init()`")}static init(r){return e||(Module=Object.assign({},Module,r),e=new Promise(e=>{var r,n={};for(r in Module)Module.hasOwnProperty(r)&&(n[r]=Module[r]);var s,o,_=[],a="./this.program",u=function(e,t){throw t},i=!1,l=!1;i="object"==typeof window,l="function"==typeof importScripts,s="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,o=!i&&!s&&!l;var d,c,m,f,p,h="";s?(h=l?require("path").dirname(h)+"/":__dirname+"/",d=function(e,t){return f||(f=require("fs")),p||(p=require("path")),e=p.normalize(e),f.readFileSync(e,t?null:"utf8")},m=function(e){var t=d(e,!0);return t.buffer||(t=new Uint8Array(t)),k(t.buffer),t},process.argv.length>1&&(a=process.argv[1].replace(/\\/g,"/")),_=process.argv.slice(2),"undefined"!=typeof module&&(module.exports=Module),u=function(e){process.exit(e)},Module.inspect=function(){return"[Emscripten Module object]"}):o?("undefined"!=typeof read&&(d=function(e){return read(e)}),m=function(e){var t;return"function"==typeof readbuffer?new Uint8Array(readbuffer(e)):(k("object"==typeof(t=read(e,"binary"))),t)},"undefined"!=typeof scriptArgs?_=scriptArgs:void 0!==arguments&&(_=arguments),"function"==typeof quit&&(u=function(e){quit(e)}),"undefined"!=typeof print&&("undefined"==typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!=typeof printErr?printErr:print)):(i||l)&&(l?h=self.location.href:void 0!==t&&t.currentScript&&(h=t.currentScript.src),h=0!==h.indexOf("blob:")?h.substr(0,h.lastIndexOf("/")+1):"",d=function(e){var t=new XMLHttpRequest;return t.open("GET",e,!1),t.send(null),t.responseText},l&&(m=function(e){var t=new XMLHttpRequest;return t.open("GET",e,!1),t.responseType="arraybuffer",t.send(null),new Uint8Array(t.response)}),c=function(e,t,r){var n=new XMLHttpRequest;n.open("GET",e,!0),n.responseType="arraybuffer",n.onload=function(){200==n.status||0==n.status&&n.response?t(n.response):r()},n.onerror=r,n.send(null)});Module.print||console.log.bind(console);var g=Module.printErr||console.warn.bind(console);for(r in n)n.hasOwnProperty(r)&&(Module[r]=n[r]);n=null,Module.arguments&&(_=Module.arguments),Module.thisProgram&&(a=Module.thisProgram),Module.quit&&(u=Module.quit);var w=16;var y,M=[];function b(e,t){if(!y){y=new WeakMap;for(var r=0;r