Changes to particles.scroll.pub

ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 3: title Particle Syntax
+ editButton /edit.html
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 41: FAQ | Twitter
+ scrollVersionLink
+
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 41: FAQ | Twitter
- center
- This website published by Breck's Lab.
- https://breckyunits.com/lab.html Breck's Lab
-
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 25: inlineCss style.css
-

-

-

-

-

- FAQ |
-

-

- This website published by Breck's Lab.
-

+
+ center
+ Scroll | Scroll SDK
+ https://scroll.pub Scroll
+ https://sdk.scroll.pub Scroll SDK
+
+ center
+ Parsers Designer | Particles Sandbox
+ https://sdk.scroll.pub/designer Parsers Designer
+ https://sdk.scroll.pub/sandbox Particles Sandbox
+
+ center
+ FAQ | Twitter
+ https://faq.scroll.pub FAQ
+ https://twitter.com/breckyunits Twitter
+
+ center
+ This website published by Breck's Lab.
+ https://breckyunits.com/lab.html Breck's Lab
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 38: inlineCss style.css
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

Particles

-
-

-
-
-

-
-

-

-

-

-

- FAQ |
-

-

- This website published by Breck's Lab.
-

-
-
-
-
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated script.js
script.js
Changed around line 68: aFutureWithoutCompilers
- }
+ }
+
+ start()
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 5: metaTags
- inlineJs script.js
Changed around line 42: inlineJs script.js
-
-
+
+ inlineJs script.js
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 41: inlineJs script.js
-
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 6: inlineCss style.css
-
-
+
+
Changed around line 46: inlineJs script.js
-
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated script.js
script.js
Changed around line 1
+
+ const data = new jtree.TreeNode(`helloWorld3D
+ title Hello world
+ description Particle Syntax (Particles) is a simple new syntax for writing code and data. Particles brings code to life, by giving it physical dimensions. Particles is code you could hold.
+ noSyntaxChars
+ title No Syntax
+ description Say goodbye to unnecessary syntax characters! Particles drops those. The Particles Experiment is to see if we can ditch all of that syntax for easier parsing without losing functionality.
+ createLanguages
+ title Create New Languages
+ description Particles is a basic building block that you can build higher level languages on top of. Try it now.
+ paper
+ title Works Offline
+ description You can use Particles with pen and paper too. Graph paper works great. For example, healthcare providers could write notes directly in Particles. This can make it easier for lab scientists to analyze that data later on.
+ programWithPictures
+ title Program With Pictures
+ description Particles can easily support non-traditional programming language designs. For example, you can design new languages that use pictures instead of words.
+ webForms
+ title Web Forms Reinvented
+ description Particles could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Particles documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
+ cleanData
+ title Bug Free Data
+ description We no longer need to store our data in error prone CSV, XML, or JSON. Particles gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
+ programsAreAlsoSpreadsheets
+ title Programs Are Spreadsheets
+ description With Particles, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in languages built on Particles.
+ visualProgramming
+ title Visual Programming
+ description Particles might help make true visual programming a reality. Visual editors generate clean Particles code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
+ apis
+ title A New Wave of APIs
+ description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Particles could do a similar thing. Compared to JSON APIs, Particles APIs could be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
+ complexityCanBeCounted
+ title We Can Now Count Complexity
+ description Particles allows you to easily count the complexity of a given piece of code or data. With Particles, you can objectively decide which thing is simpler than another. You can count complexity in "Particle Units" like particles, subparticles, atoms, callbacks and bits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
+ aFutureWithoutCompilers
+ title A Future Without Compilers?
+ description In the decades ahead, Particles could change the way we think about computers. Particles is like a 2-dimensional binary and could be used at the lowest levels of computing. We could see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
+ const changeImage = event => {
+ if (!event.target.href) return true
+ event.preventDefault()
+ const id = event.target.value
+ loadSlide(id)
+ location.hash = id
+ }
+ const loadSlide = id => {
+ const node = data.getNode(id)
+ if (!node) return true
+ const sel = document.getElementsByClassName("selected")[0]
+ if (sel) sel.classList.remove("selected")
+ document.getElementById(id + "Link").classList.add("selected")
+ document.getElementById("heroImage").src = `demos/${id}.svg`
+ document.getElementById("caption").innerHTML = node.get("description") || ""
+ }
+ const start = () => {
+ data.forEach(node => {
+ const id = node.getLine()
+ const aTag = document.createElement("A")
+ aTag.appendChild(document.createTextNode(node.get("title")))
+ aTag.href = `demos/${id}.svg`
+ aTag.value = id
+ aTag.id = id + "Link"
+ document.getElementById("slideHolder").appendChild(aTag)
+ })
+
+ const url = new URL(document.location.href)
+ if (url.hash && url.hash.length > 1) {
+ loadSlide(url.hash.substr(1))
+ } else {
+ loadSlide("helloWorld3D")
+ }
+ }
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated script.js
script.js
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 1
+ title Particle Syntax
- content.html
+
+ lib/jtree.browser.js
+ inlineJs script.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Particles

+
+

+
+
+

+
+

+

+

+

+

+ FAQ |
+

+

+ This website published by Breck's Lab.
+

+
+
+
+
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 1
+ metaTags
+ inlineCss style.css
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated style.css
style.css
Changed around line 1
+
+ html {
+ box-sizing: border-box;
+ }
+ body {
+ margin: auto;
+ text-align: center;
+ width: 1200px;
+ }
+ *,
+ *:before,
+ *:after {
+ box-sizing: inherit;
+ }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
+ font-weight: 300;
+ margin: auto;
+ }
+ body h1 {
+ font-weight: 300;
+ }
+ a {
+ color: rgba(65, 124, 129, 1);
+ text-decoration: none;
+ }
+ a:hover {
+ color: rgba(14, 72, 78, 1);
+ }
+
+ #logo {
+ width: 100px;
+ }
+ .libraries a,
+ #slideHolder a {
+ display: block;
+ }
+ body {
+ background: #eee;
+ color: rgba(1, 47, 52, 1);
+ }
+ .primaryColorLightest {
+ color: rgba(104, 151, 155, 1);
+ }
+ .primaryColorLight {
+ color: rgba(65, 124, 129, 1);
+ }
+ .primaryColorMid {
+ color: rgba(35, 98, 103, 1);
+ }
+ .primaryColorDark {
+ color: rgba(14, 72, 78, 1);
+ }
+ .primaryColorDarkest {
+ color: rgba(1, 47, 52, 1);
+ }
+ .section {
+ padding: 20px;
+ }
+ #mainTable {
+ margin-top: 15px;
+ margin-bottom: 30px;
+ }
+ .leftCol {
+ width: 290px;
+ text-align: left;
+ border-right: 1px solid #bbb;
+ }
+ .rightCol {
+ text-align: left;
+ width: 505px;
+ padding-left: 30px;
+ }
+ #heroHolder {
+ height: 505px;
+ text-align: center;
+ }
+ #heroImage {
+ width: 500px;
+ height: 500px;
+ }
+ #slideHolder a.selected {
+ font-weight: bold;
+ }
+ #social a {
+ opacity: 0.8;
+ }
+ #social a:hover {
+ opacity: 0.9;
+ }
+ #social a:click {
+ opacity: 1;
+ }
+ #caption {
+ padding-right: 10px;
+ text-align: left;
+ height: 5rem;
+ }
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated style.css
style.css
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated content.html
content.html
Changed around line 3
- html {
- box-sizing: border-box;
- }
- body {
- margin: auto;
- text-align: center;
- width: 1200px;
- }
- *,
- *:before,
- *:after {
- box-sizing: inherit;
- }
- body {
- font-family: -apple-system, BlinkMacSystemFont, sans-serif;
- font-weight: 300;
- margin: auto;
- }
- body h1 {
- font-weight: 300;
- }
- a {
- color: rgba(65, 124, 129, 1);
- text-decoration: none;
- }
- a:hover {
- color: rgba(14, 72, 78, 1);
- }
-
- #logo {
- width: 100px;
- }
- .libraries a,
- #slideHolder a {
- display: block;
- }
- body {
- background: #eee;
- color: rgba(1, 47, 52, 1);
- }
- .primaryColorLightest {
- color: rgba(104, 151, 155, 1);
- }
- .primaryColorLight {
- color: rgba(65, 124, 129, 1);
- }
- .primaryColorMid {
- color: rgba(35, 98, 103, 1);
- }
- .primaryColorDark {
- color: rgba(14, 72, 78, 1);
- }
- .primaryColorDarkest {
- color: rgba(1, 47, 52, 1);
- }
- .section {
- padding: 20px;
- }
- #mainTable {
- margin-top: 15px;
- margin-bottom: 30px;
- }
- .leftCol {
- width: 290px;
- text-align: left;
- border-right: 1px solid #bbb;
- }
- .rightCol {
- text-align: left;
- width: 505px;
- padding-left: 30px;
- }
- #heroHolder {
- height: 505px;
- text-align: center;
- }
- #heroImage {
- width: 500px;
- height: 500px;
- }
- #slideHolder a.selected {
- font-weight: bold;
- }
- #social a {
- opacity: 0.8;
- }
- #social a:hover {
- opacity: 0.9;
- }
- #social a:click {
- opacity: 1;
- }
- #caption {
- padding-right: 10px;
- text-align: left;
- height: 5rem;
- }
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Changed around line 1
+ buildHtml
+
+ content.html
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Renamed index.html to content.html
content.html
ffff:72.234.190.31
ffff:72.234.190.31
3 months ago
Updated index.scroll
index.scroll
Breck Yunits
Breck Yunits
4 months ago
index.html
Changed around line 136: apis
- description Particles allows you to easily count the complexity of a given piece of code or data. With Particles, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
+ description Particles allows you to easily count the complexity of a given piece of code or data. With Particles, you can objectively decide which thing is simpler than another. You can count complexity in "Particle Units" like particles, subparticles, atoms, callbacks and bits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
Breck Yunits
Breck Yunits
4 months ago
index.html
Changed around line 106
- description Particle Syntax (Particles) is a simple new notation for writing code and data. Particles brings code to life, by giving it physical dimensions. Particles is code you could hold.
+ description Particle Syntax (Particles) is a simple new syntax for writing code and data. Particles brings code to life, by giving it physical dimensions. Particles is code you could hold.
Changed around line 211: aFutureWithoutCompilers
-
+
readme.scroll
Changed around line 5: printTitle
- This repo contains the source code for the https://notation.scroll.pub website
+ This repo contains the source code for the https://particles.scroll.pub website
testLinks.sh
Changed around line 1
- linkinator -r https://notation.scroll.pub
+ linkinator -r https://particles.scroll.pub
Breck Yunits
Breck Yunits
4 months ago
Update CNAME
CNAME
Changed around line 1
- notation.scroll.pub
+ particles.scroll.pub
Breck Yunits
Breck Yunits
4 months ago
index.html
Changed around line 101
- Particle Notation
+ Particles
- description Particle Notation is a simple new notation for writing code and data. Particle Notation brings code to life, by giving it physical dimensions. Particle Notation is code you could hold.
+ description Particle Syntax (Particles) is a simple new notation for writing code and data. Particles brings code to life, by giving it physical dimensions. Particles is code you could hold.
- description Say goodbye to unnecessary syntax characters! Particle Notation drops those. The Particle Notation Experiment is to see if we can ditch all of that syntax for easier parsing without losing functionality.
+ description Say goodbye to unnecessary syntax characters! Particles drops those. The Particles Experiment is to see if we can ditch all of that syntax for easier parsing without losing functionality.
- description Particle Notation is a basic building block that you can build higher level languages on top of. Try it now.
+ description Particles is a basic building block that you can build higher level languages on top of. Try it now.
- description You can use Particle Notation with pen and paper too. Graph paper works great. For example, healthcare providers could write notes directly in Particle Notation. This can make it easier for lab scientists to analyze that data later on.
+ description You can use Particles with pen and paper too. Graph paper works great. For example, healthcare providers could write notes directly in Particles. This can make it easier for lab scientists to analyze that data later on.
- description Particle Notation can easily support non-traditional programming language designs. For example, you can design new languages that use pictures instead of words.
+ description Particles can easily support non-traditional programming language designs. For example, you can design new languages that use pictures instead of words.
- description Particle Notation could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Particle Notation documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
+ description Particles could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Particles documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
- description We no longer need to store our data in error prone CSV, XML, or JSON. Particle Notation gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
+ description We no longer need to store our data in error prone CSV, XML, or JSON. Particles gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
- description With Particle Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in languages built on Particle Notation.
+ description With Particles, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in languages built on Particles.
- description Particle Notation might help make true visual programming a reality. Visual editors generate clean Particle Notation code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
+ description Particles might help make true visual programming a reality. Visual editors generate clean Particles code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
- description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Particle Notation could do a similar thing. Compared to JSON APIs, Particle Notation APIs could be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
+ description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Particles could do a similar thing. Compared to JSON APIs, Particles APIs could be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
- description Particle Notation allows you to easily count the complexity of a given piece of code or data. With Particle Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
+ description Particles allows you to easily count the complexity of a given piece of code or data. With Particles, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
- description In the decades ahead, Particle Notation could change the way we think about computers. Particle Notation is like a 2-dimensional binary and could be used at the lowest levels of computing. We could see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
+ description In the decades ahead, Particles could change the way we think about computers. Particles is like a 2-dimensional binary and could be used at the lowest levels of computing. We could see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
Changed around line 185: aFutureWithoutCompilers
-

Particle Notation

+

Particles

Changed around line 202: aFutureWithoutCompilers
readme.scroll
Changed around line 1
- title Particle Notation
+ title Particles
+ # Particle Syntax
+
Breck Yunits
Breck Yunits
4 months ago
index.html
Changed around line 202: aFutureWithoutCompilers
Breck Yunits
Breck Yunits
4 months ago
index.html
Changed around line 101
- Scroll Notation
+ Particle Notation
- description Scroll Notation is a simple new notation for writing code and data. Scroll Notation brings code to life, by giving it physical dimensions. Scroll Notation is code you could hold.
+ description Particle Notation is a simple new notation for writing code and data. Particle Notation brings code to life, by giving it physical dimensions. Particle Notation is code you could hold.
- description Say goodbye to unnecessary syntax characters! Scroll Notation drops those. The Scroll Notation Experiment is to see if we can ditch all of that syntax for easier parsing without losing functionality.
+ description Say goodbye to unnecessary syntax characters! Particle Notation drops those. The Particle Notation Experiment is to see if we can ditch all of that syntax for easier parsing without losing functionality.
- description Scroll Notation is a basic building block that you can build higher level languages on top of. Try it now.
+ description Particle Notation is a basic building block that you can build higher level languages on top of. Try it now.
- description You can use Scroll Notation with pen and paper too. Graph paper works great. For example, healthcare providers could write notes directly in Scroll Notation. This can make it easier for lab scientists to analyze that data later on.
+ description You can use Particle Notation with pen and paper too. Graph paper works great. For example, healthcare providers could write notes directly in Particle Notation. This can make it easier for lab scientists to analyze that data later on.
- description Scroll Notation can easily support non-traditional programming language designs. For example, you can design new languages that use pictures instead of words.
+ description Particle Notation can easily support non-traditional programming language designs. For example, you can design new languages that use pictures instead of words.
- description Scroll Notation could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Scroll Notation documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
+ description Particle Notation could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Particle Notation documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
- description We no longer need to store our data in error prone CSV, XML, or JSON. Scroll Notation gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
+ description We no longer need to store our data in error prone CSV, XML, or JSON. Particle Notation gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
- description With Scroll Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in languages built on Scroll Notation.
+ description With Particle Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in languages built on Particle Notation.
- description Scroll Notation might help make true visual programming a reality. Visual editors generate clean Scroll Notation code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
+ description Particle Notation might help make true visual programming a reality. Visual editors generate clean Particle Notation code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
- description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Scroll Notation could do a similar thing. Compared to JSON APIs, Scroll Notation APIs could be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
+ description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Particle Notation could do a similar thing. Compared to JSON APIs, Particle Notation APIs could be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
- description Scroll Notation allows you to easily count the complexity of a given piece of code or data. With Scroll Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
+ description Particle Notation allows you to easily count the complexity of a given piece of code or data. With Particle Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
- description In the decades ahead, Scroll Notation could change the way we think about computers. Scroll Notation is like a 2-dimensional binary and could be used at the lowest levels of computing. We could see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
+ description In the decades ahead, Particle Notation could change the way we think about computers. Particle Notation is like a 2-dimensional binary and could be used at the lowest levels of computing. We could see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
Changed around line 185: aFutureWithoutCompilers
-

Scroll Notation

+

Particle Notation

Changed around line 202: aFutureWithoutCompilers
readme.scroll
Changed around line 1
- title Scroll Notation
+ title Particle Notation
- ## Formerly called Tree Notation
+ ## Formerly called Scroll Notation/Tree Notation/Space/Note
Breck Yunits
Breck Yunits
7 months ago
Link fix. Thank you Zireael07
index.html
Changed around line 130: programsAreAlsoSpreadsheets
- description Scroll Notation might help make true visual programming a reality. Visual editors generate clean Scroll Notation code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
+ description Scroll Notation might help make true visual programming a reality. Visual editors generate clean Scroll Notation code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
Breck Yunits
Breck Yunits
7 months ago
checkpoint
.gitignore
Changed around line 1
+ .DS_Store
index.html
Changed around line 198: aFutureWithoutCompilers
-
-

+

-

SDKs

Breck Yunits
Breck Yunits
7 months ago
checkpoint
index.html
Changed around line 207: aFutureWithoutCompilers
Breck Yunits
Breck Yunits
7 months ago
checkpoint
index.html
Changed around line 121: programWithPictures
- description Scroll Notation could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Scroll Notation documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
+ description Scroll Notation could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Scroll Notation documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
Changed around line 136: apis
- description Scroll Notation allows you to easily count the complexity of a given piece of code or data. With Scroll Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
+ description Scroll Notation allows you to easily count the complexity of a given piece of code or data. With Scroll Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
Changed around line 211: aFutureWithoutCompilers
-
+
jtree.browser.js
Changed around line 6: class TreeUtils {
- const runOnlyTheseTest = Object.keys(testTree).filter(key => key.startsWith("_"))
- const testsToRun = runOnlyTheseTest.length ? runOnlyTheseTest : Object.keys(testTree)
+ const runOnlyTheseTest = Object.keys(testTree).filter((key) =>
+ key.startsWith("_"),
+ )
+ const testsToRun = runOnlyTheseTest.length
+ ? runOnlyTheseTest
+ : Object.keys(testTree)
- tap.test(testName, async childTest => {
+ tap.test(testName, async (childTest) => {
Changed around line 61: class TreeUtils {
- ? str
- .toLowerCase()
- .replace(reg, "")
- .replace(/ /g, "-")
+ ? str.toLowerCase().replace(reg, "").replace(/ /g, "-")
Changed around line 83: class TreeUtils {
- return index === -1 || index + 1 === values.length ? values[0] : values[index + 1]
+ return index === -1 || index + 1 === values.length
+ ? values[0]
+ : values[index + 1]
Changed around line 93: class TreeUtils {
- if (!columns) columns = arrays.map(arr => Object.keys(arr[0]))
+ if (!columns) columns = arrays.map((arr) => Object.keys(arr[0]))
- arr.forEach(row => {
+ arr.forEach((row) => {
- cols.forEach(col => (obj[col] = row[col]))
+ cols.forEach((col) => (obj[col] = row[col]))
Changed around line 116: class TreeUtils {
- const clone = list.slice(0, limit).map(item => `'${item}'`)
+ const clone = list.slice(0, limit).map((item) => `'${item}'`)
- const del = " ,|\t;^%$!#@~*&+-=_:?.{}[]()<>/".split("").find(idea => !str.includes(idea))
+ const del = " ,|\t;^%$!#@~*&+-=_:?.{}[]()<>/"
+ .split("")
+ .find((idea) => !str.includes(idea))
Changed around line 140: class TreeUtils {
- static didYouMean(str = "", options = [], caseSensitive = false, threshold = 0.4, thresholdAbsolute = 20) {
+ static didYouMean(
+ str = "",
+ options = [],
+ caseSensitive = false,
+ threshold = 0.4,
+ thresholdAbsolute = 20,
+ ) {
- if (thresholdRelative !== null && thresholdAbsolute !== null) maximumEditDistanceToBeBestMatch = Math.min(thresholdRelative, thresholdAbsolute)
- else if (thresholdRelative !== null) maximumEditDistanceToBeBestMatch = thresholdRelative
- else if (thresholdAbsolute !== null) maximumEditDistanceToBeBestMatch = thresholdAbsolute
+ if (thresholdRelative !== null && thresholdAbsolute !== null)
+ maximumEditDistanceToBeBestMatch = Math.min(
+ thresholdRelative,
+ thresholdAbsolute,
+ )
+ else if (thresholdRelative !== null)
+ maximumEditDistanceToBeBestMatch = thresholdRelative
+ else if (thresholdAbsolute !== null)
+ maximumEditDistanceToBeBestMatch = thresholdAbsolute
- const editDistance = TreeUtils._getEditDistance(str, caseSensitive ? candidate : candidate.toLowerCase(), maximumEditDistanceToBeBestMatch)
+ const editDistance = TreeUtils._getEditDistance(
+ str,
+ caseSensitive ? candidate : candidate.toLowerCase(),
+ maximumEditDistanceToBeBestMatch,
+ )
Changed around line 216: class TreeUtils {
- if (stringB.charAt(bIndex - 1) === stringA.charAt(aIndex - 1)) matrix[bIndex][aIndex] = matrix[bIndex - 1][aIndex - 1]
+ if (stringB.charAt(bIndex - 1) === stringA.charAt(aIndex - 1))
+ matrix[bIndex][aIndex] = matrix[bIndex - 1][aIndex - 1]
- matrix[bIndex - 1][aIndex] + 1
- )
+ matrix[bIndex - 1][aIndex] + 1,
+ ),
Changed around line 271: class TreeUtils {
- words.forEach(word => {
+ words.forEach((word) => {
- return Object.keys(index).map(key => {
+ return Object.keys(index).map((key) => {
- count: index[key]
+ count: index[key],
- static getRandomString(length = 30, letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")) {
+ static getRandomString(
+ length = 30,
+ letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(
+ "",
+ ),
+ ) {
- str += letters[Math.round(Math.min(Math.random() * letters.length, letters.length - 1))]
+ str +=
+ letters[
+ Math.round(
+ Math.min(Math.random() * letters.length, letters.length - 1),
+ )
+ ]
Changed around line 310: class TreeUtils {
- bit += letters[Math.round(Math.min(Math.random() * letters.length, letters.length - 1))]
+ bit +=
+ letters[
+ Math.round(
+ Math.min(Math.random() * letters.length, letters.length - 1),
+ )
+ ]
Changed around line 327: class TreeUtils {
- return function() {
+ return function () {
- if (this._tickTime === undefined) this._tickTime = Date.now() - 1000 * process.uptime()
+ if (this._tickTime === undefined)
+ this._tickTime = Date.now() - 1000 * process.uptime()
Changed around line 357: class TreeUtils {
- arr.forEach(val => (map[val] = true))
+ arr.forEach((val) => (map[val] = true))
- .replace(/[^a-zA-Z0-9]/g, sub => {
+ .replace(/[^a-zA-Z0-9]/g, (sub) => {
- Object.keys(object).forEach(key => {
+ Object.keys(object).forEach((key) => {
Changed around line 377: class TreeUtils {
- return dataTable.map(row => {
+ return dataTable.map((row) => {
- const arrayOfFns = Array.isArray(accessorOrAccessors) ? accessorOrAccessors : [accessorOrAccessors]
+ const arrayOfFns = Array.isArray(accessorOrAccessors)
+ ? accessorOrAccessors
+ : [accessorOrAccessors]
Changed around line 412: class TreeUtils {
- else if (!nodeBExtendsSomething && nodeAExtendsSomething) return nodeBFirst
+ else if (!nodeBExtendsSomething && nodeAExtendsSomething)
+ return nodeBFirst
Changed around line 454: class TreeUtils {
- TreeUtils.linkify = text => {
+ TreeUtils.linkify = (text) => {
- replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim
- replacedText = text.replace(replacePattern1, '$1')
+ replacePattern1 =
+ /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim
+ replacedText = text.replace(
+ replacePattern1,
+ '$1',
+ )
- replacedText = replacedText.replace(replacePattern2, '$1$2')
+ replacedText = replacedText.replace(
+ replacePattern2,
+ '$1$2',
+ )
- replacedText = replacedText.replace(replacePattern3, '$1')
+ replacedText = replacedText.replace(
+ replacePattern3,
+ '$1',
+ )
Changed around line 507: class AbstractNode {
- ;(function(FileFormat) {
+ ;(function (FileFormat) {
- ;(function(WhereOperators) {
+ ;(function (WhereOperators) {
Changed around line 528: var WhereOperators
- ;(function(TreeNotationConstants) {
+ ;(function (TreeNotationConstants) {
- constructor(catchAllNodeConstructor, firstWordMap = {}, regexTests = undefined) {
+ constructor(
+ catchAllNodeConstructor,
+ firstWordMap = {},
+ regexTests = undefined,
+ ) {
Changed around line 549: class Parser {
- return this._firstWordMap[this._getFirstWord(line, zi)] || this._getConstructorFromRegexTests(line) || this._getCatchAllNodeConstructor(contextNode)
+ return (
+ this._firstWordMap[this._getFirstWord(line, zi)] ||
+ this._getConstructorFromRegexTests(line) ||
+ this._getCatchAllNodeConstructor(contextNode)
+ )
Changed around line 563: class Parser {
- const hit = this._regexTests.find(test => test.regex.test(line))
+ const hit = this._regexTests.find((test) => test.regex.test(line))
Changed around line 580: class TreeNode extends AbstractNode {
- return Promise.all(this.map(child => child.execute(context)))
+ return Promise.all(this.map((child) => child.execute(context)))
Changed around line 590: class TreeNode extends AbstractNode {
- return this.map(child => child.executeSync(context))
+ return this.map((child) => child.executeSync(context))
Changed around line 612: class TreeNode extends AbstractNode {
- return this.getParent().filter(node => node !== this)
+ return this.getParent().filter((node) => node !== this)
Changed around line 628: class TreeNode extends AbstractNode {
- y: this._getYCoordinate(relativeTo)
+ y: this._getYCoordinate(relativeTo),
Changed around line 638: class TreeNode extends AbstractNode {
- this.forEach(child => {
+ this.forEach((child) => {
Changed around line 681: class TreeNode extends AbstractNode {
- return this.length ? new Set(this.getFirstWords()).size !== this.length : false
+ return this.length
+ ? new Set(this.getFirstWords()).size !== this.length
+ : false
Changed around line 706: class TreeNode extends AbstractNode {
- language.getXI().repeat(indentCount) + this.getLine(language) + (this.length ? language.getYI() + this._childrenToString(indentCount + 1, language) : "")
+ language.getXI().repeat(indentCount) +
+ this.getLine(language) +
+ (this.length
+ ? language.getYI() + this._childrenToString(indentCount + 1, language)
+ : "")
Changed around line 742: class TreeNode extends AbstractNode {
- nodeChildren: "nodeChildren"
+ nodeChildren: "nodeChildren",
- ? `${this.getYI()}` + `${this._childrenToHtml(indentCount + 1)}`
+ ? `${this.getYI()}` +
+ `${this._childrenToHtml(
+ indentCount + 1,
+ )}`
Changed around line 777: class TreeNode extends AbstractNode {
- this.getChildren().forEach(node => {
- fields.forEach(field => {
+ this.getChildren().forEach((node) => {
+ fields.forEach((field) => {
- getBiDirectionalMaps(propertyNameOrFn, propertyNameOrFn2 = node => node.getWord(0)) {
+ getBiDirectionalMaps(
+ propertyNameOrFn,
+ propertyNameOrFn2 = (node) => node.getWord(0),
+ ) {
- const value1 = is1Str ? node.get(propertyNameOrFn) : propertyNameOrFn(node, index, children)
- const value2 = is2Str ? node.get(propertyNameOrFn2) : propertyNameOrFn2(node, index, children)
+ const value1 = is1Str
+ ? node.get(propertyNameOrFn)
+ : propertyNameOrFn(node, index, children)
+ const value2 = is2Str
+ ? node.get(propertyNameOrFn2)
+ : propertyNameOrFn2(node, index, children)
Changed around line 819: class TreeNode extends AbstractNode {
- this.getWords()
- .slice(0, wordIndex)
- .join(this.getZI()).length +
+ this.getWords().slice(0, wordIndex).join(this.getZI()).length +
Changed around line 840: class TreeNode extends AbstractNode {
- word: word
+ word: word,
- node.getWordBoundaryIndices().forEach(index => {
+ node.getWordBoundaryIndices().forEach((index) => {
- x: index
+ x: index,
Changed around line 868: class TreeNode extends AbstractNode {
- this.getWords().forEach(word => {
+ this.getWords().forEach((word) => {
Changed around line 886: class TreeNode extends AbstractNode {
- word.split("").forEach(letter => {
+ word.split("").forEach((letter) => {
Changed around line 898: class TreeNode extends AbstractNode {
- errs.forEach(err => errors.push(err))
+ errs.forEach((err) => errors.push(err))
Changed around line 924: class TreeNode extends AbstractNode {
- return (content ? content : "") + (this.length ? this.getYI() + this._childrenToString() : "")
+ return (
+ (content ? content : "") +
+ (this.length ? this.getYI() + this._childrenToString() : "")
+ )
Changed around line 956: class TreeNode extends AbstractNode {
- clone.forEach(node => {
+ clone.forEach((node) => {
- cols.forEach(col => {
+ cols.forEach((col) => {
Changed around line 969: class TreeNode extends AbstractNode {
- return this.getParent()._getFirstWordPath(relativeTo) + this.getXI() + this.getFirstWord()
+ return (
+ this.getParent()._getFirstWordPath(relativeTo) +
+ this.getXI() +
+ this.getFirstWord()
+ )
Changed around line 1001: class TreeNode extends AbstractNode {
- .map((word, index) => `${TreeUtils.stripHtml(word)}`)
+ .map(
+ (word, index) =>
+ `${TreeUtils.stripHtml(word)}`,
+ )
- return this.length ? `${indentCount === -1 ? "" : "\n"}${this._childrenToXml(indentCount > -1 ? indentCount + 2 : -1)}${" ".repeat(indentCount)}` : ""
+ return this.length
+ ? `${indentCount === -1 ? "" : "\n"}${this._childrenToXml(
+ indentCount > -1 ? indentCount + 2 : -1,
+ )}${" ".repeat(indentCount)}`
+ : ""
- return `${indent}<${tag}>${this._getXmlContent(indentCount)}${indentCount === -1 ? "" : "\n"}`
+ return `${indent}<${tag}>${this._getXmlContent(indentCount)}${
+ indentCount === -1 ? "" : "\n"
+ }`
Changed around line 1029: class TreeNode extends AbstractNode {
- const tupleValue = hasChildrenNoContent ? this.toObject() : hasContentAndHasChildren ? this.getContentWithChildren() : content
+ const tupleValue = hasChildrenNoContent
+ ? this.toObject()
+ : hasContentAndHasChildren
+ ? this.getContentWithChildren()
+ : content
Changed around line 1049: class TreeNode extends AbstractNode {
- .map(child => child.toString())
- .join("\n")
+ .map((child) => child.toString())
+ .join("\n"),
Changed around line 1061: class TreeNode extends AbstractNode {
- return this.getTopDownArray().find(node => node._hasColumns(columns))
+ return this.getTopDownArray().find((node) => node._hasColumns(columns))
- return this.find(node => node.getWord(index) === name)
+ return this.find((node) => node.getWord(index) === name)
- return this.filter(node => node.getWord(index) === name)
+ return this.filter((node) => node.getWord(index) === name)
- this.forEach(node => {
+ this.forEach((node) => {
- columnNames.forEach(name => {
+ columnNames.forEach((name) => {
Changed around line 1094: class TreeNode extends AbstractNode {
- const fn = node => {
+ const fn = (node) => {
- else if (operator === WhereOperators.notEqual) return fixedValue !== typedCell
- else if (operator === WhereOperators.includes) return typedCell !== undefined && typedCell.includes(fixedValue)
- else if (operator === WhereOperators.doesNotInclude) return typedCell === undefined || !typedCell.includes(fixedValue)
- else if (operator === WhereOperators.greaterThan) return typedCell > fixedValue
- else if (operator === WhereOperators.lessThan) return typedCell < fixedValue
- else if (operator === WhereOperators.greaterThanOrEqual) return typedCell >= fixedValue
- else if (operator === WhereOperators.lessThanOrEqual) return typedCell <= fixedValue
+ else if (operator === WhereOperators.notEqual)
+ return fixedValue !== typedCell
+ else if (operator === WhereOperators.includes)
+ return typedCell !== undefined && typedCell.includes(fixedValue)
+ else if (operator === WhereOperators.doesNotInclude)
+ return typedCell === undefined || !typedCell.includes(fixedValue)
+ else if (operator === WhereOperators.greaterThan)
+ return typedCell > fixedValue
+ else if (operator === WhereOperators.lessThan)
+ return typedCell < fixedValue
+ else if (operator === WhereOperators.greaterThanOrEqual)
+ return typedCell >= fixedValue
+ else if (operator === WhereOperators.lessThanOrEqual)
+ return typedCell <= fixedValue
- else if (operator === WhereOperators.in && isArray) return fixedValue.includes(typedCell)
- else if (operator === WhereOperators.notIn && isArray) return !fixedValue.includes(typedCell)
+ else if (operator === WhereOperators.in && isArray)
+ return fixedValue.includes(typedCell)
+ else if (operator === WhereOperators.notIn && isArray)
+ return !fixedValue.includes(typedCell)
- this.filter(fn).forEach(node => {
+ this.filter(fn).forEach((node) => {
- return this.filter(node => node.has(firstWord))
+ return this.filter((node) => node.has(firstWord))
Changed around line 1139: class TreeNode extends AbstractNode {
- .forEach(node => {
+ .forEach((node) => {
Changed around line 1150: class TreeNode extends AbstractNode {
- this.forEach(child => {
+ this.forEach((child) => {
Changed around line 1161: class TreeNode extends AbstractNode {
- Object.values(levels).forEach(level => {
- level.forEach(item => arr.push(item))
+ Object.values(levels).forEach((level) => {
+ level.forEach((item) => arr.push(item))
- this.getTopDownArray().forEach(node => {
+ this.getTopDownArray().forEach((node) => {
Changed around line 1183: class TreeNode extends AbstractNode {
- return this.map(node => node.getLine())
+ return this.map((node) => node.getLine())
Changed around line 1196: class TreeNode extends AbstractNode {
- if (typeof indexOrIndexArray === "number") return this._nodeAt(indexOrIndexArray)
- if (indexOrIndexArray.length === 1) return this._nodeAt(indexOrIndexArray[0])
+ if (typeof indexOrIndexArray === "number")
+ return this._nodeAt(indexOrIndexArray)
+ if (indexOrIndexArray.length === 1)
+ return this._nodeAt(indexOrIndexArray[0])
Changed around line 1207: class TreeNode extends AbstractNode {
- this.forEach(node => {
+ this.forEach((node) => {
Changed around line 1221: class TreeNode extends AbstractNode {
- return this.map(node => node._toHtml(indentCount)).join(joinBy)
+ return this.map((node) => node._toHtml(indentCount)).join(joinBy)
- return this.map(node => node.toString(indentCount, language)).join(language.getYI())
+ return this.map((node) => node.toString(indentCount, language)).join(
+ language.getYI(),
+ )
Changed around line 1236: class TreeNode extends AbstractNode {
- return this.map(child => child.compile()).join(this._getChildJoinCharacter())
+ return this.map((child) => child.compile()).join(
+ this._getChildJoinCharacter(),
+ )
Changed around line 1247: class TreeNode extends AbstractNode {
- tree: tree => tree.toString(),
- csv: tree => tree.toCsv(),
- tsv: tree => tree.toTsv()
+ tree: (tree) => tree.toString(),
+ csv: (tree) => tree.toCsv(),
+ tsv: (tree) => tree.toTsv(),
- if (listTag && indentLevel > 1) prefix = " ".repeat(indentLevel - 2) + listTag + " "
- return prefix + `${this.getFirstWord()}:` + (this.getContent() ? " " + this.getContent() : "")
+ if (listTag && indentLevel > 1)
+ prefix = " ".repeat(indentLevel - 2) + listTag + " "
+ return (
+ prefix +
+ `${this.getFirstWord()}:` +
+ (this.getContent() ? " " + this.getContent() : "")
+ )
Changed around line 1283: class TreeNode extends AbstractNode {
- if (indentLevel > 1) return children.join("\n").replace(" ".repeat(indentLevel), " ".repeat(indentLevel - 2) + "- ")
+ if (indentLevel > 1)
+ return children
+ .join("\n")
+ .replace(" ".repeat(indentLevel), " ".repeat(indentLevel - 2) + "- ")
Changed around line 1294: class TreeNode extends AbstractNode {
- return this.map(node => node._toYamlListElement(indentLevel + 2))
+ return this.map((node) => node._toYamlListElement(indentLevel + 2))
Changed around line 1302: class TreeNode extends AbstractNode {
- return this.map(node => node._toYamlAssociativeArrayElement(indentLevel))
+ return this.map((node) => node._toYamlAssociativeArrayElement(indentLevel))
Changed around line 1311: class TreeNode extends AbstractNode {
- firstWordPath.forEach(path => (map[path] = true))
- return this.getTopDownArray().filter(node => {
+ firstWordPath.forEach((path) => (map[path] = true))
+ return this.getTopDownArray().filter((node) => {
Changed around line 1322: class TreeNode extends AbstractNode {
- return this.map(node => node.get(path))
+ return this.map((node) => node.get(path))
- .forEach(node => {
+ .forEach((node) => {
Changed around line 1347: class TreeNode extends AbstractNode {
- return this.filter(node => node.getFirstWord() === globPath)
+ return this.filter((node) => node.getFirstWord() === globPath)
- const matchingNodes = current === "*" ? this.getChildren() : this.filter(child => child.getFirstWord() === current)
- return [].concat.apply([], matchingNodes.map(node => node._getNodesByGlobPath(rest)))
+ const matchingNodes =
+ current === "*"
+ ? this.getChildren()
+ : this.filter((child) => child.getFirstWord() === current)
+ return [].concat.apply(
+ [],
+ matchingNodes.map((node) => node._getNodesByGlobPath(rest)),
+ )
Changed around line 1378: class TreeNode extends AbstractNode {
- return next === length ? parent._getChildren()[0] : parent._getChildren()[next]
+ return next === length
+ ? parent._getChildren()[0]
+ : parent._getChildren()[next]
Changed around line 1388: class TreeNode extends AbstractNode {
- return prev === -1 ? parent._getChildren()[length - 1] : parent._getChildren()[prev]
+ return prev === -1
+ ? parent._getChildren()[length - 1]
+ : parent._getChildren()[prev]
- this.forEach(node => {
+ this.forEach((node) => {
- node.forEach(node => {
+ node.forEach((node) => {
- const ancestorNodes = this._getAncestorNodes((node, id) => node._getNodesByColumn(0, id), node => node.get(key), this)
+ const ancestorNodes = this._getAncestorNodes(
+ (node, id) => node._getNodesByColumn(0, id),
+ (node) => node.get(key),
+ this,
+ )
- getAncestorNodesByInheritanceViaColumnIndices(thisColumnNumber, extendsColumnNumber) {
- const ancestorNodes = this._getAncestorNodes((node, id) => node._getNodesByColumn(thisColumnNumber, id), node => node.getWord(extendsColumnNumber), this)
+ getAncestorNodesByInheritanceViaColumnIndices(
+ thisColumnNumber,
+ extendsColumnNumber,
+ ) {
+ const ancestorNodes = this._getAncestorNodes(
+ (node, id) => node._getNodesByColumn(thisColumnNumber, id),
+ (node) => node.getWord(extendsColumnNumber),
+ this,
+ )
- _getAncestorNodes(getPotentialParentNodesByIdFn, getParentIdFn, cannotContainNode) {
+ _getAncestorNodes(
+ getPotentialParentNodesByIdFn,
+ getParentIdFn,
+ cannotContainNode,
+ ) {
- const potentialParentNodes = getPotentialParentNodesByIdFn(this.getParent(), parentId)
- if (!potentialParentNodes.length) throw new Error(`"${this.getLine()} tried to extend "${parentId}" but "${parentId}" not found.`)
- if (potentialParentNodes.length > 1) throw new Error(`Invalid inheritance family tree. Multiple unique ids found for "${parentId}"`)
+ const potentialParentNodes = getPotentialParentNodesByIdFn(
+ this.getParent(),
+ parentId,
+ )
+ if (!potentialParentNodes.length)
+ throw new Error(
+ `"${this.getLine()} tried to extend "${parentId}" but "${parentId}" not found.`,
+ )
+ if (potentialParentNodes.length > 1)
+ throw new Error(
+ `Invalid inheritance family tree. Multiple unique ids found for "${parentId}"`,
+ )
- if (parentNode === cannotContainNode) throw new Error(`Loop detected between '${this.getLine()}' and '${parentNode.getLine()}'`)
- const ancestorNodes = parentNode._getAncestorNodes(getPotentialParentNodesByIdFn, getParentIdFn, cannotContainNode)
+ if (parentNode === cannotContainNode)
+ throw new Error(
+ `Loop detected between '${this.getLine()}' and '${parentNode.getLine()}'`,
+ )
+ const ancestorNodes = parentNode._getAncestorNodes(
+ getPotentialParentNodesByIdFn,
+ getParentIdFn,
+ cannotContainNode,
+ )
Changed around line 1474: class TreeNode extends AbstractNode {
- const types = header.map(i => "int")
- matrix.forEach(row => {
+ const types = header.map((i) => "int")
+ matrix.forEach((row) => {
Changed around line 1493: class TreeNode extends AbstractNode {
- string: str => str,
+ string: (str) => str,
- int: parseInt
+ int: parseInt,
- const cellFn = (cellValue, rowIndex, columnIndex) => (rowIndex ? parsers[types[columnIndex]](cellValue) : cellValue)
+ const cellFn = (cellValue, rowIndex, columnIndex) =>
+ rowIndex ? parsers[types[columnIndex]](cellValue) : cellValue
- const cellFn = (str, row, column) => (!str.toString().match(regex) ? str : `"` + str.replace(/\"/g, `""`) + `"`)
+ const cellFn = (str, row, column) =>
+ !str.toString().match(regex) ? str : `"` + str.replace(/\"/g, `""`) + `"`
- this.forEach(child => {
+ this.forEach((child) => {
- columns.forEach(col => {
+ columns.forEach((col) => {
Changed around line 1522: class TreeNode extends AbstractNode {
- const headerArray = header.map((columnName, index) => cellFn(columnName, 0, index))
+ const headerArray = header.map((columnName, index) =>
+ cellFn(columnName, 0, index),
+ )
- })
+ }),
- header: headerArray
+ header: headerArray,
- return data.header.join(delimiter) + "\n" + data.rows.map(row => row.join(delimiter)).join("\n")
+ return (
+ data.header.join(delimiter) +
+ "\n" +
+ data.rows.map((row) => row.join(delimiter)).join("\n")
+ )
Changed around line 1555: class TreeNode extends AbstractNode {
- const widths = header.map(col => (col.length > maxCharactersPerColumn ? maxCharactersPerColumn : col.length))
+ const widths = header.map((col) =>
+ col.length > maxCharactersPerColumn ? maxCharactersPerColumn : col.length,
+ )
- this.forEach(node => {
+ this.forEach((node) => {
- if (length > widths[index]) widths[index] = length > maxCharactersPerColumn ? maxCharactersPerColumn : length
+ if (length > widths[index])
+ widths[index] =
+ length > maxCharactersPerColumn ? maxCharactersPerColumn : length
Changed around line 1585: class TreeNode extends AbstractNode {
- return this._toOutline(node => node.getLine())
+ return this._toOutline((node) => node.getLine())
- const growBranch = (outlineTreeNode, last, lastStates, nodeFn, callback) => {
+ const growBranch = (
+ outlineTreeNode,
+ last,
+ lastStates,
+ nodeFn,
+ callback,
+ ) => {
- if (lastStatesCopy.push([outlineTreeNode, last]) && lastStates.length > 0) {
+ if (
+ lastStatesCopy.push([outlineTreeNode, last]) &&
+ lastStates.length > 0
+ ) {
Changed around line 1620: class TreeNode extends AbstractNode {
- node.forEach(node => {
+ node.forEach((node) => {
- growBranch({ node: this }, false, [], nodeFn, line => (output += line + "\n"))
+ growBranch(
+ { node: this },
+ false,
+ [],
+ nodeFn,
+ (line) => (output += line + "\n"),
+ )
- return node._setLineAndChildren(this.getLine(), this.childrenToString(), index)
+ return node._setLineAndChildren(
+ this.getLine(),
+ this.childrenToString(),
+ index,
+ )
Changed around line 1651: class TreeNode extends AbstractNode {
- .map(str => new constructor(str))
+ .map((str) => new constructor(str))
- return this.toMarkdownTableAdvanced(this._getUnionNames(), val => val)
+ return this.toMarkdownTableAdvanced(this._getUnionNames(), (val) => val)
- const empty = columns.map(col => "-")
+ const empty = columns.map((col) => "-")
- const formattedValues = row.map((val, colIndex) => formatFn(val, rowIndex, colIndex))
+ const formattedValues = row.map((val, colIndex) =>
+ formatFn(val, rowIndex, colIndex),
+ )
Changed around line 1690: class TreeNode extends AbstractNode {
- .map(line => (line.substr(0, 1) === this.getXI() ? line : this.getXI() + line))
- .map(line => line.substr(1))
+ .map((line) =>
+ line.substr(0, 1) === this.getXI() ? line : this.getXI() + line,
+ )
+ .map((line) => line.substr(1))
Changed around line 1718: class TreeNode extends AbstractNode {
- content.forEach(node => {
+ content.forEach((node) => {
Changed around line 1731: class TreeNode extends AbstractNode {
- this._appendFromJavascriptObjectTuple(firstWord, content[firstWord], circularCheckArray.slice(0))
+ this._appendFromJavascriptObjectTuple(
+ firstWord,
+ content[firstWord],
+ circularCheckArray.slice(0),
+ )
Changed around line 1752: class TreeNode extends AbstractNode {
- else if (content instanceof Date) line = firstWord + " " + content.getTime().toString()
+ else if (content instanceof Date)
+ line = firstWord + " " + content.getTime().toString()
- const length = content instanceof Array ? content.length : Object.keys(content).length
- if (length) children = new TreeNode()._setChildren(content, circularCheckArray)
+ const length =
+ content instanceof Array ? content.length : Object.keys(content).length
+ if (length)
+ children = new TreeNode()._setChildren(content, circularCheckArray)
Changed around line 1784: class TreeNode extends AbstractNode {
- lines.forEach(line => {
+ lines.forEach((line) => {
Changed around line 1798: class TreeNode extends AbstractNode {
- const nodeConstructor = parent._getParser()._getNodeConstructor(lineContent, parent)
+ const nodeConstructor = parent
+ ._getParser()
+ ._getNodeConstructor(lineContent, parent)
Changed around line 1813: class TreeNode extends AbstractNode {
- return this.map(node => node.getContent())
+ return this.map((node) => node.getContent())
- return this.filter(child => child instanceof constructor)
+ return this.filter((child) => child instanceof constructor)
- return this.find(child => child instanceof constructor)
+ return this.find((child) => child instanceof constructor)
Changed around line 1840: class TreeNode extends AbstractNode {
- return this.map(node => node.getFirstWord())
+ return this.map((node) => node.getFirstWord())
Changed around line 1853: class TreeNode extends AbstractNode {
- return this.map(node => node._toXml(indentCount)).join("")
+ return this.map((node) => node._toXml(indentCount)).join("")
Changed around line 1905: class TreeNode extends AbstractNode {
- this.forEach(node => {
+ this.forEach((node) => {
Changed around line 1915: class TreeNode extends AbstractNode {
- return this.isRoot() || this.getParent().isRoot() ? undefined : this.getParent().getParent()
+ return this.isRoot() || this.getParent().isRoot()
+ ? undefined
+ : this.getParent().getParent()
Changed around line 1940: class TreeNode extends AbstractNode {
- const mTimes = this.map(child => child.getTreeMTime())
+ const mTimes = this.map((child) => child.getTreeMTime())
- return this._setCMTime(newestTime || this._getProcessTimeInMilliseconds())._getCMTime()
+ return this._setCMTime(
+ newestTime || this._getProcessTimeInMilliseconds(),
+ )._getCMTime()
Changed around line 1967: class TreeNode extends AbstractNode {
- _setVirtualAncestorNodesByInheritanceViaColumnIndicesAndThenExpand(nodes, thisIdColumnNumber, extendsIdColumnNumber) {
+ _setVirtualAncestorNodesByInheritanceViaColumnIndicesAndThenExpand(
+ nodes,
+ thisIdColumnNumber,
+ extendsIdColumnNumber,
+ ) {
- if (map[nodeId]) throw new Error(`Tried to define a node with id "${nodeId}" but one is already defined.`)
+ if (map[nodeId])
+ throw new Error(
+ `Tried to define a node with id "${nodeId}" but one is already defined.`,
+ )
- parentId: node.getWord(extendsIdColumnNumber)
+ parentId: node.getWord(extendsIdColumnNumber),
- Object.values(map).forEach(nodeInfo => {
+ Object.values(map).forEach((nodeInfo) => {
- if (parentId && !parentNode) throw new Error(`Node "${nodeInfo.nodeId}" tried to extend "${parentId}" but "${parentId}" not found.`)
+ if (parentId && !parentNode)
+ throw new Error(
+ `Node "${nodeInfo.nodeId}" tried to extend "${parentId}" but "${parentId}" not found.`,
+ )
- nodes.forEach(node => node._expandFromVirtualParentTree())
+ nodes.forEach((node) => node._expandFromVirtualParentTree())
Changed around line 2003: class TreeNode extends AbstractNode {
- if (parentNode._isExpanding) throw new Error(`Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`)
+ if (parentNode._isExpanding)
+ throw new Error(
+ `Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`,
+ )
Changed around line 2016: class TreeNode extends AbstractNode {
- _expandChildren(thisIdColumnNumber, extendsIdColumnNumber, childrenThatNeedExpanding = this.getChildren()) {
- return this._setVirtualAncestorNodesByInheritanceViaColumnIndicesAndThenExpand(childrenThatNeedExpanding, thisIdColumnNumber, extendsIdColumnNumber)
+ _expandChildren(
+ thisIdColumnNumber,
+ extendsIdColumnNumber,
+ childrenThatNeedExpanding = this.getChildren(),
+ ) {
+ return this._setVirtualAncestorNodesByInheritanceViaColumnIndicesAndThenExpand(
+ childrenThatNeedExpanding,
+ thisIdColumnNumber,
+ extendsIdColumnNumber,
+ )
- const node = nodeOrStr instanceof TreeNode ? nodeOrStr : new TreeNode(nodeOrStr)
+ const node =
+ nodeOrStr instanceof TreeNode ? nodeOrStr : new TreeNode(nodeOrStr)
- node.forEach(sourceNode => {
+ node.forEach((sourceNode) => {
- this.appendLineAndChildren(sourceNode.getLine(), sourceNode.childrenToString())
+ this.appendLineAndChildren(
+ sourceNode.getLine(),
+ sourceNode.childrenToString(),
+ )
- targetNode = this.touchNode(firstWord).setContent(sourceNode.getContent())
+ targetNode = this.touchNode(firstWord).setContent(
+ sourceNode.getContent(),
+ )
Changed around line 2063: class TreeNode extends AbstractNode {
- defs.forEach(def => {
+ defs.forEach((def) => {
- const uses = allUses.filter(node => node.hasWord(1, macroName))
+ const uses = allUses.filter((node) => node.hasWord(1, macroName))
- const replaceFn = str => {
+ const replaceFn = (str) => {
Changed around line 2075: class TreeNode extends AbstractNode {
- uses.forEach(node => {
+ uses.forEach((node) => {
Changed around line 2097: class TreeNode extends AbstractNode {
- this.getTopDownArray().forEach(node => {
+ this.getTopDownArray().forEach((node) => {
Changed around line 2119: class TreeNode extends AbstractNode {
- if (content.match(this.getYI())) return this.setContentWithChildren(content)
+ if (content.match(this.getYI()))
+ return this.setContentWithChildren(content)
- return this.getParent().insertLineAndChildren(line, children, this.getIndex())
+ return this.getParent().insertLineAndChildren(
+ line,
+ children,
+ this.getIndex(),
+ )
- return this.getParent().insertLineAndChildren(line, children, this.getIndex() + 1)
+ return this.getParent().insertLineAndChildren(
+ line,
+ children,
+ this.getIndex() + 1,
+ )
Changed around line 2167: class TreeNode extends AbstractNode {
- return this.getParent()._setLineAndChildren(this.getLine(), this.childrenToString(), this.getIndex() + 1)
+ return this.getParent()._setLineAndChildren(
+ this.getLine(),
+ this.childrenToString(),
+ this.getIndex() + 1,
+ )
Changed around line 2200: class TreeNode extends AbstractNode {
- this._getNodesByLineRegex(matches, columns.map(str => new RegExp("^" + str)))
+ this._getNodesByLineRegex(
+ matches,
+ columns.map((str) => new RegExp("^" + str)),
+ )
- const candidates = this.filter(child => child.getLine().match(reg))
- if (!rgs.length) return candidates.forEach(cand => matches.push(cand))
- candidates.forEach(cand => cand._getNodesByLineRegex(matches, rgs))
+ const candidates = this.filter((child) => child.getLine().match(reg))
+ if (!rgs.length) return candidates.forEach((cand) => matches.push(cand))
+ candidates.forEach((cand) => cand._getNodesByLineRegex(matches, rgs))
- return node.map(node => this._setLineAndChildren(node.getLine(), node.childrenToString()))
+ return node.map((node) =>
+ this._setLineAndChildren(node.getLine(), node.childrenToString()),
+ )
- indexesToDelete.reverse().forEach(index => this._getChildrenArray().splice(index, 1))
+ indexesToDelete
+ .reverse()
+ .forEach((index) => this._getChildrenArray().splice(index, 1))
Changed around line 2247: class TreeNode extends AbstractNode {
- this.forEach(node => node.getWords().reverse())
+ this.forEach((node) => node.getWords().reverse())
Changed around line 2260: class TreeNode extends AbstractNode {
- this.forEach(node => {
+ this.forEach((node) => {
Changed around line 2271: class TreeNode extends AbstractNode {
- this.findNodes(oldName).forEach(node => node.setFirstWord(newName))
+ this.findNodes(oldName).forEach((node) => node.setFirstWord(newName))
Changed around line 2289: class TreeNode extends AbstractNode {
- return targetNode ? targetNode._deleteAllChildNodesWithFirstWord(nextFirstWord) : 0
+ return targetNode
+ ? targetNode._deleteAllChildNodesWithFirstWord(nextFirstWord)
+ : 0
- this.forEach(node => node.delete(firstWord))
+ this.forEach((node) => node.delete(firstWord))
- const results = this.getTopDownArray().filter(node => node.hasDuplicateFirstWords())
+ const results = this.getTopDownArray().filter((node) =>
+ node.hasDuplicateFirstWords(),
+ )
Changed around line 2310: class TreeNode extends AbstractNode {
- const newNode = parent.insertLineAndChildren(child.getLine(), child.childrenToString(), index + childIndex)
+ const newNode = parent.insertLineAndChildren(
+ child.getLine(),
+ child.childrenToString(),
+ index + childIndex,
+ )
Changed around line 2334: class TreeNode extends AbstractNode {
- const line = index.toString() + (content === undefined ? "" : this.getZI() + content)
+ const line =
+ index.toString() + (content === undefined ? "" : this.getZI() + content)
- .filter(node => node.isBlankLine())
- .forEach(node => node.destroy())
+ .filter((node) => node.isBlankLine())
+ .forEach((node) => node.destroy())
Changed around line 2357: class TreeNode extends AbstractNode {
- this.setWords(
- this.getWords()
- .slice(0, index)
- .concat(words)
- )
+ this.setWords(this.getWords().slice(0, index).concat(words))
Changed around line 2383: class TreeNode extends AbstractNode {
- firstWordPathArray.forEach(firstWord => {
- contextNode = contextNode.getNode(firstWord) || contextNode.appendLine(firstWord)
+ firstWordPathArray.forEach((firstWord) => {
+ contextNode =
+ contextNode.getNode(firstWord) || contextNode.appendLine(firstWord)
Changed around line 2400: class TreeNode extends AbstractNode {
- return this.getChildren().some(node => node.getLine() === line)
+ return this.getChildren().some((node) => node.getLine() === line)
- return this.filter(node => node.getLine() === line)
+ return this.filter((node) => node.getLine() === line)
- lines.map(line => line.destroy())
+ lines.map((line) => line.destroy())
- const indices = indexOrIndices instanceof Array ? indexOrIndices : [indexOrIndices]
+ const indices =
+ indexOrIndices instanceof Array ? indexOrIndices : [indexOrIndices]
Changed around line 2442: class TreeNode extends AbstractNode {
- addObjectsAsDelimited(arrayOfObjects, delimiter = TreeUtils._chooseDelimiter(new TreeNode(arrayOfObjects).toString())) {
+ addObjectsAsDelimited(
+ arrayOfObjects,
+ delimiter = TreeUtils._chooseDelimiter(
+ new TreeNode(arrayOfObjects).toString(),
+ ),
+ ) {
- const rows = arrayOfObjects.map(item =>
+ const rows = arrayOfObjects.map((item) =>
- .replace(/[\n\r]/g, "")
+ .replace(/[\n\r]/g, ""),
- setChildrenAsDelimited(tree, delimiter = TreeUtils._chooseDelimiter(tree.toString())) {
+ setChildrenAsDelimited(
+ tree,
+ delimiter = TreeUtils._chooseDelimiter(tree.toString()),
+ ) {
- convertChildrenToDelimited(delimiter = TreeUtils._chooseDelimiter(this.childrenToString())) {
+ convertChildrenToDelimited(
+ delimiter = TreeUtils._chooseDelimiter(this.childrenToString()),
+ ) {
- rowsAsStrings.forEach(row => {
+ rowsAsStrings.forEach((row) => {
Changed around line 2483: class TreeNode extends AbstractNode {
- const newNode = grandParent.insertLineAndChildren(this.getLine(), this.length ? this.childrenToString() : undefined, parentIndex + 1)
+ const newNode = grandParent.insertLineAndChildren(
+ this.getLine(),
+ this.length ? this.childrenToString() : undefined,
+ parentIndex + 1,
+ )
- const newNode = olderSibling.appendLineAndChildren(this.getLine(), this.length ? this.childrenToString() : undefined)
+ const newNode = olderSibling.appendLineAndChildren(
+ this.getLine(),
+ this.length ? this.childrenToString() : undefined,
+ )
- nodes.forEach(node => node.shiftRight())
+ nodes.forEach((node) => node.shiftRight())
Changed around line 2541: class TreeNode extends AbstractNode {
- return str.includes(quoteChar) ? this._strToRows(str, delimiter, quoteChar) : str.split("\n").map(line => line.split(delimiter))
+ return str.includes(quoteChar)
+ ? this._strToRows(str, delimiter, quoteChar)
+ : str.split("\n").map((line) => line.split(delimiter))
Changed around line 2619: class TreeNode extends AbstractNode {
- row[numberOfColumns - 1] = row.slice(numberOfColumns - 1).join(delimiter)
+ row[numberOfColumns - 1] = row
+ .slice(numberOfColumns - 1)
+ .join(delimiter)
Changed around line 2641: class TreeNode extends AbstractNode {
- if (typeof windowObj.DOMParser !== "undefined") this._xmlParser = xmlStr => new windowObj.DOMParser().parseFromString(xmlStr, "text/xml")
- else if (typeof windowObj.ActiveXObject !== "undefined" && new windowObj.ActiveXObject("Microsoft.XMLDOM")) {
- this._xmlParser = xmlStr => {
+ if (typeof windowObj.DOMParser !== "undefined")
+ this._xmlParser = (xmlStr) =>
+ new windowObj.DOMParser().parseFromString(xmlStr, "text/xml")
+ else if (
+ typeof windowObj.ActiveXObject !== "undefined" &&
+ new windowObj.ActiveXObject("Microsoft.XMLDOM")
+ ) {
+ this._xmlParser = (xmlStr) => {
Changed around line 2677: class TreeNode extends AbstractNode {
- if (shapeArr.length) rootNode.forEach(node => TreeNode.fromShape(shapeArr.slice(0), node))
+ if (shapeArr.length)
+ rootNode.forEach((node) => TreeNode.fromShape(shapeArr.slice(0), node))
- return new TreeNode(table.map(row => this._zipObject(header, row)))
+ return new TreeNode(table.map((row) => this._zipObject(header, row)))
Changed around line 2705: class TreeNode extends AbstractNode {
- if (child.tagName && child.tagName.match(/parsererror/i)) throw new Error("Parse Error")
- if (child.childNodes.length > 0 && child.tagName) children.appendLineAndChildren(child.tagName, this._treeNodeFromXml(child))
+ if (child.tagName && child.tagName.match(/parsererror/i))
+ throw new Error("Parse Error")
+ if (child.childNodes.length > 0 && child.tagName)
+ children.appendLineAndChildren(
+ child.tagName,
+ this._treeNodeFromXml(child),
+ )
Changed around line 2751: class TreeNode extends AbstractNode {
- tree: content => new TreeNode(content),
- csv: content => this.fromCsv(content),
- tsv: content => this.fromTsv(content)
+ tree: (content) => new TreeNode(content),
+ csv: (content) => this.fromCsv(content),
+ tsv: (content) => this.fromTsv(content),
Changed around line 2778: class AbstractExtendibleTreeNode extends TreeNode {
- this.forEach(node => {
- const path = node._getAncestorsArray().map(node => node._getId())
+ this.forEach((node) => {
+ const path = node._getAncestorsArray().map((node) => node._getId())
Changed around line 2787: class AbstractExtendibleTreeNode extends TreeNode {
- return TreeUtils.flatten(this._getAncestorsArray().map(node => node.getChildrenByNodeConstructor(constructor)))
+ return TreeUtils.flatten(
+ this._getAncestorsArray().map((node) =>
+ node.getChildrenByNodeConstructor(constructor),
+ ),
+ )
Changed around line 2800: class AbstractExtendibleTreeNode extends TreeNode {
- return this._getAncestorsArray().find(node => node.has(firstWordPath))
+ return this._getAncestorsArray().find((node) => node.has(firstWordPath))
- if (!this._cache_ancestorSet) this._cache_ancestorSet = new Set(this._getAncestorsArray().map(def => def._getId()))
+ if (!this._cache_ancestorSet)
+ this._cache_ancestorSet = new Set(
+ this._getAncestorsArray().map((def) => def._getId()),
+ )
Changed around line 2822: class AbstractExtendibleTreeNode extends TreeNode {
- if (cannotContainNodes && cannotContainNodes.includes(this)) throw new Error(`Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`)
+ if (cannotContainNodes && cannotContainNodes.includes(this))
+ throw new Error(
+ `Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`,
+ )
- ancestors = ancestors.concat(parentNode._getAncestorsArray(cannotContainNodes))
+ ancestors = ancestors.concat(
+ parentNode._getAncestorsArray(cannotContainNodes),
+ )
Changed around line 2844: class ExtendibleTreeNode extends AbstractExtendibleTreeNode {
- this.forEach(child => {
+ this.forEach((child) => {
Changed around line 2858: window.TreeNode = TreeNode
- ;(function(GrammarConstantsCompiler) {
+ ;(function (GrammarConstantsCompiler) {
Changed around line 2867: var GrammarConstantsCompiler
- ;(function(PreludeCellTypeIds) {
+ ;(function (PreludeCellTypeIds) {
Changed around line 2878: var PreludeCellTypeIds
- ;(function(GrammarConstantsConstantTypes) {
+ ;(function (GrammarConstantsConstantTypes) {
- ;(function(GrammarBundleFiles) {
+ ;(function (GrammarBundleFiles) {
Changed around line 2893: var GrammarBundleFiles
- ;(function(GrammarConstants) {
+ ;(function (GrammarConstants) {
Changed around line 2944: var GrammarConstants
- return cellIndex === 0 ? this._getAutocompleteResultsForFirstWord(partialWord) : this._getAutocompleteResultsForCell(partialWord, cellIndex)
+ return cellIndex === 0
+ ? this._getAutocompleteResultsForFirstWord(partialWord)
+ : this._getAutocompleteResultsForCell(partialWord, cellIndex)
- return this.filter(node => node.doesExtend(nodeTypeId))
+ return this.filter((node) => node.doesExtend(nodeTypeId))
- return [this.getFirstWord() ? new UnknownNodeTypeError(this) : new BlankLineError(this)]
+ return [
+ this.getFirstWord()
+ ? new UnknownNodeTypeError(this)
+ : new BlankLineError(this),
+ ]
- lines.push(cells.map(cell => cell.generateSimulatedData()).join(" "))
+ lines.push(cells.map((cell) => cell.generateSimulatedData()).join(" "))
Changed around line 2974: class GrammarBackedNode extends TreeNode {
- let defs = Object.values(this.getDefinition().getFirstWordMapWithDefinitions())
+ let defs = Object.values(
+ this.getDefinition().getFirstWordMapWithDefinitions(),
+ )
- defs = defs.filter(def => {
+ defs = defs.filter((def) => {
- return defs.map(def => {
+ return defs.map((def) => {
- displayText: id + (description ? " " + description : "")
+ displayText: id + (description ? " " + description : ""),
Changed around line 3010: class GrammarBackedNode extends TreeNode {
- TreeUtils.makeSortByFn(runtimeNode => {
- return orderMap[runtimeNode.getDefinition().getNodeTypeIdFromDefinition()]
- })
+ TreeUtils.makeSortByFn((runtimeNode) => {
+ return orderMap[
+ runtimeNode.getDefinition().getNodeTypeIdFromDefinition()
+ ]
+ }),
- Object.values(this.getDefinition().getFirstWordMapWithDefinitions()).forEach(def => {
+ Object.values(
+ this.getDefinition().getFirstWordMapWithDefinitions(),
+ ).forEach((def) => {
- if (!this.getChildren().some(node => node.getDefinition() === def))
- errors.push(new MissingRequiredNodeTypeError(this, def.getNodeTypeIdFromDefinition()))
+ if (!this.getChildren().some((node) => node.getDefinition() === def))
+ errors.push(
+ new MissingRequiredNodeTypeError(
+ this,
+ def.getNodeTypeIdFromDefinition(),
+ ),
+ )
Changed around line 3063: class GrammarBackedRootNode extends GrammarBackedNode {
- this.getTopDownArray().forEach(node => {
+ this.getTopDownArray().forEach((node) => {
Changed around line 3071: class GrammarBackedRootNode extends GrammarBackedNode {
- return this.getAllTypedWords().filter(typedWord => typedWord.type === cellTypeId)
+ return this.getAllTypedWords().filter(
+ (typedWord) => typedWord.type === cellTypeId,
+ )
- return this.getTopDownArray().filter(node => node.getDefinition().getNodeTypeIdFromDefinition() === nodeTypeId)
+ return this.getTopDownArray().filter(
+ (node) =>
+ node.getDefinition().getNodeTypeIdFromDefinition() === nodeTypeId,
+ )
- .map(child => child.getIndentation() + child.getLineCellTypes())
+ .map((child) => child.getIndentation() + child.getLineCellTypes())
Changed around line 3101: class GrammarBackedRootNode extends GrammarBackedNode {
- errorCount: errorCount
+ errorCount: errorCount,
- if (errorCount) obj.errorMessages = errs.map(err => err.getMessage()).join(";")
+ if (errorCount)
+ obj.errorMessages = errs.map((err) => err.getMessage()).join(";")
- })
+ }),
Changed around line 3117: class GrammarBackedRootNode extends GrammarBackedNode {
- .filter(err => err instanceof UnknownNodeTypeError)
- .map(err => err.getNode().getFirstWord())
- )
+ .filter((err) => err instanceof UnknownNodeTypeError)
+ .map((err) => err.getNode().getFirstWord()),
+ ),
- this.getAllWordBoundaryCoordinates().map(coordinate => {
- const results = this.getAutocompleteResultsAt(coordinate.y, coordinate.x)
+ this.getAllWordBoundaryCoordinates().map((coordinate) => {
+ const results = this.getAutocompleteResultsAt(
+ coordinate.y,
+ coordinate.x,
+ )
- suggestions: results.matches.map(m => m.text).join(" ")
+ suggestions: results.matches.map((m) => m.text).join(" "),
- })
+ }),
Changed around line 3150: class GrammarBackedRootNode extends GrammarBackedNode {
- matches: nodeInScope.getAutocompleteResults(wordProperties.word, wordIndex)
+ matches: nodeInScope.getAutocompleteResults(
+ wordProperties.word,
+ wordIndex,
+ ),
- const familyTree = new GrammarProgram(clone.toString()).getNodeTypeFamilyTree()
+ const familyTree = new GrammarProgram(
+ clone.toString(),
+ ).getNodeTypeFamilyTree()
Changed around line 3178: class GrammarBackedRootNode extends GrammarBackedNode {
- grammarProgram.getValidConcreteAndAbstractNodeTypeDefinitions().forEach(def => {
- usage.appendLine([def.getNodeTypeIdFromDefinition(), "line-id", "nodeType", def.getRequiredCellTypeIds().join(" ")].join(" "))
- })
+ grammarProgram
+ .getValidConcreteAndAbstractNodeTypeDefinitions()
+ .forEach((def) => {
+ usage.appendLine(
+ [
+ def.getNodeTypeIdFromDefinition(),
+ "line-id",
+ "nodeType",
+ def.getRequiredCellTypeIds().join(" "),
+ ].join(" "),
+ )
+ })
- stats.appendLine([filepath + "-" + lineNumber, node.getWords().join(" ")].join(" "))
+ stats.appendLine(
+ [filepath + "-" + lineNumber, node.getWords().join(" ")].join(" "),
+ )
- .map(child => child.getIndentation() + child.getLineHighlightScopes())
+ .map((child) => child.getIndentation() + child.getLineHighlightScopes())
- .map(child => child.constructor.name + this.getZI() + child.getIndentation() + child.getLineCellTypes())
+ .map(
+ (child) =>
+ child.constructor.name +
+ this.getZI() +
+ child.getIndentation() +
+ child.getLineCellTypes(),
+ )
- .map(child => child.constructor.name + this.getZI() + child.getIndentation() + child.getLine())
+ .map(
+ (child) =>
+ child.constructor.name +
+ this.getZI() +
+ child.getIndentation() +
+ child.getLine(),
+ )
- const typeNode = this._cache_highlightScopeTree.getTopDownArray()[lineIndex - 1]
+ const typeNode =
+ this._cache_highlightScopeTree.getTopDownArray()[lineIndex - 1]
- this._cache_highlightScopeTree = new TreeNode(this.getInPlaceHighlightScopeTree())
+ this._cache_highlightScopeTree = new TreeNode(
+ this.getInPlaceHighlightScopeTree(),
+ )
Changed around line 3250: class GrammarBackedNonRootNode extends GrammarBackedNode {
- {}
+ {},
Changed around line 3265: class GrammarBackedNonRootNode extends GrammarBackedNode {
- return this._getGrammarBackedCellArray().filter(cell => cell.getWord() !== undefined)
+ return this._getGrammarBackedCellArray().filter(
+ (cell) => cell.getWord() !== undefined,
+ )
Changed around line 3275: class GrammarBackedNonRootNode extends GrammarBackedNode {
- const actualWordCountOrRequiredCellCount = Math.max(this.getWords().length, numberOfRequiredCells)
+ const actualWordCountOrRequiredCellCount = Math.max(
+ this.getWords().length,
+ numberOfRequiredCells,
+ )
- for (let cellIndex = 0; cellIndex < actualWordCountOrRequiredCellCount; cellIndex++) {
+ for (
+ let cellIndex = 0;
+ cellIndex < actualWordCountOrRequiredCellCount;
+ cellIndex++
+ ) {
- let cellTypeDefinition = grammarProgram.getCellTypeDefinitionById(cellTypeId)
+ let cellTypeDefinition =
+ grammarProgram.getCellTypeDefinitionById(cellTypeId)
- if (cellTypeDefinition) cellConstructor = cellTypeDefinition.getCellConstructor()
+ if (cellTypeDefinition)
+ cellConstructor = cellTypeDefinition.getCellConstructor()
- cellTypeDefinition = grammarProgram.getCellTypeDefinitionById(cellTypeId)
+ cellTypeDefinition =
+ grammarProgram.getCellTypeDefinitionById(cellTypeId)
- cells[cellIndex] = new cellConstructor(this, cellIndex, cellTypeDefinition, cellTypeId, isCatchAll)
+ cells[cellIndex] = new cellConstructor(
+ this,
+ cellIndex,
+ cellTypeDefinition,
+ cellTypeId,
+ isCatchAll,
+ )
- .map(slot => slot.getCellTypeId())
+ .map((slot) => slot.getCellTypeId())
- .map(slot => slot.getHighlightScope() || defaultScope)
+ .map((slot) => slot.getHighlightScope() || defaultScope)
- .map(check => check.getErrorIfAny())
- .filter(i => i)
+ .map((check) => check.getErrorIfAny())
+ .filter((i) => i)
Changed around line 3337: class GrammarBackedNonRootNode extends GrammarBackedNode {
- const indentCharacter = this.getDefinition()._getCompilerObject()[GrammarConstantsCompiler.indentCharacter]
+ const indentCharacter =
+ this.getDefinition()._getCompilerObject()[
+ GrammarConstantsCompiler.indentCharacter
+ ]
- return indentCharacter !== undefined ? indentCharacter.repeat(indent.length) : indent
+ return indentCharacter !== undefined
+ ? indentCharacter.repeat(indent.length)
+ : indent
- const catchAllCellDelimiter = compiler[GrammarConstantsCompiler.catchAllCellDelimiter]
+ const catchAllCellDelimiter =
+ compiler[GrammarConstantsCompiler.catchAllCellDelimiter]
- return str !== undefined ? TreeUtils.formatStr(str, catchAllCellDelimiter, this.cells) : this.getLine()
+ return str !== undefined
+ ? TreeUtils.formatStr(str, catchAllCellDelimiter, this.cells)
+ : this.getLine()
- if (def.isTerminalNodeType()) return this._getCompiledIndentation() + this._getCompiledLine()
+ if (def.isTerminalNodeType())
+ return this._getCompiledIndentation() + this._getCompiledLine()
- const openChildrenString = compiler[GrammarConstantsCompiler.openChildren] || ""
- const closeChildrenString = compiler[GrammarConstantsCompiler.closeChildren] || ""
- const childJoinCharacter = compiler[GrammarConstantsCompiler.joinChildrenWith] || "\n"
+ const openChildrenString =
+ compiler[GrammarConstantsCompiler.openChildren] || ""
+ const closeChildrenString =
+ compiler[GrammarConstantsCompiler.closeChildren] || ""
+ const childJoinCharacter =
+ compiler[GrammarConstantsCompiler.joinChildrenWith] || "\n"
- const compiledChildren = this.map(child => child.compile()).join(childJoinCharacter)
+ const compiledChildren = this.map((child) => child.compile()).join(
+ childJoinCharacter,
+ )
Changed around line 3378: ${indent}${closeChildrenString}`
- this._getGrammarBackedCellArray().forEach(cell => {
+ this._getGrammarBackedCellArray().forEach((cell) => {
Changed around line 3435: class AbstractGrammarBackedCell {
- let words = cellDef ? cellDef._getAutocompleteWordOptions(this.getNode().getRootProgramNode()) : []
+ let words = cellDef
+ ? cellDef._getAutocompleteWordOptions(this.getNode().getRootProgramNode())
+ : []
- if (partialWord) words = words.filter(word => word.includes(partialWord))
- return words.map(word => {
+ if (partialWord) words = words.filter((word) => word.includes(partialWord))
+ return words.map((word) => {
- displayText: word
+ displayText: word,
Changed around line 3463: class AbstractGrammarBackedCell {
- return this._getCellTypeDefinition().isValid(this._word, this.getNode().getRootProgramNode()) && this._isValid()
+ return (
+ this._getCellTypeDefinition().isValid(
+ this._word,
+ this.getNode().getRootProgramNode(),
+ ) && this._isValid()
+ )
- return this._word === undefined ? new MissingWordError(this) : new InvalidWordError(this)
+ return this._word === undefined
+ ? new MissingWordError(this)
+ : new InvalidWordError(this)
Changed around line 3519: class GrammarFloatCell extends AbstractGrammarBackedCell {
- return TreeUtils.getRandomString(2, "0123456789".split("")) + "." + TreeUtils.getRandomString(2, "0123456789".split(""))
+ return (
+ TreeUtils.getRandomString(2, "0123456789".split("")) +
+ "." +
+ TreeUtils.getRandomString(2, "0123456789".split(""))
+ )
Changed around line 3546: class GrammarBoolCell extends AbstractGrammarBackedCell {
- return TreeUtils.getRandomString(1, ["1", "true", "t", "yes", "0", "false", "f", "no"])
+ return TreeUtils.getRandomString(1, [
+ "1",
+ "true",
+ "t",
+ "yes",
+ "0",
+ "false",
+ "f",
+ "no",
+ ])
Changed around line 3623: class AbstractTreeError {
- return lineIndex === this.getLineIndex() && this._doesCharacterIndexFallOnWord(characterIndex)
+ return (
+ lineIndex === this.getLineIndex() &&
+ this._doesCharacterIndexFallOnWord(characterIndex)
+ )
- return this.getCellIndex() === this.getNode().getWordIndexAtCharacterIndex(characterIndex)
+ return (
+ this.getCellIndex() ===
+ this.getNode().getWordIndexAtCharacterIndex(characterIndex)
+ )
Changed around line 3647: class AbstractTreeError {
- if (this.isMissingWordError()) return this._getCodeMirrorLineWidgetElementCellTypeHints()
- if (suggestion) return this._getCodeMirrorLineWidgetElementWithSuggestion(onApplySuggestionCallBack, suggestion)
+ if (this.isMissingWordError())
+ return this._getCodeMirrorLineWidgetElementCellTypeHints()
+ if (suggestion)
+ return this._getCodeMirrorLineWidgetElementWithSuggestion(
+ onApplySuggestionCallBack,
+ suggestion,
+ )
- return this.getNode()
- .getDefinition()
- .getNodeTypeIdFromDefinition()
+ return this.getNode().getDefinition().getNodeTypeIdFromDefinition()
- this.getIndent() +
- this.getNode()
- .getDefinition()
- .getLineHints()
- )
+ this.getIndent() + this.getNode().getDefinition().getLineHints(),
+ ),
- el.appendChild(document.createTextNode(this.getIndent() + this.getMessage()))
+ el.appendChild(
+ document.createTextNode(this.getIndent() + this.getMessage()),
+ )
- _getCodeMirrorLineWidgetElementWithSuggestion(onApplySuggestionCallBack, suggestion) {
+ _getCodeMirrorLineWidgetElementWithSuggestion(
+ onApplySuggestionCallBack,
+ suggestion,
+ ) {
- el.appendChild(document.createTextNode(this.getIndent() + `${this.getErrorTypeName()}. Suggestion: ${suggestion}`))
+ el.appendChild(
+ document.createTextNode(
+ this.getIndent() +
+ `${this.getErrorTypeName()}. Suggestion: ${suggestion}`,
+ ),
+ )
Changed around line 3699: class AbstractTreeError {
- return this.getNode()
- .getGrammarProgramRoot()
- .getExtensionName()
+ return this.getNode().getGrammarProgramRoot().getExtensionName()
Changed around line 3717: class AbstractTreeError {
- message: this.getMessage()
+ message: this.getMessage(),
Changed around line 3750: class AbstractCellError extends AbstractTreeError {
- .map(option => option.text)
+ .map((option) => option.text),
Changed around line 3759: class UnknownNodeTypeError extends AbstractTreeError {
- return super.getMessage() + ` Invalid nodeType "${node.getFirstWord()}". Valid nodeTypes are: ${TreeUtils._listToEnglishText(options, 7)}.`
+ return (
+ super.getMessage() +
+ ` Invalid nodeType "${node.getFirstWord()}". Valid nodeTypes are: ${TreeUtils._listToEnglishText(
+ options,
+ 7,
+ )}.`
+ )
- return TreeUtils.didYouMean(node.getFirstWord(), parentNode.getAutocompleteResults("", 0).map(option => option.text))
+ return TreeUtils.didYouMean(
+ node.getFirstWord(),
+ parentNode.getAutocompleteResults("", 0).map((option) => option.text),
+ )
Changed around line 3789: class UnknownNodeTypeError extends AbstractTreeError {
- return super.getMessage() + ` Line: "${this.getNode().getLine()}". Blank lines are errors.`
+ return (
+ super.getMessage() +
+ ` Line: "${this.getNode().getLine()}". Blank lines are errors.`
+ )
Changed around line 3817: class MissingRequiredNodeTypeError extends AbstractTreeError {
- return super.getMessage() + ` Multiple "${this.getNode().getFirstWord()}" found.`
+ return (
+ super.getMessage() + ` Multiple "${this.getNode().getFirstWord()}" found.`
+ )
Changed around line 3830: class NodeTypeUsedMultipleTimesError extends AbstractTreeError {
- return super.getMessage() + ` No cellType "${this.getCell().getCellTypeId()}" found. Language grammar for "${this.getExtension()}" may need to be fixed.`
+ return (
+ super.getMessage() +
+ ` No cellType "${this.getCell().getCellTypeId()}" found. Language grammar for "${this.getExtension()}" may need to be fixed.`
+ )
- return super.getMessage() + ` "${this.getCell().getWord()}" does not fit in cellType "${this.getCell().getCellTypeId()}".`
+ return (
+ super.getMessage() +
+ ` "${this.getCell().getWord()}" does not fit in cellType "${this.getCell().getCellTypeId()}".`
+ )
- if (suggestion) return `Change "${this.getCell().getWord()}" to "${suggestion}"`
+ if (suggestion)
+ return `Change "${this.getCell().getWord()}" to "${suggestion}"`
Changed around line 3857: class InvalidWordError extends AbstractCellError {
- return super.getMessage() + ` Extra word "${this.getCell().getWord()}" in ${this.getNodeTypeId()}.`
+ return (
+ super.getMessage() +
+ ` Extra word "${this.getCell().getWord()}" in ${this.getNodeTypeId()}.`
+ )
Changed around line 3872: class ExtraWordError extends AbstractCellError {
- return super.getMessage() + ` Missing word for cell "${this.getCell().getCellTypeId()}".`
+ return (
+ super.getMessage() +
+ ` Missing word for cell "${this.getCell().getCellTypeId()}".`
+ )
Changed around line 3902: class EnumFromCellTypesTestNode extends AbstractGrammarWordTestNode {
- if (programRootNode._enumMaps[enumGroup]) return programRootNode._enumMaps[enumGroup]
+ if (programRootNode._enumMaps[enumGroup])
+ return programRootNode._enumMaps[enumGroup]
- cellTypeIds.forEach(typeId => (cellTypeMap[typeId] = true))
+ cellTypeIds.forEach((typeId) => (cellTypeMap[typeId] = true))
- .filter(typedWord => cellTypeMap[typedWord.type])
- .forEach(typedWord => {
+ .filter((typedWord) => cellTypeMap[typedWord.type])
+ .forEach((typedWord) => {
Changed around line 3952: class cellTypeDefinitionNode extends AbstractExtendibleTreeNode {
- const wordToNativeJavascriptTypeParser = this.getCellConstructor().parserFunctionName
+ const wordToNativeJavascriptTypeParser =
+ this.getCellConstructor().parserFunctionName
- return ${wordToNativeJavascriptTypeParser ? wordToNativeJavascriptTypeParser + `(this.getWord(${wordIndex}))` : `this.getWord(${wordIndex})`}
+ return ${
+ wordToNativeJavascriptTypeParser
+ ? wordToNativeJavascriptTypeParser + `(this.getWord(${wordIndex}))`
+ : `this.getWord(${wordIndex})`
+ }
- const wordToNativeJavascriptTypeParser = this.getCellConstructor().parserFunctionName
+ const wordToNativeJavascriptTypeParser =
+ this.getCellConstructor().parserFunctionName
Changed around line 4002: class cellTypeDefinitionNode extends AbstractExtendibleTreeNode {
- const options = Object.keys(enumNode.getNode(GrammarConstants.enum).getOptions())
+ const options = Object.keys(
+ enumNode.getNode(GrammarConstants.enum).getOptions(),
+ )
- return node ? Object.keys(node.getNode(GrammarConstants.enumFromCellTypes)._getEnumFromCellTypes(program)) : undefined
+ return node
+ ? Object.keys(
+ node
+ .getNode(GrammarConstants.enumFromCellTypes)
+ ._getEnumFromCellTypes(program),
+ )
+ : undefined
- return this._getEnumOptions() || this._getEnumFromCellTypeOptions(program) || []
+ return (
+ this._getEnumOptions() || this._getEnumFromCellTypeOptions(program) || []
+ )
- return this._getFromExtended(GrammarConstants.regex) || (enumOptions ? "(?:" + enumOptions.join("|") + ")" : "[^ ]*")
+ return (
+ this._getFromExtended(GrammarConstants.regex) ||
+ (enumOptions ? "(?:" + enumOptions.join("|") + ")" : "[^ ]*")
+ )
- return this._getChildrenByNodeConstructorInExtended(AbstractGrammarWordTestNode).every(node => node.isValid(str, programRootNode))
+ return this._getChildrenByNodeConstructorInExtended(
+ AbstractGrammarWordTestNode,
+ ).every((node) => node.isValid(str, programRootNode))
Changed around line 4052: class GrammarCompilerNode extends TreeNode {
- GrammarConstantsCompiler.closeChildren
+ GrammarConstantsCompiler.closeChildren,
- types.forEach(type => {
+ types.forEach((type) => {
Changed around line 4082: class GrammarNodeTypeConstantString extends GrammarNodeTypeConstant {
- return this.length ? this.childrenToString() : this.getWordsFrom(2).join(" ")
+ return this.length
+ ? this.childrenToString()
+ : this.getWordsFrom(2).join(" ")
Changed around line 4112: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- GrammarConstants.todoComment
+ GrammarConstants.todoComment,
- types.forEach(type => {
+ types.forEach((type) => {
Changed around line 4131: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- Object.keys(obj).forEach(key => {
+ Object.keys(obj).forEach((key) => {
- const items = extended ? this._getChildrenByNodeConstructorInExtended(GrammarNodeTypeConstant) : this.getChildrenByNodeConstructor(GrammarNodeTypeConstant)
+ const items = extended
+ ? this._getChildrenByNodeConstructorInExtended(GrammarNodeTypeConstant)
+ : this.getChildrenByNodeConstructor(GrammarNodeTypeConstant)
- items.forEach(node => {
+ items.forEach((node) => {
Changed around line 4165: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- this._cache_definedNodeConstructor = this.getLanguageDefinitionProgram()._getCompiledLoadedNodeTypes()[this.getNodeTypeIdFromDefinition()]
+ this._cache_definedNodeConstructor =
+ this.getLanguageDefinitionProgram()._getCompiledLoadedNodeTypes()[
+ this.getNodeTypeIdFromDefinition()
+ ]
- return this.get(GrammarConstants.match) || this._getNodeTypeIdWithoutNodeTypeSuffix()
+ return (
+ this.get(GrammarConstants.match) ||
+ this._getNodeTypeIdWithoutNodeTypeSuffix()
+ )
- return this.getNodeTypeIdFromDefinition().replace(GrammarProgram.nodeTypeSuffixRegex, "")
+ return this.getNodeTypeIdFromDefinition().replace(
+ GrammarProgram.nodeTypeSuffixRegex,
+ "",
+ )
Changed around line 4194: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- return hasJsCode ? this.getNode(GrammarConstants.javascript).childrenToString() : ""
+ return hasJsCode
+ ? this.getNode(GrammarConstants.javascript).childrenToString()
+ : ""
- if (!this._cache_firstWordToNodeDefMap) this._cache_firstWordToNodeDefMap = this._createParserInfo(this._getInScopeNodeTypeIds()).firstWordMap
+ if (!this._cache_firstWordToNodeDefMap)
+ this._cache_firstWordToNodeDefMap = this._createParserInfo(
+ this._getInScopeNodeTypeIds(),
+ ).firstWordMap
Changed around line 4218: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- const getters = (requiredCells ? requiredCells.split(" ") : []).map((cellTypeId, index) => {
- const cellTypeDef = grammarProgram.getCellTypeDefinitionById(cellTypeId)
- if (!cellTypeDef) throw new Error(`No cellType "${cellTypeId}" found`)
- return cellTypeDef.getGetter(index)
- })
+ const getters = (requiredCells ? requiredCells.split(" ") : []).map(
+ (cellTypeId, index) => {
+ const cellTypeDef = grammarProgram.getCellTypeDefinitionById(cellTypeId)
+ if (!cellTypeDef) throw new Error(`No cellType "${cellTypeId}" found`)
+ return cellTypeDef.getGetter(index)
+ },
+ )
- if (catchAllCellTypeId) getters.push(grammarProgram.getCellTypeDefinitionById(catchAllCellTypeId).getCatchAllGetter(getters.length))
+ if (catchAllCellTypeId)
+ getters.push(
+ grammarProgram
+ .getCellTypeDefinitionById(catchAllCellTypeId)
+ .getCatchAllGetter(getters.length),
+ )
- Object.values(this._getUniqueConstantNodes(false)).forEach(node => {
+ Object.values(this._getUniqueConstantNodes(false)).forEach((node) => {
Changed around line 4244: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- regexTests: []
+ regexTests: [],
- const allProgramNodeTypeDefinitionsMap = this._getProgramNodeTypeDefinitionCache()
+ const allProgramNodeTypeDefinitionsMap =
+ this._getProgramNodeTypeDefinitionCache()
- .filter(nodeTypeId => allProgramNodeTypeDefinitionsMap[nodeTypeId].isOrExtendsANodeTypeInScope(nodeTypeIdsInScope))
- .filter(nodeTypeId => !allProgramNodeTypeDefinitionsMap[nodeTypeId]._isAbstract())
- .forEach(nodeTypeId => {
+ .filter((nodeTypeId) =>
+ allProgramNodeTypeDefinitionsMap[
+ nodeTypeId
+ ].isOrExtendsANodeTypeInScope(nodeTypeIdsInScope),
+ )
+ .filter(
+ (nodeTypeId) =>
+ !allProgramNodeTypeDefinitionsMap[nodeTypeId]._isAbstract(),
+ )
+ .forEach((nodeTypeId) => {
- if (regex) result.regexTests.push({ regex: regex, nodeConstructor: def.getNodeTypeIdFromDefinition() })
+ if (regex)
+ result.regexTests.push({
+ regex: regex,
+ nodeConstructor: def.getNodeTypeIdFromDefinition(),
+ })
- arr.sort(TreeUtils.makeSortByFn(definition => definition.getFrequency()))
+ arr.sort(TreeUtils.makeSortByFn((definition) => definition.getFrequency()))
- return arr.map(definition => definition.getNodeTypeIdFromDefinition())
+ return arr.map((definition) => definition.getNodeTypeIdFromDefinition())
Changed around line 4306: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- if (this._cache_isRoot === undefined) this._cache_isRoot = this._getLanguageRootNode() === this
+ if (this._cache_isRoot === undefined)
+ this._cache_isRoot = this._getLanguageRootNode() === this
- return this.get(GrammarConstants.baseNodeType) === GrammarConstants.errorNode
+ return (
+ this.get(GrammarConstants.baseNodeType) === GrammarConstants.errorNode
+ )
Changed around line 4324: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- if (this._isErrorNodeType()) return "getErrors() { return this._getErrorNodeErrors() }"
+ if (this._isErrorNodeType())
+ return "getErrors() { return this._getErrorNodeErrors() }"
Changed around line 4343: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- firstWords.map(firstWord => `"${firstWord}" : ${myFirstWordMap[firstWord].getNodeTypeIdFromDefinition()}`).join(",\n") +
+ firstWords
+ .map(
+ (firstWord) =>
+ `"${firstWord}" : ${myFirstWordMap[
+ firstWord
+ ].getNodeTypeIdFromDefinition()}`,
+ )
+ .join(",\n") +
- .map(rule => {
+ .map((rule) => {
- const catchAllStr = catchAllConstructor ? catchAllConstructor : this._amIRoot() ? `this._getBlobNodeCatchAllNodeType()` : "undefined"
+ const catchAllStr = catchAllConstructor
+ ? catchAllConstructor
+ : this._amIRoot()
+ ? `this._getBlobNodeCatchAllNodeType()`
+ : "undefined"
Changed around line 4374: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- if (!nodeDef) throw new Error(`No definition found for nodeType id "${nodeTypeId}"`)
+ if (!nodeDef)
+ throw new Error(`No definition found for nodeType id "${nodeTypeId}"`)
Changed around line 4383: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- this._getCustomJavascriptMethods()
- ].filter(code => code)
+ this._getCustomJavascriptMethods(),
+ ].filter((code) => code)
- const doesRootExtendMe = this.has(GrammarConstants.root) && rootNode._getAncestorSet().has(this._getGeneratedClassName())
+ const doesRootExtendMe =
+ this.has(GrammarConstants.root) &&
+ rootNode._getAncestorSet().has(this._getGeneratedClassName())
Changed around line 4401: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- this.getParent()
- .toString()
- .replace(/\\/g, "\\\\")
+ this.getParent().toString().replace(/\\/g, "\\\\"),
- .map(def => {
+ .map((def) => {
Changed around line 4420: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- const items = this._getChildrenByNodeConstructorInExtended(GrammarCompilerNode)
+ const items =
+ this._getChildrenByNodeConstructorInExtended(GrammarCompilerNode)
- items.forEach(node => {
+ items.forEach((node) => {
Changed around line 4431: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- return `${this.getNodeTypeIdFromDefinition()}: ${this.getRequiredCellTypeIds().join(" ")}${catchAllCellTypeId ? ` ${catchAllCellTypeId}...` : ""}`
+ return `${this.getNodeTypeIdFromDefinition()}: ${this.getRequiredCellTypeIds().join(
+ " ",
+ )}${catchAllCellTypeId ? ` ${catchAllCellTypeId}...` : ""}`
- return firstWordsInScope.some(firstWord => chain.has(firstWord))
+ return firstWordsInScope.some((firstWord) => chain.has(firstWord))
- return !this._getFromExtended(GrammarConstants.inScope) && !this._getFromExtended(GrammarConstants.catchAllNodeType)
+ return (
+ !this._getFromExtended(GrammarConstants.inScope) &&
+ !this._getFromExtended(GrammarConstants.catchAllNodeType)
+ )
- const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
+ const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
- const match = regexMatch ? `'${regexMatch}'` : `'^ *${escapeRegExp(firstWordMatch)}(?: |$)'`
+ const match = regexMatch
+ ? `'${regexMatch}'`
+ : `'^ *${escapeRegExp(firstWordMatch)}(?: |$)'`
- const firstCellTypeDef = program.getCellTypeDefinitionById(requiredCellTypeIds[0])
- const firstWordHighlightScope = (firstCellTypeDef ? firstCellTypeDef.getHighlightScope() : defaultHighlightScope) + "." + this.getNodeTypeIdFromDefinition()
+ const firstCellTypeDef = program.getCellTypeDefinitionById(
+ requiredCellTypeIds[0],
+ )
+ const firstWordHighlightScope =
+ (firstCellTypeDef
+ ? firstCellTypeDef.getHighlightScope()
+ : defaultHighlightScope) +
+ "." +
+ this.getNodeTypeIdFromDefinition()
- match: ${match}
Changed around line 4473: class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
- if (!cellTypeDefinition) throw new Error(`No ${GrammarConstants.cellType} ${cellTypeId} found`) // todo: standardize error/capture error at grammar time
- return ` ${index + 1}: ${(cellTypeDefinition.getHighlightScope() || defaultHighlightScope) + "." + cellTypeDefinition.getCellTypeId()}`
+ if (!cellTypeDefinition)
+ throw new Error(`No ${GrammarConstants.cellType} ${cellTypeId} found`) // todo: standardize error/capture error at grammar time
+ return ` ${index + 1}: ${
+ (cellTypeDefinition.getHighlightScope() || defaultHighlightScope) +
+ "." +
+ cellTypeDefinition.getCellTypeId()
+ }`
- const cellTypesToRegex = cellTypeIds => cellTypeIds.map(cellTypeId => `({{${cellTypeId}}})?`).join(" ?")
+ const cellTypesToRegex = (cellTypeIds) =>
+ cellTypeIds.map((cellTypeId) => `({{${cellTypeId}}})?`).join(" ?")
- match: ${cellTypesToRegex(requiredCellTypeIds)}
Changed around line 4493: ${captures}
- if (!this._cache_nodeTypeInheritanceSet) this._cache_nodeTypeInheritanceSet = new Set(this.getAncestorNodeTypeIdsArray())
+ if (!this._cache_nodeTypeInheritanceSet)
+ this._cache_nodeTypeInheritanceSet = new Set(
+ this.getAncestorNodeTypeIdsArray(),
+ )
- this._cache_ancestorNodeTypeIdsArray = this._getAncestorsArray().map(def => def.getNodeTypeIdFromDefinition())
+ this._cache_ancestorNodeTypeIdsArray = this._getAncestorsArray().map(
+ (def) => def.getNodeTypeIdFromDefinition(),
+ )
Changed around line 4533: class GrammarProgram extends AbstractGrammarDefinitionNode {
- { regex: GrammarProgram.nodeTypeFullRegex, nodeConstructor: nodeTypeDefinitionNode },
- { regex: GrammarProgram.cellTypeFullRegex, nodeConstructor: cellTypeDefinitionNode }
+ {
+ regex: GrammarProgram.nodeTypeFullRegex,
+ nodeConstructor: nodeTypeDefinitionNode,
+ },
+ {
+ regex: GrammarProgram.cellTypeFullRegex,
+ nodeConstructor: cellTypeDefinitionNode,
+ },
Changed around line 4550: class GrammarProgram extends AbstractGrammarDefinitionNode {
- if (!this._cache_compiledLoadedNodeTypes) throw new Error(`Failed to getNodeTypeMap`)
+ if (!this._cache_compiledLoadedNodeTypes)
+ throw new Error(`Failed to getNodeTypeMap`)
- this._cache_compiledLoadedNodeTypes = this._importBrowserRootNodeTypeConstructor(this.toBrowserJavascript(), this.getGrammarName()).getNodeTypeMap()
+ this._cache_compiledLoadedNodeTypes =
+ this._importBrowserRootNodeTypeConstructor(
+ this.toBrowserJavascript(),
+ this.getGrammarName(),
+ ).getNodeTypeMap()
Changed around line 4575: class GrammarProgram extends AbstractGrammarDefinitionNode {
- console.log(`Error in compiled grammar code for language "${this.getGrammarName()}":`)
+ console.log(
+ `Error in compiled grammar code for language "${this.getGrammarName()}":`,
+ )
- .join("\n")
+ .join("\n"),
Changed around line 4599: class GrammarProgram extends AbstractGrammarDefinitionNode {
- this.getValidConcreteAndAbstractNodeTypeDefinitions().forEach(def =>
- def.getExamples().forEach(example => {
- const exampleProgram = new programConstructor(example.childrenToString())
- exampleProgram.getAllErrors(example._getLineNumber() + 1).forEach(err => {
- errors.push(err)
- })
- })
+ this.getValidConcreteAndAbstractNodeTypeDefinitions().forEach((def) =>
+ def.getExamples().forEach((example) => {
+ const exampleProgram = new programConstructor(
+ example.childrenToString(),
+ )
+ exampleProgram
+ .getAllErrors(example._getLineNumber() + 1)
+ .forEach((err) => {
+ errors.push(err)
+ })
+ }),
Changed around line 4631: ${exampleNode ? exampleNode.childrenToString(1) : ""}
- - ${languageName} has ${nodeTypeFamilyTree.getTopDownArray().length} node types.
+ - ${languageName} has ${
+ nodeTypeFamilyTree.getTopDownArray().length
+ } node types.
- ${languageName} has ${Object.keys(cellTypes).length} cell types
- - The source code for ${languageName} is ${this.getTopDownArray().length} lines long.
+ - The source code for ${languageName} is ${
+ this.getTopDownArray().length
+ } lines long.
Changed around line 4665: paragraph Here are the "todos" present in the source code for ${languageName}:
- .filter(node => node.getWord(0) === "todo")
- .map(node => ` - ${node.getLine()}`)
+ .filter((node) => node.getWord(0) === "todo")
+ .map((node) => ` - ${node.getLine()}`)
- link https://github.com/treenotation/jtree JTree library.`
+ link https://github.com/breck7/scrollsdk ScrollDSK.`
Changed around line 4683: paragraph This readme was auto-generated using the
- jtree: TreeNode.getVersion()
- }
+ jtree: TreeNode.getVersion(),
+ },
- 2
+ 2,
Changed around line 4697: if (errors.length)
- files[GrammarBundleFiles.indexJs] = `module.exports = require("./${nodePath}")`
+ files[
+ GrammarBundleFiles.indexJs
+ ] = `module.exports = require("./${nodePath}")`
- files[GrammarBundleFiles.indexHtml] = `
+ files[
+ GrammarBundleFiles.indexHtml
+ ] = `
Changed around line 4712: ${testCode}
- files[GrammarBundleFiles.testJs] = `const ${languageName} = require("./index.js")
+ files[
+ GrammarBundleFiles.testJs
+ ] = `const ${languageName} = require("./index.js")
- return this._getRootNodeTypeDefinitionNode().get(GrammarConstants.compilesTo)
+ return this._getRootNodeTypeDefinitionNode().get(
+ GrammarConstants.compilesTo,
+ )
- if (!this._cache_cellTypes) this._cache_cellTypes = this._getCellTypeDefinitions()
+ if (!this._cache_cellTypes)
+ this._cache_cellTypes = this._getCellTypeDefinitions()
Changed around line 4735: ${testCode}`
- Object.values(this.getValidConcreteAndAbstractNodeTypeDefinitions()).forEach(node => {
+ Object.values(
+ this.getValidConcreteAndAbstractNodeTypeDefinitions(),
+ ).forEach((node) => {
Changed around line 4746: ${testCode}`
- this.getChildrenByNodeConstructor(cellTypeDefinitionNode).forEach(type => (types[type.getCellTypeId()] = type))
+ this.getChildrenByNodeConstructor(cellTypeDefinitionNode).forEach(
+ (type) => (types[type.getCellTypeId()] = type),
+ )
- return this.getChildrenByNodeConstructor(nodeTypeDefinitionNode).filter(node => node._hasValidNodeTypeId())
+ return this.getChildrenByNodeConstructor(nodeTypeDefinitionNode).filter(
+ (node) => node._hasValidNodeTypeId(),
+ )
- this.forEach(def => {
- if (def instanceof AbstractGrammarDefinitionNode && def.has(GrammarConstants.root) && def._hasValidNodeTypeId()) this._cache_rootNodeTypeNode = def
+ this.forEach((def) => {
+ if (
+ def instanceof AbstractGrammarDefinitionNode &&
+ def.has(GrammarConstants.root) &&
+ def._hasValidNodeTypeId()
+ )
+ this._cache_rootNodeTypeNode = def
- this._cache_rootNodeTypeNode = this.concat(`${GrammarConstants.defaultRootNode}
+ this._cache_rootNodeTypeNode = this
+ .concat(`${GrammarConstants.defaultRootNode}
Changed around line 4794: ${testCode}`
- const nodeTypesNode = this._getRootNodeTypeDefinitionNode().getNode(GrammarConstants.inScope)
+ const nodeTypesNode = this._getRootNodeTypeDefinitionNode().getNode(
+ GrammarConstants.inScope,
+ )
- const nodeTypesNode = this._getRootNodeTypeDefinitionNode().getNode(GrammarConstants.inScope)
+ const nodeTypesNode = this._getRootNodeTypeDefinitionNode().getNode(
+ GrammarConstants.inScope,
+ )
- this.getChildrenByNodeConstructor(nodeTypeDefinitionNode).forEach(nodeTypeDefinitionNode => {
- this._cache_nodeTypeDefinitions[nodeTypeDefinitionNode.getNodeTypeIdFromDefinition()] = nodeTypeDefinitionNode
- })
+ this.getChildrenByNodeConstructor(nodeTypeDefinitionNode).forEach(
+ (nodeTypeDefinitionNode) => {
+ this._cache_nodeTypeDefinitions[
+ nodeTypeDefinitionNode.getNodeTypeIdFromDefinition()
+ ] = nodeTypeDefinitionNode
+ },
+ )
Changed around line 4825: ${testCode}`
- if (!this._cache_rootConstructorClass) this._cache_rootConstructorClass = this._getRootConstructor()
+ if (!this._cache_rootConstructorClass)
+ this._cache_rootConstructorClass = this._getRootConstructor()
- return this._getRootNodeTypeDefinitionNode().get(GrammarConstants.extensions)
+ return this._getRootNodeTypeDefinitionNode().get(
+ GrammarConstants.extensions,
+ )
Changed around line 4851: ${testCode}`
- const nodeTypeClasses = defs.map(def => def._nodeDefToJavascriptClass()).join("\n\n")
- const rootName = this._getRootNodeTypeDefinitionNode()._getGeneratedClassName()
+ const nodeTypeClasses = defs
+ .map((def) => def._nodeDefToJavascriptClass())
+ .join("\n\n")
+ const rootName =
+ this._getRootNodeTypeDefinitionNode()._getGeneratedClassName()
Changed around line 4879: ${exportScript}
- .map(name => ` ${name}: '${cellTypeDefs[name].getRegexString()}'`)
+ .map((name) => ` ${name}: '${cellTypeDefs[name].getRegexString()}'`)
+ .join("\n")
+ const defs = this.getValidConcreteAndAbstractNodeTypeDefinitions().filter(
+ (kw) => !kw._isAbstract(),
+ )
+ const nodeTypeContexts = defs.map((def) => def.getMatchBlock()).join("\n\n")
+ const includes = defs
+ .map(
+ (nodeTypeDef) =>
+ ` - include: '${nodeTypeDef.getNodeTypeIdFromDefinition()}'`,
+ )
- const defs = this.getValidConcreteAndAbstractNodeTypeDefinitions().filter(kw => !kw._isAbstract())
- const nodeTypeContexts = defs.map(def => def.getMatchBlock()).join("\n\n")
- const includes = defs.map(nodeTypeDef => ` - include: '${nodeTypeDef.getNodeTypeIdFromDefinition()}'`).join("\n")
Changed around line 4907: ${includes}
- GrammarProgram.makeNodeTypeId = str =>
- TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(GrammarProgram.nodeTypeSuffixRegex, "") + GrammarConstants.nodeTypeSuffix
- GrammarProgram.makeCellTypeId = str =>
- TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(GrammarProgram.cellTypeSuffixRegex, "") + GrammarConstants.cellTypeSuffix
- GrammarProgram.nodeTypeSuffixRegex = new RegExp(GrammarConstants.nodeTypeSuffix + "$")
- GrammarProgram.nodeTypeFullRegex = new RegExp("^[a-zA-Z0-9_]+" + GrammarConstants.nodeTypeSuffix + "$")
- GrammarProgram.cellTypeSuffixRegex = new RegExp(GrammarConstants.cellTypeSuffix + "$")
- GrammarProgram.cellTypeFullRegex = new RegExp("^[a-zA-Z0-9_]+" + GrammarConstants.cellTypeSuffix + "$")
+ GrammarProgram.makeNodeTypeId = (str) =>
+ TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(
+ GrammarProgram.nodeTypeSuffixRegex,
+ "",
+ ) + GrammarConstants.nodeTypeSuffix
+ GrammarProgram.makeCellTypeId = (str) =>
+ TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(
+ GrammarProgram.cellTypeSuffixRegex,
+ "",
+ ) + GrammarConstants.cellTypeSuffix
+ GrammarProgram.nodeTypeSuffixRegex = new RegExp(
+ GrammarConstants.nodeTypeSuffix + "$",
+ )
+ GrammarProgram.nodeTypeFullRegex = new RegExp(
+ "^[a-zA-Z0-9_]+" + GrammarConstants.nodeTypeSuffix + "$",
+ )
+ GrammarProgram.cellTypeSuffixRegex = new RegExp(
+ GrammarConstants.cellTypeSuffix + "$",
+ )
+ GrammarProgram.cellTypeFullRegex = new RegExp(
+ "^[a-zA-Z0-9_]+" + GrammarConstants.cellTypeSuffix + "$",
+ )
Changed around line 4938: window.GrammarBackedRootNode = GrammarBackedRootNode
- this._upgradeMany(globPatterns, fromVersion, toVersion).forEach(file => file.tree.toDisk(file.path))
+ this._upgradeMany(globPatterns, fromVersion, toVersion).forEach((file) =>
+ file.tree.toDisk(file.path),
+ )
Changed around line 4948: class Upgrader extends TreeNode {
- const files = TreeUtils.flatten(globPatterns.map(pattern => glob.sync(pattern)))
+ const files = TreeUtils.flatten(
+ globPatterns.map((pattern) => glob.sync(pattern)),
+ )
- return files.map(path => {
+ return files.map((path) => {
- path: path
+ path: path,
Changed around line 4982: class UnknownGrammarProgram extends TreeNode {
- .filter(word => word)
- .map(word => GrammarProgram.makeNodeTypeId(word))
+ .filter((word) => word)
+ .map((word) => GrammarProgram.makeNodeTypeId(word))
Changed around line 4995: class UnknownGrammarProgram extends TreeNode {
- if (firstWordIsAnInteger && parentFirstWord) node.setFirstWord(GrammarProgram.makeNodeTypeId(parentFirstWord + "Child"))
+ if (firstWordIsAnInteger && parentFirstWord)
+ node.setFirstWord(
+ GrammarProgram.makeNodeTypeId(parentFirstWord + "Child"),
+ )
Changed around line 5006: class UnknownGrammarProgram extends TreeNode {
- if (!keywordsToChildKeywords[firstWord]) keywordsToChildKeywords[firstWord] = {}
- if (!keywordsToNodeInstances[firstWord]) keywordsToNodeInstances[firstWord] = []
+ if (!keywordsToChildKeywords[firstWord])
+ keywordsToChildKeywords[firstWord] = {}
+ if (!keywordsToNodeInstances[firstWord])
+ keywordsToNodeInstances[firstWord] = []
- node.forEach(child => {
+ node.forEach((child) => {
- return { keywordsToChildKeywords: keywordsToChildKeywords, keywordsToNodeInstances: keywordsToNodeInstances }
+ return {
+ keywordsToChildKeywords: keywordsToChildKeywords,
+ keywordsToNodeInstances: keywordsToNodeInstances,
+ }
- const childNodeTypeIds = childFirstWords.map(word => GrammarProgram.makeNodeTypeId(word))
- if (childNodeTypeIds.length) nodeDefNode.touchNode(GrammarConstants.inScope).setWordsFrom(1, childNodeTypeIds)
+ const childNodeTypeIds = childFirstWords.map((word) =>
+ GrammarProgram.makeNodeTypeId(word),
+ )
+ if (childNodeTypeIds.length)
+ nodeDefNode
+ .touchNode(GrammarConstants.inScope)
+ .setWordsFrom(1, childNodeTypeIds)
- .map(line => line.getContent())
- .filter(line => line)
- .map(line => line.split(xi))
- const instanceCellCounts = new Set(cellsForAllInstances.map(cells => cells.length))
+ .map((line) => line.getContent())
+ .filter((line) => line)
+ .map((line) => line.split(xi))
+ const instanceCellCounts = new Set(
+ cellsForAllInstances.map((cells) => cells.length),
+ )
- const cellType = this._getBestCellType(firstWord, instances.length, maxCellsOnLine, cellsForAllInstances.map(cells => cells[cellIndex]))
- if (!globalCellTypeMap.has(cellType.cellTypeId)) globalCellTypeMap.set(cellType.cellTypeId, cellType.cellTypeDefinition)
+ const cellType = this._getBestCellType(
+ firstWord,
+ instances.length,
+ maxCellsOnLine,
+ cellsForAllInstances.map((cells) => cells[cellIndex]),
+ )
+ if (!globalCellTypeMap.has(cellType.cellTypeId))
+ globalCellTypeMap.set(cellType.cellTypeId, cellType.cellTypeDefinition)
Changed around line 5060: class UnknownGrammarProgram extends TreeNode {
- const needsMatchProperty = TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(firstWord) !== firstWord
+ const needsMatchProperty =
+ TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(firstWord) !==
+ firstWord
- if (catchAllCellType) nodeDefNode.set(GrammarConstants.catchAllCellType, catchAllCellType)
+ if (catchAllCellType)
+ nodeDefNode.set(GrammarConstants.catchAllCellType, catchAllCellType)
- if (cellLine.length > 0) nodeDefNode.set(GrammarConstants.cells, cellLine.join(xi))
+ if (cellLine.length > 0)
+ nodeDefNode.set(GrammarConstants.cells, cellLine.join(xi))
Changed around line 5089: class UnknownGrammarProgram extends TreeNode {
- const { keywordsToChildKeywords, keywordsToNodeInstances } = this._getKeywordMaps(clone)
+ const { keywordsToChildKeywords, keywordsToNodeInstances } =
+ this._getKeywordMaps(clone)
- .filter(word => word)
- .map(firstWord =>
- this._inferNodeTypeDef(firstWord, globalCellTypeMap, Object.keys(keywordsToChildKeywords[firstWord]), keywordsToNodeInstances[firstWord])
+ .filter((word) => word)
+ .map((firstWord) =>
+ this._inferNodeTypeDef(
+ firstWord,
+ globalCellTypeMap,
+ Object.keys(keywordsToChildKeywords[firstWord]),
+ keywordsToNodeInstances[firstWord],
+ ),
- return [this._inferRootNodeForAPrefixLanguage(grammarName).toString(), cellTypeDefs.join(yi), nodeTypeDefs.join(yi)].filter(def => def).join("\n")
+ return [
+ this._inferRootNodeForAPrefixLanguage(grammarName).toString(),
+ cellTypeDefs.join(yi),
+ nodeTypeDefs.join(yi),
+ ]
+ .filter((def) => def)
+ .join("\n")
- const values = Array.from(asSet).filter(c => c)
- const every = fn => {
+ const values = Array.from(asSet).filter((c) => c)
+ const every = (fn) => {
- if (every(str => str === "0" || str === "1")) return { cellTypeId: PreludeCellTypeIds.bitCell }
+ if (every((str) => str === "0" || str === "1"))
+ return { cellTypeId: PreludeCellTypeIds.bitCell }
- every(str => {
+ every((str) => {
Changed around line 5135: class UnknownGrammarProgram extends TreeNode {
- if (every(str => str.match(/^-?\d*.?\d+$/))) return { cellTypeId: PreludeCellTypeIds.floatCell }
+ if (every((str) => str.match(/^-?\d*.?\d+$/)))
+ return { cellTypeId: PreludeCellTypeIds.floatCell }
- if (every(str => bools.has(str.toLowerCase()))) return { cellTypeId: PreludeCellTypeIds.boolCell }
+ if (every((str) => bools.has(str.toLowerCase())))
+ return { cellTypeId: PreludeCellTypeIds.boolCell }
- if (instanceCount > 1 && maxCellsOnLine === 1 && allValues.length > asSet.size && asSet.size < enumLimit)
+ if (
+ instanceCount > 1 &&
+ maxCellsOnLine === 1 &&
+ allValues.length > asSet.size &&
+ asSet.size < enumLimit
+ )
- enum ${values.join(xi)}`
+ enum ${values.join(xi)}`,
Changed around line 5159: class UnknownGrammarProgram extends TreeNode {
- ;(function(CmToken) {
+ ;(function (CmToken) {
Changed around line 5187: var CmToken
- $: CmToken.Comment
+ $: CmToken.Comment,
- $: CmToken.String2
- }
+ $: CmToken.String2,
+ },
- $: CmToken.Atom
+ $: CmToken.Atom,
- $: CmToken.Number
+ $: CmToken.Number,
- $: CmToken.Link
- }
+ $: CmToken.Link,
+ },
- $: CmToken.Def
- }
- }
+ $: CmToken.Def,
+ },
+ },
- $: CmToken.Def
+ $: CmToken.Def,
- $: CmToken.Def
+ $: CmToken.Def,
- $: CmToken.Tag
+ $: CmToken.Tag,
- $: CmToken.Variable
- }
- }
+ $: CmToken.Variable,
+ },
+ },
- $: CmToken.Attribute
+ $: CmToken.Attribute,
- $: CmToken.Def
- }
+ $: CmToken.Def,
+ },
- $: CmToken.Def
- }
- }
+ $: CmToken.Def,
+ },
+ },
- $: CmToken.Error
- }
+ $: CmToken.Error,
+ },
- $: CmToken.Operator
+ $: CmToken.Operator,
- "special-method": CmToken.Def
- }
+ "special-method": CmToken.Def,
+ },
- $: CmToken.Comment
+ $: CmToken.Comment,
- $: CmToken.Bracket
- }
+ $: CmToken.Bracket,
+ },
- }
+ },
- $: CmToken.Keyword
+ $: CmToken.Keyword,
- $: CmToken.String2
- }
+ $: CmToken.String2,
+ },
- $: CmToken.Def
+ $: CmToken.Def,
- $: CmToken.Variable2
+ $: CmToken.Variable2,
- $: CmToken.Def
+ $: CmToken.Def,
- $: CmToken.Type
+ $: CmToken.Type,
- $: CmToken.Property
- }
- }
+ $: CmToken.Property,
+ },
+ },
- $: CmToken.Variable3
+ $: CmToken.Variable3,
- $: CmToken.Property
- }
+ $: CmToken.Property,
+ },
- $: CmToken.Property
- }
+ $: CmToken.Property,
+ },
- $: CmToken.Def
- }
- }
+ $: CmToken.Def,
+ },
+ },
- return matchingBranch ? textMateScopeToCodeMirrorStyle(scopeSegments, matchingBranch) || matchingBranch.$ || null : null
+ return matchingBranch
+ ? textMateScopeToCodeMirrorStyle(scopeSegments, matchingBranch) ||
+ matchingBranch.$ ||
+ null
+ : null
- constructor(name, getProgramConstructorMethod, getProgramCodeMethod, codeMirrorLib = undefined) {
+ constructor(
+ name,
+ getProgramConstructorMethod,
+ getProgramCodeMethod,
+ codeMirrorLib = undefined,
+ ) {
- this._getProgramCodeMethod = getProgramCodeMethod || (instance => (instance ? instance.getValue() : this._originalValue))
+ this._getProgramCodeMethod =
+ getProgramCodeMethod ||
+ ((instance) => (instance ? instance.getValue() : this._originalValue))
Changed around line 5365: class TreeNotationCodeMirrorMode {
- "8": "backspace",
- "9": "tab",
- "13": "enter",
- "16": "shift",
- "17": "ctrl",
- "18": "alt",
- "19": "pause",
- "20": "capslock",
- "27": "escape",
- "33": "pageup",
- "34": "pagedown",
- "35": "end",
- "36": "home",
- "37": "left",
- "38": "up",
- "39": "right",
- "40": "down",
- "45": "insert",
- "46": "delete",
- "91": "left window key",
- "92": "right window key",
- "93": "select",
- "112": "f1",
- "113": "f2",
- "114": "f3",
- "115": "f4",
- "116": "f5",
- "117": "f6",
- "118": "f7",
- "119": "f8",
- "120": "f9",
- "121": "f10",
- "122": "f11",
- "123": "f12",
- "144": "numlock",
- "145": "scrolllock"
+ 8: "backspace",
+ 9: "tab",
+ 13: "enter",
+ 16: "shift",
+ 17: "ctrl",
+ 18: "alt",
+ 19: "pause",
+ 20: "capslock",
+ 27: "escape",
+ 33: "pageup",
+ 34: "pagedown",
+ 35: "end",
+ 36: "home",
+ 37: "left",
+ 38: "up",
+ 39: "right",
+ 40: "down",
+ 45: "insert",
+ 46: "delete",
+ 91: "left window key",
+ 92: "right window key",
+ 93: "select",
+ 112: "f1",
+ 113: "f2",
+ 114: "f3",
+ 115: "f4",
+ 116: "f5",
+ 117: "f6",
+ 118: "f7",
+ 119: "f8",
+ 120: "f9",
+ 121: "f10",
+ 122: "f11",
+ 123: "f12",
+ 144: "numlock",
+ 145: "scrolllock",
Changed around line 5414: class TreeNotationCodeMirrorMode {
- hint: (cmInstance, options) => this.codeMirrorAutocomplete(cmInstance, options)
- }
+ hint: (cmInstance, options) =>
+ this.codeMirrorAutocomplete(cmInstance, options),
+ },
- this._cmInstance = this._getCodeMirrorLib().fromTextArea(area, defaultOptions)
+ this._cmInstance = this._getCodeMirrorLib().fromTextArea(
+ area,
+ defaultOptions,
+ )
Changed around line 5442: class TreeNotationCodeMirrorMode {
- const result = await this._getParsedProgram().getAutocompleteResultsAt(cursor.line, cursor.ch)
+ const result = await this._getParsedProgram().getAutocompleteResultsAt(
+ cursor.line,
+ cursor.ch,
+ )
- if (result.matches.length === 1 && result.matches[0].text === result.word) return null
+ if (result.matches.length === 1 && result.matches[0].text === result.word)
+ return null
- to: codeMirrorLib.Pos(cursor.line, result.endCharIndex)
+ to: codeMirrorLib.Pos(cursor.line, result.endCharIndex),
Changed around line 5490: class TreeNotationCodeMirrorMode {
- const highlightScope = program.getCellHighlightScopeAtPosition(lineIndex, cellIndex)
- const style = highlightScope ? textMateScopeToCodeMirrorStyle(highlightScope.split(".")) : undefined
+ const highlightScope = program.getCellHighlightScopeAtPosition(
+ lineIndex,
+ cellIndex,
+ )
+ const style = highlightScope
+ ? textMateScopeToCodeMirrorStyle(highlightScope.split("."))
+ : undefined
- cellIndex: 0
+ cellIndex: 0,
Breck Yunits
Breck Yunits
7 months ago
checkpoint
index.html
Changed around line 112: noSyntaxChars
- description Scroll Notation is a basic building block that you can build higher level Tree Languages on top of. Try it now.
+ description Scroll Notation is a basic building block that you can build higher level languages on top of. Try it now.
- description Scroll Notation can easily support non-traditional programming language designs. For example, you can design new Tree Languages that use pictures instead of words.
+ description Scroll Notation can easily support non-traditional programming language designs. For example, you can design new languages that use pictures instead of words.
Changed around line 127: cleanData
- description With Scroll Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in Tree Languages.
+ description With Scroll Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in languages built on Scroll Notation.
- description Scroll Notation might help make true visual programming a reality. Visual editors generate clean Tree Language code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
+ description Scroll Notation might help make true visual programming a reality. Visual editors generate clean Scroll Notation code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
Breck Yunits
Breck Yunits
7 months ago
checkpoint
README.md
Changed around line 0
- This repo contains the source code for https://treenotation.org
index.html
Changed around line 101
- Tree Notation
+ Scroll Notation
- description Tree Notation is a simple new notation for writing code and data. Tree Notation brings code to life, by giving it physical dimensions. Tree Notation is code you could hold.
+ description Scroll Notation is a simple new notation for writing code and data. Scroll Notation brings code to life, by giving it physical dimensions. Scroll Notation is code you could hold.
- description Say goodbye to unnecessary syntax characters! Tree Notation drops those. The Tree Notation Experiment is to see if we can ditch all of that syntax for easier parsing without losing functionality.
+ description Say goodbye to unnecessary syntax characters! Scroll Notation drops those. The Scroll Notation Experiment is to see if we can ditch all of that syntax for easier parsing without losing functionality.
- description Tree Notation is a basic building block that you can build higher level Tree Languages on top of. Try it now.
+ description Scroll Notation is a basic building block that you can build higher level Tree Languages on top of. Try it now.
- description You can use Tree Notation with pen and paper too. Graph paper works great. For example, healthcare providers could write notes directly in Tree Notation. This can make it easier for lab scientists to analyze that data later on.
+ description You can use Scroll Notation with pen and paper too. Graph paper works great. For example, healthcare providers could write notes directly in Scroll Notation. This can make it easier for lab scientists to analyze that data later on.
- description Tree Notation can easily support non-traditional programming language designs. For example, you can design new Tree Languages that use pictures instead of words.
+ description Scroll Notation can easily support non-traditional programming language designs. For example, you can design new Tree Languages that use pictures instead of words.
- description Tree Notation could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Tree Notation documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
+ description Scroll Notation could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Scroll Notation documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
- description We no longer need to store our data in error prone CSV, XML, or JSON. Tree Notation gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
+ description We no longer need to store our data in error prone CSV, XML, or JSON. Scroll Notation gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
- description With Tree Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in Tree Languages.
+ description With Scroll Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in Tree Languages.
- description Tree Notation might help make true visual programming a reality. Visual editors generate clean Tree Language code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
+ description Scroll Notation might help make true visual programming a reality. Visual editors generate clean Tree Language code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
- description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Tree Notation could do a similar thing. Compared to JSON APIs, Tree Notation APIs could be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
+ description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Scroll Notation could do a similar thing. Compared to JSON APIs, Scroll Notation APIs could be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
- description Tree Notation allows you to easily count the complexity of a given piece of code or data. With Tree Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
+ description Scroll Notation allows you to easily count the complexity of a given piece of code or data. With Scroll Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
- description In the decades ahead, Tree Notation could change the way we think about computers. Tree Notation is like a 2-dimensional binary and could be used at the lowest levels of computing. We could see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
+ description In the decades ahead, Scroll Notation could change the way we think about computers. Scroll Notation is like a 2-dimensional binary and could be used at the lowest levels of computing. We could see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
Changed around line 185: aFutureWithoutCompilers
-

Tree Notation

+

Scroll Notation

Changed around line 200: aFutureWithoutCompilers
- FAQ |
+ FAQ |
-

-
-
-

-

Libraries

+

SDKs

- C++
- Go
-

* Implementations wanted!

-
+ This website published by Breck's Lab.
-
+
readme.scroll
Changed around line 1
+ title Scroll Notation
+ printTitle
+
+ ## Formerly called Tree Notation
+
+ This repo contains the source code for the https://notation.scroll.pub website
+
+
+
testLinks.sh
Changed around line 1
- linkinator -r https://treenotation.org
+ linkinator -r https://notation.scroll.pub
Breck Yunits
Breck Yunits
7 months ago
Update CNAME
CNAME
Changed around line 1
- treenotation.org
+ notation.scroll.pub
Breck Yunits
Breck Yunits
3 years ago
Link updates
index.html
Changed around line 118: paper
- description Tree Notation can easily support non-traditional programming language designs. For example, you can design new Tree Languages that use pictures instead of words.
+ description Tree Notation can easily support non-traditional programming language designs. For example, you can design new Tree Languages that use pictures instead of words.
Changed around line 203: aFutureWithoutCompilers
Breck Yunits
Breck Yunits
3 years ago
Update index.html
index.html
Changed around line 203: aFutureWithoutCompilers
Breck Yunits
Breck Yunits
3 years ago
Update index.html
index.html
Changed around line 208: aFutureWithoutCompilers
- FAQ
+ FAQ |
Breck Yunits
Breck Yunits
4 years ago
Add link to Go library.
index.html
Changed around line 221: aFutureWithoutCompilers
+ Go
- Go*
Breck Yunits
Breck Yunits
4 years ago
Update index.html Add Kotlin link
index.html
Changed around line 220: aFutureWithoutCompilers
Breck Yunits
Breck Yunits
4 years ago
Feedback from user tests
index.html
Changed around line 106
- description Tree Notation is a simple new notation for writing code and data. Tree Notation brings code to life, by giving it 2-dimensions. Tree Notation is code you could hold.
+ description Tree Notation is a simple new notation for writing code and data. Tree Notation brings code to life, by giving it physical dimensions. Tree Notation is code you could hold.
- description Say goodbye to cryptic syntax characters! Tree Notation does away with those. It turns out, there is nothing that you can do with syntax characters that you can't do more easily with simple spacing.
+ description Say goodbye to unnecessary syntax characters! Tree Notation drops those. The Tree Notation Experiment is to see if we can ditch all of that syntax for easier parsing without losing functionality.
- description You can use Tree Notation with pen and paper too. Graph paper works great. For example, healthcare providers can take notes directly in Tree Notation. This can make it easier for lab scientists to analyze that health data later on.
+ description You can use Tree Notation with pen and paper too. Graph paper works great. For example, healthcare providers could write notes directly in Tree Notation. This can make it easier for lab scientists to analyze that data later on.
- description Programming languages no longer have to be constrained to words. You can design new Tree Languages that use pictures instead of words.
+ description Tree Notation can easily support non-traditional programming language designs. For example, you can design new Tree Languages that use pictures instead of words.
- description Tree Notation is the future of web forms. Web forms will still be able to look and operate just like they do today, but under the hood web forms will soon be creating Tree Notation documents. Designing forms will be easier. Autocomplete will be better. And eReceipts and copy/paste for web forms will become standard. See demo.
+ description Tree Notation could be the future of web forms. Web forms would still be able to look and operate just like they do today, but under the hood web forms could soon be creating Tree Notation documents. Designing forms would be easier. Autocomplete would be better. And eReceipts and copy/paste for web forms could become standard. See demo.
- description With Tree Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications will save their files in Tree Languages.
+ description With Tree Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications could save their files in Tree Languages.
- description Tree Notation is the solution to make true visual programming a reality. Visual editors generate clean Tree Language code under the hood, that is as good as the same code handwritten. Try an early example.
+ description Tree Notation might help make true visual programming a reality. Visual editors generate clean Tree Language code under the hood, that, with the help of tools, may be as good as the same code handwritten. Try an early example.
- description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Tree Notation will do a similar thing. Compared to JSON APIs, Tree Notation APIs will be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
+ description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Tree Notation could do a similar thing. Compared to JSON APIs, Tree Notation APIs could be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
- description In the decades ahead, Tree Notation will change the way we think about computers. Tree Notation is like a 2-dimensional binary and will be used at the lowest levels of computing. We will see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
+ description In the decades ahead, Tree Notation could change the way we think about computers. Tree Notation is like a 2-dimensional binary and could be used at the lowest levels of computing. We could see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
Breck Yunits
Breck Yunits
5 years ago
Link fixes
index.html
Changed around line 112: noSyntaxChars
- description Tree Notation is a basic building block that you can build higher level Tree Languages on top of. Try it now.
+ description Tree Notation is a basic building block that you can build higher level Tree Languages on top of. Try it now.
Changed around line 124: webForms
- description We no longer need to store our data in error prone CSV, XML, or JSON. Tree Notation gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
+ description We no longer need to store our data in error prone CSV, XML, or JSON. Tree Notation gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
Changed around line 133: visualProgramming
- description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Tree Notation will do a similar thing. Compared to JSON APIs, Tree Notation APIs will be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
+ description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Tree Notation will do a similar thing. Compared to JSON APIs, Tree Notation APIs will be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
Changed around line 200: aFutureWithoutCompilers
Breck Yunits
Breck Yunits
5 years ago
Added TCF link
index.html
Changed around line 204: aFutureWithoutCompilers
Breck Yunits
Breck Yunits
5 years ago
Added more links
index.html
Changed around line 121: programWithPictures
- description Tree Notation is the future of web forms. Web forms will still be able to look and operate just like they do today, but under the hood web forms will soon be creating Tree Notation documents. Designing forms will be easier. Autocomplete will be better. And eReceipts and copy/paste for web forms will become standard.
+ description Tree Notation is the future of web forms. Web forms will still be able to look and operate just like they do today, but under the hood web forms will soon be creating Tree Notation documents. Designing forms will be easier. Autocomplete will be better. And eReceipts and copy/paste for web forms will become standard. See demo.
Changed around line 139: complexityCanBeCounted
- description In the decades ahead, Tree Notation will change the way we think about computers. Tree Notation is like a 2-dimensional binary and will be used at the lowest levels of computing. We will see new types of Tree Machines that operate very differently than the computers we have today.`)
+ description In the decades ahead, Tree Notation will change the way we think about computers. Tree Notation is like a 2-dimensional binary and will be used at the lowest levels of computing. We will see new types of Tree Machines that operate very differently than the computers we have today. Read more.`)
testLinks.sh
Changed around line 1
- linkinator https://treenotation.org
+ linkinator -r https://treenotation.org
Breck Yunits
Breck Yunits
5 years ago
Fixed broken link. Added link test script
index.html
Changed around line 207: aFutureWithoutCompilers
- FAQ
+ FAQ
testLinks.sh
Changed around line 1
+ linkinator https://treenotation.org
Breck Yunits
Breck Yunits
5 years ago
Merge pull request #18 from plin0009/move-caption Left-align and fix height for caption
plin0009
plin0009
5 years ago
Left-align and fix height for caption
index.html
Changed around line 97
+ text-align: left;
+ height: 5rem;
Breck Yunits
Breck Yunits
5 years ago
Merge pull request #15 from plin0009/move-caption Moved caption under main SVG.
Breck Yunits
Breck Yunits
5 years ago
Merge pull request #17 from ggangix/fixBrokenLink Fix broken link to complexity paper
ggangix
ggangix
5 years ago
Fix broken link to complexity paper
index.html
Changed around line 134: apis
- description Tree Notation allows you to easily count the complexity of a given piece of code or data. With Tree Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
+ description Tree Notation allows you to easily count the complexity of a given piece of code or data. With Tree Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
plin0009
plin0009
5 years ago
Moved caption under main SVG.
index.html
Changed around line 189: aFutureWithoutCompilers
-

+

Breck Yunits
Breck Yunits
5 years ago
standardized svg sizes to 600 by 600
demos/aFutureWithoutCompilers.svg
Changed around line 1
-
+
-
+
Changed around line 31
-
+
demos/apis.svg
Changed around line 1
-
+
-
+
demos/cleanData.svg
Changed around line 1
-
+
-
+
demos/complexityCanBeCounted.svg
Changed around line 1
-
+
-
+
demos/createLanguages.svg
Changed around line 1
-
-
+
+
demos/helloWorld3D.svg
Changed around line 1
-
-
+
+
Changed around line 22
-
+
Changed around line 31
-
+
Changed around line 39
-
+
demos/noSyntaxChars.svg
Changed around line 1
-
+
-
+
demos/paper.svg
Changed around line 1
-
+
-
+
demos/programWithPictures.svg
Changed around line 1
-
+
-
+
demos/programsAreAlsoSpreadsheets.svg
Changed around line 1
-
+
-
+
Changed around line 14
-
+
demos/visualProgramming.svg
Changed around line 1
-
+
-
+
demos/webForms.svg
Changed around line 1
-
+
-
+
index.html
Changed around line 72
- width: 500px;
+ width: 505px;
- height: 500px;
+ height: 505px;
- width: 100%;
+ width: 500px;
+ height: 500px;
Breck Yunits
Breck Yunits
5 years ago
added link to arrow
index.html
Changed around line 130: visualProgramming
- description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Tree Notation will do a similar thing. Compared to JSON APIs, Tree Notation APIs will be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity.
+ description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Tree Notation will do a similar thing. Compared to JSON APIs, Tree Notation APIs will be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity. See an early demo.
Breck Yunits
Breck Yunits
5 years ago
standardizing style
.prettierignore
Changed around line 1
+ *.md
+ *.html
index.html
Changed around line 9
- width: 800px;
+ width: 1200px;
Changed around line 19
+ margin: auto;
Changed around line 99
-
+
lib/jtree.browser.js
Breck Yunits
Breck Yunits
5 years ago
New demos
demos/apis.svg
Changed around line 1
+
+
+ Untitled 17
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/visualProgramming.svg
Changed around line 1
+
+
+ Untitled 16
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/webForms.svg
Changed around line 1
+
+
+ Untitled 15
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
index.html
Changed around line 115: paper
+ webForms
+ title Web Forms Reinvented
+ description Tree Notation is the future of web forms. Web forms will still be able to look and operate just like they do today, but under the hood web forms will soon be creating Tree Notation documents. Designing forms will be easier. Autocomplete will be better. And eReceipts and copy/paste for web forms will become standard.
- description We no longer need to store our data in error prone CSV, XML, or JSON. Tree Notation gives us a simpler, more powerful encoding for data with lots of new advanced features.
+ description We no longer need to store our data in error prone CSV, XML, or JSON. Tree Notation gives us a simpler, more powerful encoding for data with lots of new advanced features. See an early example.
+ visualProgramming
+ title Visual Programming
+ description Tree Notation is the solution to make true visual programming a reality. Visual editors generate clean Tree Language code under the hood, that is as good as the same code handwritten. Try an early example.
+ apis
+ title A New Wave of APIs
+ description In the 2000's, JSON was invented and as a simpler XML it led to an explosion in new web APIs. In the 2020's Tree Notation will do a similar thing. Compared to JSON APIs, Tree Notation APIs will be easier for end users, more intelligent through shared grammars, and more robust via reduced complexity.
Breck Yunits
Breck Yunits
5 years ago
Redid homepage with new descriptions from lobsters feedback
demos/aFutureWithoutCompilers.svg
Changed around line 1
-
+
-
+
Changed around line 31
-
+
demos/cleanData.svg
Changed around line 1
-
+
-
+
demos/complexityCanBeCounted.svg
Changed around line 1
-
+
-
+
demos/createLanguages.svg
Changed around line 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/helloWorld3D.svg
Changed around line 1
-
-
+
+
Changed around line 22
-
+
Changed around line 31
-
+
Changed around line 39
-
+
demos/noSyntaxChars.svg
Changed around line 1
-
+
-
+
demos/paper.svg
Changed around line 1
-
+
-
+
demos/programWithPictures.svg
Changed around line 1
-
+
-
+
demos/programsAreAlsoSpreadsheets.svg
Changed around line 1
-
+
-
-
-
-
-
-
+
+
+
+
+
-
-
index.css
Changed around line 0
- html {
- box-sizing: border-box;
- }
- body {
- margin: auto;
- text-align: center;
- width: 600px;
- }
- *,
- *:before,
- *:after {
- box-sizing: inherit;
- }
- body {
- font-family: -apple-system, BlinkMacSystemFont, sans-serif;
- font-weight: 300;
- }
- body h1 {
- font-weight: 300;
- }
- a {
- color: rgba(65, 124, 129, 1);
- text-decoration: none;
- }
- a:hover {
- color: rgba(14, 72, 78, 1);
- }
- #heroImage {
- height: 400px;
- }
- .libraries a {
- display: block;
- }
- body {
- background: #eee;
- color: rgba(1, 47, 52, 1);
- }
- .primaryColorLightest {
- color: rgba(104, 151, 155, 1);
- }
- .primaryColorLight {
- color: rgba(65, 124, 129, 1);
- }
- .primaryColorMid {
- color: rgba(35, 98, 103, 1);
- }
- .primaryColorDark {
- color: rgba(14, 72, 78, 1);
- }
- .primaryColorDarkest {
- color: rgba(1, 47, 52, 1);
- }
- .section {
- padding: 20px;
- }
index.html
Changed around line 2
-
+
+
+ const data = new jtree.TreeNode(`helloWorld3D
+ title Hello world
+ description Tree Notation is a simple new notation for writing code and data. Tree Notation brings code to life, by giving it 2-dimensions. Tree Notation is code you could hold.
+ noSyntaxChars
+ title No Syntax
+ description Say goodbye to cryptic syntax characters! Tree Notation does away with those. It turns out, there is nothing that you can do with syntax characters that you can't do more easily with simple spacing.
+ createLanguages
+ title Create New Languages
+ description Tree Notation is a basic building block that you can build higher level Tree Languages on top of. Try it now.
+ paper
+ title Works Offline
+ description You can use Tree Notation with pen and paper too. Graph paper works great. For example, healthcare providers can take notes directly in Tree Notation. This can make it easier for lab scientists to analyze that health data later on.
+ programWithPictures
+ title Program With Pictures
+ description Programming languages no longer have to be constrained to words. You can design new Tree Languages that use pictures instead of words.
+ cleanData
+ title Bug Free Data
+ description We no longer need to store our data in error prone CSV, XML, or JSON. Tree Notation gives us a simpler, more powerful encoding for data with lots of new advanced features.
+ programsAreAlsoSpreadsheets
+ title Programs Are Spreadsheets
+ description With Tree Notation, you think of code as being in rows and cells, just like in spreadsheets. Future spreadsheet applications will save their files in Tree Languages.
+ complexityCanBeCounted
+ title We Can Now Count Complexity
+ description Tree Notation allows you to easily count the complexity of a given piece of code or data. With Tree Notation, you can objectively decide which thing is simpler than another. You can count complexity in "Tree Units" like lines, edges, words, cells, repeats and cellBits. You can count complexity starting from 0's and 1's or from a higher level. Read more.
+ aFutureWithoutCompilers
+ title A Future Without Compilers?
+ description In the decades ahead, Tree Notation will change the way we think about computers. Tree Notation is like a 2-dimensional binary and will be used at the lowest levels of computing. We will see new types of Tree Machines that operate very differently than the computers we have today.`)
- document.getElementById("heroImage").src = event.target.href
- location.hash = encodeURIComponent(event.target.href)
+ const id = event.target.value
+ loadSlide(id)
+ location.hash = id
+ }
+ const loadSlide = id => {
+ const node = data.getNode(id)
+ if (!node) return true
+ const sel = document.getElementsByClassName("selected")[0]
+ if (sel) sel.classList.remove("selected")
+ document.getElementById(id + "Link").classList.add("selected")
+ document.getElementById("heroImage").src = `demos/${id}.svg`
+ document.getElementById("caption").innerHTML = node.get("description") || ""
+ }
+ const start = () => {
+ data.forEach(node => {
+ const id = node.getLine()
+ const aTag = document.createElement("A")
+ aTag.appendChild(document.createTextNode(node.get("title")))
+ aTag.href = `demos/${id}.svg`
+ aTag.value = id
+ aTag.id = id + "Link"
+ document.getElementById("slideHolder").appendChild(aTag)
+ })
+
+ const url = new URL(document.location.href)
+ if (url.hash && url.hash.length > 1) {
+ loadSlide(url.hash.substr(1))
+ } else {
+ loadSlide("helloWorld3D")
+ }
-

Tree Notation

-
-

- Why Tree Notation?
-

+
+
+
+
+
+
+
+
+
+
+
+
+

Tree Notation

+
+

+

+
+
+
+
Changed around line 196
-

+

Changed around line 221
- const url = new URL(document.location.href)
- if (url.hash && url.hash.length > 1) {
- document.getElementById("heroImage").src = decodeURIComponent(url.hash).substr(1)
- }
+ start()
jtree.browser.js
Changed around line 1
+ class TreeUtils {
+ static getFileExtension(filepath = "") {
+ const match = filepath.match(/\.([^\.]+)$/)
+ return (match && match[1]) || ""
+ }
+ static async runTestTree(testTree) {
+ // todo: browser version
+ const tap = require("tap")
+ const runOnlyTheseTest = Object.keys(testTree).filter(key => key.startsWith("_"))
+ const testsToRun = runOnlyTheseTest.length ? runOnlyTheseTest : Object.keys(testTree)
+ for (let testName of testsToRun) {
+ await new Promise((resolve, reject) => {
+ tap.test(testName, async childTest => {
+ await testTree[testName](childTest.equal)
+ childTest.end()
+ resolve()
+ })
+ })
+ }
+ }
+ static getMethodFromDotPath(context, str) {
+ const methodParts = str.split(".")
+ while (methodParts.length > 1) {
+ context = contextethodParts.shift()]()
+ }
+ const final = methodParts.shift()
+ return [context, final]
+ }
+ static requireAbsOrRelative(filePath, contextFilePath) {
+ if (!filePath.startsWith(".")) return require(filePath)
+ const path = require("path")
+ const folder = this.getPathWithoutFileName(contextFilePath)
+ const file = path.resolve(folder + "/" + filePath)
+ return require(file)
+ }
+ // Removes last ".*" from this string
+ static removeFileExtension(filename) {
+ return filename ? filename.replace(/\.[^\.]+$/, "") : ""
+ }
+ static getFileName(path) {
+ const parts = path.split("/") // todo: change for windows?
+ return parts.pop()
+ }
+ static getPathWithoutFileName(path) {
+ const parts = path.split("/") // todo: change for windows?
+ parts.pop()
+ return parts.join("/")
+ }
+ static shuffleInPlace(arr) {
+ // https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
+ for (let index = arr.length - 1; index > 0; index--) {
+ const tempIndex = Math.floor(Math.random() * (index + 1))
+ ;[arr[index], arr[tempIndex]] = [arr[tempIndex], arr[index]]
+ }
+ return arr
+ }
+ // Only allows a-zA-Z0-9-_ (And optionally .)
+ static _permalink(str, reg) {
+ return str.length
+ ? str
+ .toLowerCase()
+ .replace(reg, "")
+ .replace(/ /g, "-")
+ : ""
+ }
+ static stringToPermalink(str) {
+ return this._permalink(str, /[^a-z0-9- _\.]/gi)
+ }
+ static getAvailablePermalink(permalink, doesFileExistSyncFn) {
+ const extension = this.getFileExtension(permalink)
+ permalink = this.removeFileExtension(permalink)
+ const originalPermalink = permalink
+ let num = 2
+ let suffix = ""
+ let filename = `${originalPermalink}${suffix}.${extension}`
+ while (doesFileExistSyncFn(filename)) {
+ filename = `${originalPermalink}${suffix}.${extension}`
+ suffix = "-" + num
+ num++
+ }
+ return filename
+ }
+ static toggle(currentValue, values) {
+ const index = values.indexOf(currentValue)
+ return index === -1 || index + 1 === values.length ? values[0] : values[index + 1]
+ }
+ static getClassNameFromFilePath(filepath) {
+ return this.removeFileExtension(this.getFileName(filepath))
+ }
+ static joinArraysOn(joinOn, arrays, columns) {
+ const rows = {}
+ let index = 0
+ if (!columns) columns = arrays.map(arr => Object.keys(arr[0]))
+ arrays.forEach((arr, index) => {
+ const cols = columns[index]
+ arr.forEach(row => {
+ const key = joinOn ? row[joinOn] : index++
+ if (!rows[key]) rows[key] = {}
+ const obj = rows[key]
+ cols.forEach(col => (obj[col] = row[col]))
+ })
+ })
+ return Object.values(rows)
+ }
+ static _getParentFolder(path) {
+ if (path.endsWith("/")) path = this._removeLastSlash(path)
+ return path.replace(/\/[^\/]*$/, "") + "/"
+ }
+ static _removeLastSlash(path) {
+ return path.replace(/\/$/, "")
+ }
+ static _listToEnglishText(list, limit = 5) {
+ const len = list.length
+ if (!len) return ""
+ if (len === 1) return `'${list[0]}'`
+ const clone = list.slice(0, limit).map(item => `'${item}'`)
+ const last = clone.pop()
+ if (len <= limit) return clone.join(", ") + ` and ${last}`
+ return clone.join(", ") + ` and ${len - limit} more`
+ }
+ // todo: refactor so instead of str input takes an array of cells(strings) and scans each indepndently.
+ static _chooseDelimiter(str) {
+ const del = " ,|\t;^%$!#@~*&+-=_:?.{}[]()<>/".split("").find(idea => !str.includes(idea))
+ if (!del) throw new Error("Could not find a delimiter")
+ return del
+ }
+ static flatten(arr) {
+ if (arr.flat) return arr.flat()
+ return arr.reduce((acc, val) => acc.concat(val), [])
+ }
+ static escapeBackTicks(str) {
+ return str.replace(/\`/g, "\\`").replace(/\$\{/g, "\\${")
+ }
+ static ucfirst(str) {
+ return str.charAt(0).toUpperCase() + str.slice(1)
+ }
+ // Adapted from: https://github.com/dcporter/didyoumean.js/blob/master/didYouMean-1.2.1.js
+ static didYouMean(str = "", options = [], caseSensitive = false, threshold = 0.4, thresholdAbsolute = 20) {
+ if (!caseSensitive) str = str.toLowerCase()
+ // Calculate the initial value (the threshold) if present.
+ const thresholdRelative = threshold * str.length
+ let maximumEditDistanceToBeBestMatch
+ if (thresholdRelative !== null && thresholdAbsolute !== null) maximumEditDistanceToBeBestMatch = Math.min(thresholdRelative, thresholdAbsolute)
+ else if (thresholdRelative !== null) maximumEditDistanceToBeBestMatch = thresholdRelative
+ else if (thresholdAbsolute !== null) maximumEditDistanceToBeBestMatch = thresholdAbsolute
+ // Get the edit distance to each option. If the closest one is less than 40% (by default) of str's length, then return it.
+ let closestMatch
+ const len = options.length
+ for (let optionIndex = 0; optionIndex < len; optionIndex++) {
+ const candidate = options[optionIndex]
+ if (!candidate) continue
+ const editDistance = TreeUtils._getEditDistance(str, caseSensitive ? candidate : candidate.toLowerCase(), maximumEditDistanceToBeBestMatch)
+ if (editDistance < maximumEditDistanceToBeBestMatch) {
+ maximumEditDistanceToBeBestMatch = editDistance
+ closestMatch = candidate
+ }
+ }
+ return closestMatch
+ }
+ // Adapted from: https://github.com/dcporter/didyoumean.js/blob/master/didYouMean-1.2.1.js
+ static _getEditDistance(stringA, stringB, maxInt) {
+ // Handle null or undefined max.
+ maxInt = maxInt || maxInt === 0 ? maxInt : TreeUtils.MAX_INT
+ const aLength = stringA.length
+ const bLength = stringB.length
+ // Fast path - no A or B.
+ if (aLength === 0) return Math.min(maxInt + 1, bLength)
+ if (bLength === 0) return Math.min(maxInt + 1, aLength)
+ // Fast path - length diff larger than max.
+ if (Math.abs(aLength - bLength) > maxInt) return maxInt + 1
+ // Slow path.
+ const matrix = []
+ // Set up the first row ([0, 1, 2, 3, etc]).
+ for (let bIndex = 0; bIndex <= bLength; bIndex++) {
+ matrix[bIndex] = [bIndex]
+ }
+ // Set up the first column (same).
+ for (let aIndex = 0; aIndex <= aLength; aIndex++) {
+ matrix[0][aIndex] = aIndex
+ }
+ let colMin
+ let minJ
+ let maxJ
+ // Loop over the rest of the columns.
+ for (let bIndex = 1; bIndex <= bLength; bIndex++) {
+ colMin = TreeUtils.MAX_INT
+ minJ = 1
+ if (bIndex > maxInt) minJ = bIndex - maxInt
+ maxJ = bLength + 1
+ if (maxJ > maxInt + bIndex) maxJ = maxInt + bIndex
+ // Loop over the rest of the rows.
+ for (let aIndex = 1; aIndex <= aLength; aIndex++) {
+ // If j is out of bounds, just put a large value in the slot.
+ if (aIndex < minJ || aIndex > maxJ) matrix[bIndex][aIndex] = maxInt + 1
+ // Otherwise do the normal Levenshtein thing.
+ else {
+ // If the characters are the same, there's no change in edit distance.
+ if (stringB.charAt(bIndex - 1) === stringA.charAt(aIndex - 1)) matrix[bIndex][aIndex] = matrix[bIndex - 1][aIndex - 1]
+ // Otherwise, see if we're substituting, inserting or deleting.
+ else
+ matrix[bIndex][aIndex] = Math.min(
+ matrix[bIndex - 1][aIndex - 1] + 1, // Substitute
+ Math.min(
+ matrix[bIndex][aIndex - 1] + 1, // Insert
+ matrix[bIndex - 1][aIndex] + 1
+ )
+ ) // Delete
+ }
+ // Either way, update colMin.
+ if (matrix[bIndex][aIndex] < colMin) colMin = matrix[bIndex][aIndex]
+ }
+ // If this column's minimum is greater than the allowed maximum, there's no point
+ // in going on with life.
+ if (colMin > maxInt) return maxInt + 1
+ }
+ // If we made it this far without running into the max, then return the final matrix value.
+ return matrix[bLength][aLength]
+ }
+ static getLineIndexAtCharacterPosition(str, index) {
+ const lines = str.split("\n")
+ const len = lines.length
+ let position = 0
+ for (let lineNumber = 0; lineNumber < len; lineNumber++) {
+ position += lines[lineNumber].length
+ if (position >= index) return lineNumber
+ }
+ }
+ static resolvePath(filePath, programFilepath) {
+ // For use in Node.js only
+ if (!filePath.startsWith(".")) return filePath
+ const path = require("path")
+ const folder = this.getPathWithoutFileName(programFilepath)
+ return path.resolve(folder + "/" + filePath)
+ }
+ static resolveProperty(obj, path, separator = ".") {
+ const properties = Array.isArray(path) ? path : path.split(separator)
+ return properties.reduce((prev, curr) => prev && prev[curr], obj)
+ }
+ static formatStr(str, catchAllCellDelimiter = " ", parameterMap) {
+ return str.replace(/{([^\}]+)}/g, (match, path) => {
+ const val = parameterMap[path]
+ if (!val) return ""
+ return Array.isArray(val) ? val.join(catchAllCellDelimiter) : val
+ })
+ }
+ static stripHtml(text) {
+ return text && text.replace ? text.replace(/<(?:.|\n)*?>/gm, "") : text
+ }
+ static getUniqueWordsArray(allWords) {
+ const words = allWords.replace(/\n/g, " ").split(" ")
+ const index = {}
+ words.forEach(word => {
+ if (!index[word]) index[word] = 0
+ index[word]++
+ })
+ return Object.keys(index).map(key => {
+ return {
+ word: key,
+ count: index[key]
+ }
+ })
+ }
+ // todo: add seed!
+ static getRandomString(length = 30, letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")) {
+ let str = ""
+ while (length) {
+ str += letters[Math.round(Math.min(Math.random() * letters.length, letters.length - 1))]
+ length--
+ }
+ return str
+ }
+ // todo: add seed!
+ static makeRandomTree(lines = 1000) {
+ let str = ""
+ let letters = " 123abc".split("")
+ while (lines) {
+ let indent = " ".repeat(Math.round(Math.random() * 6))
+ let bit = indent
+ let rand = Math.floor(Math.random() * 30)
+ while (rand) {
+ bit += letters[Math.round(Math.min(Math.random() * letters.length, letters.length - 1))]
+ rand--
+ }
+ bit += "\n"
+ str += bit
+ lines--
+ }
+ return str
+ }
+ // adapted from https://gist.github.com/blixt/f17b47c62508be59987b
+ // 1993 Park-Miller LCG
+ static _getPRNG(seed) {
+ return function() {
+ seed = Math.imul(48271, seed) | 0 % 2147483647
+ return (seed & 2147483647) / 2147483648
+ }
+ }
+ // todo: clean up verbose/console log
+ static _tick(msg, verbose = true) {
+ if (this._tickTime === undefined) this._tickTime = Date.now() - 1000 * process.uptime()
+ const elapsed = Date.now() - this._tickTime
+ if (verbose) console.log(`${elapsed}ms ${msg}`)
+ this._tickTime = Date.now()
+ return elapsed
+ }
+ static sampleWithoutReplacement(population = [], quantity, seed) {
+ const prng = this._getPRNG(seed)
+ const sampled = {}
+ const populationSize = population.length
+ const picked = []
+ if (quantity >= populationSize) quantity = populationSize
+ while (picked.length < quantity) {
+ const index = Math.floor(prng() * populationSize)
+ if (sampled[index]) continue
+ sampled[index] = true
+ picked.push(population[index])
+ }
+ return picked
+ }
+ static arrayToMap(arr) {
+ const map = {}
+ arr.forEach(val => (map[val] = true))
+ return map
+ }
+ static _replaceNonAlphaNumericCharactersWithCharCodes(str) {
+ return str
+ .replace(/[^a-zA-Z0-9]/g, sub => {
+ return "_" + sub.charCodeAt(0).toString()
+ })
+ .replace(/^([0-9])/, "number$1")
+ }
+ static mapValues(object, fn) {
+ const result = {}
+ Object.keys(object).forEach(key => {
+ result[key] = fn(key)
+ })
+ return result
+ }
+ static javascriptTableWithHeaderRowToObjects(dataTable) {
+ dataTable = dataTable.slice()
+ const header = dataTable.shift()
+ return dataTable.map(row => {
+ const obj = {}
+ header.forEach((colName, index) => (obj[colName] = row[index]))
+ return obj
+ })
+ }
+ static makeSortByFn(accessorOrAccessors) {
+ const arrayOfFns = Array.isArray(accessorOrAccessors) ? accessorOrAccessors : [accessorOrAccessors]
+ return (objectA, objectB) => {
+ const nodeAFirst = -1
+ const nodeBFirst = 1
+ const accessor = arrayOfFns[0] // todo: handle accessors
+ const av = accessor(objectA)
+ const bv = accessor(objectB)
+ let result = av < bv ? nodeAFirst : av > bv ? nodeBFirst : 0
+ if (av === undefined && bv !== undefined) result = nodeAFirst
+ else if (bv === undefined && av !== undefined) result = nodeBFirst
+ return result
+ }
+ }
+ static _makeGraphSortFunctionFromGraph(idAccessor, graph) {
+ return (nodeA, nodeB) => {
+ const nodeAFirst = -1
+ const nodeBFirst = 1
+ const nodeAUniqueId = idAccessor(nodeA)
+ const nodeBUniqueId = idAccessor(nodeB)
+ const nodeAExtendsNodeB = graph[nodeAUniqueId].has(nodeBUniqueId)
+ const nodeBExtendsNodeA = graph[nodeBUniqueId].has(nodeAUniqueId)
+ if (nodeAExtendsNodeB) return nodeBFirst
+ else if (nodeBExtendsNodeA) return nodeAFirst
+ const nodeAExtendsSomething = graph[nodeAUniqueId].size > 1
+ const nodeBExtendsSomething = graph[nodeBUniqueId].size > 1
+ if (!nodeAExtendsSomething && nodeBExtendsSomething) return nodeAFirst
+ else if (!nodeBExtendsSomething && nodeAExtendsSomething) return nodeBFirst
+ if (nodeAUniqueId > nodeBUniqueId) return nodeBFirst
+ else if (nodeAUniqueId < nodeBUniqueId) return nodeAFirst
+ return 0
+ }
+ }
+ static _makeGraphSortFunction(idAccessor, extendsIdAccessor) {
+ return (nodeA, nodeB) => {
+ // -1 === a before b
+ const nodeAUniqueId = idAccessor(nodeA)
+ const nodeAExtends = extendsIdAccessor(nodeA)
+ const nodeBUniqueId = idAccessor(nodeB)
+ const nodeBExtends = extendsIdAccessor(nodeB)
+ const nodeAExtendsNodeB = nodeAExtends === nodeBUniqueId
+ const nodeBExtendsNodeA = nodeBExtends === nodeAUniqueId
+ const nodeAFirst = -1
+ const nodeBFirst = 1
+ if (!nodeAExtends && !nodeBExtends) {
+ // If neither extends, sort by firstWord
+ if (nodeAUniqueId > nodeBUniqueId) return nodeBFirst
+ else if (nodeAUniqueId < nodeBUniqueId) return nodeAFirst
+ return 0
+ }
+ // If only one extends, the other comes first
+ else if (!nodeAExtends) return nodeAFirst
+ else if (!nodeBExtends) return nodeBFirst
+ // If A extends B, B should come first
+ if (nodeAExtendsNodeB) return nodeBFirst
+ else if (nodeBExtendsNodeA) return nodeAFirst
+ // Sort by what they extend
+ if (nodeAExtends > nodeBExtends) return nodeBFirst
+ else if (nodeAExtends < nodeBExtends) return nodeAFirst
+ // Finally sort by firstWord
+ if (nodeAUniqueId > nodeBUniqueId) return nodeBFirst
+ else if (nodeAUniqueId < nodeBUniqueId) return nodeAFirst
+ // Should never hit this, unless we have a duplicate line.
+ return 0
+ }
+ }
+ }
+ //http://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links#21925491
+ TreeUtils.linkify = text => {
+ let replacedText
+ let replacePattern1
+ let replacePattern2
+ let replacePattern3
+ //URLs starting with http://, https://, or ftp://
+ replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim
+ replacedText = text.replace(replacePattern1, '$1')
+ //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
+ replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim
+ replacedText = replacedText.replace(replacePattern2, '$1$2')
+ //Change email addresses to mailto:: links.
+ replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim
+ replacedText = replacedText.replace(replacePattern3, '$1')
+ return replacedText
+ }
+ // todo: switch algo to: http://indiegamr.com/generate-repeatable-random-numbers-in-js/?
+ TreeUtils.makeSemiRandomFn = (seed = 1) => {
+ return () => {
+ const semiRand = Math.sin(seed++) * 10000
+ return semiRand - Math.floor(semiRand)
+ }
+ }
+ TreeUtils.MAX_INT = Math.pow(2, 32) - 1
+ window.TreeUtils = TreeUtils
+ let _jtreeLatestTime = 0
+ let _jtreeMinTimeIncrement = 0.000000000001
+ class AbstractNode {
+ _getProcessTimeInMilliseconds() {
+ // We add this loop to restore monotonically increasing .now():
+ // https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
+ let time = performance.now()
+ while (time <= _jtreeLatestTime) {
+ if (time === time + _jtreeMinTimeIncrement)
+ // Some browsers have different return values for perf.now()
+ _jtreeMinTimeIncrement = 10 * _jtreeMinTimeIncrement
+ time += _jtreeMinTimeIncrement
+ }
+ _jtreeLatestTime = time
+ return time
+ }
+ }
+ var FileFormat
+ ;(function(FileFormat) {
+ FileFormat["csv"] = "csv"
+ FileFormat["tsv"] = "tsv"
+ FileFormat["tree"] = "tree"
+ })(FileFormat || (FileFormat = {}))
+ var WhereOperators
+ ;(function(WhereOperators) {
+ WhereOperators["equal"] = "="
+ WhereOperators["notEqual"] = "!="
+ WhereOperators["lessThan"] = "<"
+ WhereOperators["lessThanOrEqual"] = "<="
+ WhereOperators["greaterThan"] = ">"
+ WhereOperators["greaterThanOrEqual"] = ">="
+ WhereOperators["includes"] = "includes"
+ WhereOperators["doesNotInclude"] = "doesNotInclude"
+ WhereOperators["in"] = "in"
+ WhereOperators["notIn"] = "notIn"
+ WhereOperators["empty"] = "empty"
+ WhereOperators["notEmpty"] = "notEmpty"
+ })(WhereOperators || (WhereOperators = {}))
+ var TreeNotationConstants
+ ;(function(TreeNotationConstants) {
+ TreeNotationConstants["extends"] = "extends"
+ })(TreeNotationConstants || (TreeNotationConstants = {}))
+ class Parser {
+ constructor(catchAllNodeConstructor, firstWordMap = {}, regexTests = undefined) {
+ this._catchAllNodeConstructor = catchAllNodeConstructor
+ this._firstWordMap = firstWordMap
+ this._regexTests = regexTests
+ }
+ getFirstWordOptions() {
+ return Object.keys(this._firstWordMap)
+ }
+ // todo: remove
+ _getFirstWordMap() {
+ return this._firstWordMap
+ }
+ _getNodeConstructor(line, contextNode, zi = " ") {
+ return this._firstWordMap[this._getFirstWord(line, zi)] || this._getConstructorFromRegexTests(line) || this._getCatchAllNodeConstructor(contextNode)
+ }
+ _getCatchAllNodeConstructor(contextNode) {
+ if (this._catchAllNodeConstructor) return this._catchAllNodeConstructor
+ const parent = contextNode.getParent()
+ if (parent) return parent._getParser()._getCatchAllNodeConstructor(parent)
+ return contextNode.constructor
+ }
+ _getConstructorFromRegexTests(line) {
+ if (!this._regexTests) return undefined
+ const hit = this._regexTests.find(test => test.regex.test(line))
+ if (hit) return hit.nodeConstructor
+ return undefined
+ }
+ _getFirstWord(line, zi) {
+ const firstBreak = line.indexOf(zi)
+ return line.substr(0, firstBreak > -1 ? firstBreak : undefined)
+ }
+ }
+ class TreeNode extends AbstractNode {
+ constructor(children, line, parent) {
+ super()
+ this._parent = parent
+ this._setLine(line)
+ this._setChildren(children)
+ }
+ execute(context) {
+ return Promise.all(this.map(child => child.execute(context)))
+ }
+ getErrors() {
+ return []
+ }
+ getLineCellTypes() {
+ // todo: make this any a constant
+ return "undefinedCellType ".repeat(this.getWords().length).trim()
+ }
+ executeSync(context) {
+ return this.map(child => child.executeSync(context))
+ }
+ isNodeJs() {
+ return typeof exports !== "undefined"
+ }
+ isBrowser() {
+ return !this.isNodeJs()
+ }
+ getOlderSiblings() {
+ if (this.isRoot()) return []
+ return this.getParent().slice(0, this.getIndex())
+ }
+ _getClosestOlderSibling() {
+ const olderSiblings = this.getOlderSiblings()
+ return olderSiblings[olderSiblings.length - 1]
+ }
+ getYoungerSiblings() {
+ if (this.isRoot()) return []
+ return this.getParent().slice(this.getIndex() + 1)
+ }
+ getSiblings() {
+ if (this.isRoot()) return []
+ return this.getParent().filter(node => node !== this)
+ }
+ _getUid() {
+ if (!this._uid) this._uid = TreeNode._makeUniqueId()
+ return this._uid
+ }
+ // todo: rename getMother? grandMother et cetera?
+ getParent() {
+ return this._parent
+ }
+ getPoint() {
+ return this._getPoint()
+ }
+ _getPoint(relativeTo) {
+ return {
+ x: this._getXCoordinate(relativeTo),
+ y: this._getYCoordinate(relativeTo)
+ }
+ }
+ getPointRelativeTo(relativeTo) {
+ return this._getPoint(relativeTo)
+ }
+ getIndentation(relativeTo) {
+ return this.getXI().repeat(this._getXCoordinate(relativeTo) - 1)
+ }
+ _getTopDownArray(arr) {
+ this.forEach(child => {
+ arr.push(child)
+ child._getTopDownArray(arr)
+ })
+ }
+ getTopDownArray() {
+ const arr = []
+ this._getTopDownArray(arr)
+ return arr
+ }
+ *getTopDownArrayIterator() {
+ for (let child of this.getChildren()) {
+ yield child
+ yield* child.getTopDownArrayIterator()
+ }
+ }
+ nodeAtLine(lineNumber) {
+ let index = 0
+ for (let node of this.getTopDownArrayIterator()) {
+ if (lineNumber === index) return node
+ index++
+ }
+ }
+ getNumberOfLines() {
+ let lineCount = 0
+ for (let node of this.getTopDownArrayIterator()) {
+ lineCount++
+ }
+ return lineCount
+ }
+ _getLineNumber(target = this) {
+ if (this._cachedLineNumber) return this._cachedLineNumber
+ let lineNumber = 1
+ for (let node of this.getRootNode().getTopDownArrayIterator()) {
+ if (node === target) return lineNumber
+ lineNumber++
+ }
+ return lineNumber
+ }
+ isBlankLine() {
+ return !this.length && !this.getLine()
+ }
+ hasDuplicateFirstWords() {
+ return this.length ? new Set(this.getFirstWords()).size !== this.length : false
+ }
+ isEmpty() {
+ return !this.length && !this.getContent()
+ }
+ _getYCoordinate(relativeTo) {
+ if (this.isRoot(relativeTo)) return 0
+ const start = relativeTo || this.getRootNode()
+ return start._getLineNumber(this)
+ }
+ isRoot(relativeTo) {
+ return relativeTo === this || !this.getParent()
+ }
+ getRootNode() {
+ return this._getRootNode()
+ }
+ _getRootNode(relativeTo) {
+ if (this.isRoot(relativeTo)) return this
+ return this.getParent()._getRootNode(relativeTo)
+ }
+ toString(indentCount = 0, language = this) {
+ if (this.isRoot()) return this._childrenToString(indentCount, language)
+ return (
+ language.getXI().repeat(indentCount) + this.getLine(language) + (this.length ? language.getYI() + this._childrenToString(indentCount + 1, language) : "")
+ )
+ }
+ printLinesFrom(start, quantity) {
+ return this._printLinesFrom(start, quantity, false)
+ }
+ printLinesWithLineNumbersFrom(start, quantity) {
+ return this._printLinesFrom(start, quantity, true)
+ }
+ _printLinesFrom(start, quantity, printLineNumbers) {
+ // todo: use iterator for better perf?
+ const end = start + quantity
+ this.toString()
+ .split("\n")
+ .slice(start, end)
+ .forEach((line, index) => {
+ if (printLineNumbers) console.log(`${start + index} ${line}`)
+ else console.log(line)
+ })
+ return this
+ }
+ getWord(index) {
+ const words = this._getLine().split(this.getZI())
+ if (index < 0) index = words.length + index
+ return words[index]
+ }
+ _toHtml(indentCount) {
+ const path = this.getPathVector().join(" ")
+ const classes = {
+ nodeLine: "nodeLine",
+ xi: "xIncrement",
+ yi: "yIncrement",
+ nodeChildren: "nodeChildren"
+ }
+ const edge = this.getXI().repeat(indentCount)
+ // Set up the firstWord part of the node
+ const edgeHtml = `${edge}`
+ const lineHtml = this._getLineHtml()
+ const childrenHtml = this.length
+ ? `${this.getYI()}` + `${this._childrenToHtml(indentCount + 1)}`
+ : ""
+ return `${edgeHtml}${lineHtml}${childrenHtml}`
+ }
+ _getWords(startFrom) {
+ if (!this._words) this._words = this._getLine().split(this.getZI())
+ return startFrom ? this._words.slice(startFrom) : this._words
+ }
+ getWords() {
+ return this._getWords(0)
+ }
+ doesExtend(nodeTypeId) {
+ return false
+ }
+ require(moduleName, filePath) {
+ if (this.isNodeJs()) return require(filePath || moduleName)
+ return windowoduleName]
+ }
+ getWordsFrom(startFrom) {
+ return this._getWords(startFrom)
+ }
+ getSparsity() {
+ const nodes = this.getChildren()
+ const fields = this._getUnionNames()
+ let count = 0
+ this.getChildren().forEach(node => {
+ fields.forEach(field => {
+ if (node.has(field)) count++
+ })
+ })
+ return 1 - count / (nodes.length * fields.length)
+ }
+ // todo: rename. what is the proper term from set/cat theory?
+ getBiDirectionalMaps(propertyNameOrFn, propertyNameOrFn2 = node => node.getWord(0)) {
+ const oneToTwo = {}
+ const twoToOne = {}
+ const is1Str = typeof propertyNameOrFn === "string"
+ const is2Str = typeof propertyNameOrFn2 === "string"
+ const children = this.getChildren()
+ this.forEach((node, index) => {
+ const value1 = is1Str ? node.get(propertyNameOrFn) : propertyNameOrFn(node, index, children)
+ const value2 = is2Str ? node.get(propertyNameOrFn2) : propertyNameOrFn2(node, index, children)
+ if (value1 !== undefined) {
+ if (!oneToTwo[value1]) oneToTwo[value1] = []
+ oneToTwo[value1].push(value2)
+ }
+ if (value2 !== undefined) {
+ if (!twoToOne[value2]) twoToOne[value2] = []
+ twoToOne[value2].push(value1)
+ }
+ })
+ return [oneToTwo, twoToOne]
+ }
+ _getWordIndexCharacterStartPosition(wordIndex) {
+ const xiLength = this.getXI().length
+ const numIndents = this._getXCoordinate(undefined) - 1
+ const indentPosition = xiLength * numIndents
+ if (wordIndex < 1) return xiLength * (numIndents + wordIndex)
+ return (
+ indentPosition +
+ this.getWords()
+ .slice(0, wordIndex)
+ .join(this.getZI()).length +
+ this.getZI().length
+ )
+ }
+ getNodeInScopeAtCharIndex(charIndex) {
+ if (this.isRoot()) return this
+ let wordIndex = this.getWordIndexAtCharacterIndex(charIndex)
+ if (wordIndex > 0) return this
+ let node = this
+ while (wordIndex < 1) {
+ node = node.getParent()
+ wordIndex++
+ }
+ return node
+ }
+ getWordProperties(wordIndex) {
+ const start = this._getWordIndexCharacterStartPosition(wordIndex)
+ const word = wordIndex < 0 ? "" : this.getWord(wordIndex)
+ return {
+ startCharIndex: start,
+ endCharIndex: start + word.length,
+ word: word
+ }
+ }
+ getAllWordBoundaryCoordinates() {
+ const coordinates = []
+ let line = 0
+ for (let node of this.getTopDownArrayIterator()) {
+ node.getWordBoundaryIndices().forEach(index => {
+ coordinates.push({
+ y: line,
+ x: index
+ })
+ })
+ line++
+ }
+ return coordinates
+ }
+ getWordBoundaryIndices() {
+ const boundaries = [0]
+ let numberOfIndents = this._getXCoordinate(undefined) - 1
+ let start = numberOfIndents
+ // Add indents
+ while (numberOfIndents) {
+ boundaries.push(boundaries.length)
+ numberOfIndents--
+ }
+ // Add columns
+ const ziIncrement = this.getZI().length
+ this.getWords().forEach(word => {
+ if (boundaries[boundaries.length - 1] !== start) boundaries.push(start)
+ start += word.length
+ if (boundaries[boundaries.length - 1] !== start) boundaries.push(start)
+ start += ziIncrement
+ })
+ return boundaries
+ }
+ getWordIndexAtCharacterIndex(charIndex) {
+ // todo: is this correct thinking for handling root?
+ if (this.isRoot()) return 0
+ const numberOfIndents = this._getXCoordinate(undefined) - 1
+ // todo: probably want to rewrite this in a performant way.
+ const spots = []
+ while (spots.length < numberOfIndents) {
+ spots.push(-(numberOfIndents - spots.length))
+ }
+ this.getWords().forEach((word, wordIndex) => {
+ word.split("").forEach(letter => {
+ spots.push(wordIndex)
+ })
+ spots.push(wordIndex)
+ })
+ return spots[charIndex]
+ }
+ getAllErrors(lineStartsAt = 1) {
+ const errors = []
+ for (let node of this.getTopDownArray()) {
+ node._cachedLineNumber = lineStartsAt // todo: cleanup
+ const errs = node.getErrors()
+ errs.forEach(err => errors.push(err))
+ // delete node._cachedLineNumber
+ lineStartsAt++
+ }
+ return errors
+ }
+ *getAllErrorsIterator() {
+ let line = 1
+ for (let node of this.getTopDownArrayIterator()) {
+ node._cachedLineNumber = line
+ const errs = node.getErrors()
+ // delete node._cachedLineNumber
+ if (errs.length) yield errs
+ line++
+ }
+ }
+ getFirstWord() {
+ return this.getWords()[0]
+ }
+ getContent() {
+ const words = this.getWordsFrom(1)
+ return words.length ? words.join(this.getZI()) : undefined
+ }
+ getContentWithChildren() {
+ // todo: deprecate
+ const content = this.getContent()
+ return (content ? content : "") + (this.length ? this.getYI() + this._childrenToString() : "")
+ }
+ getFirstNode() {
+ return this.nodeAt(0)
+ }
+ getStack() {
+ return this._getStack()
+ }
+ _getStack(relativeTo) {
+ if (this.isRoot(relativeTo)) return []
+ const parent = this.getParent()
+ if (parent.isRoot(relativeTo)) return [this]
+ else return parent._getStack(relativeTo).concat([this])
+ }
+ getStackString() {
+ return this._getStack()
+ .map((node, index) => this.getXI().repeat(index) + node.getLine())
+ .join(this.getYI())
+ }
+ getLine(language) {
+ if (!this._words && !language) return this._getLine() // todo: how does this interact with "language" param?
+ return this.getWords().join((language || this).getZI())
+ }
+ getColumnNames() {
+ return this._getUnionNames()
+ }
+ getOneHot(column) {
+ const clone = this.clone()
+ const cols = Array.from(new Set(clone.getColumn(column)))
+ clone.forEach(node => {
+ const val = node.get(column)
+ node.delete(column)
+ cols.forEach(col => {
+ node.set(column + "_" + col, val === col ? "1" : "0")
+ })
+ })
+ return clone
+ }
+ // todo: return array? getPathArray?
+ _getFirstWordPath(relativeTo) {
+ if (this.isRoot(relativeTo)) return ""
+ else if (this.getParent().isRoot(relativeTo)) return this.getFirstWord()
+ return this.getParent()._getFirstWordPath(relativeTo) + this.getXI() + this.getFirstWord()
+ }
+ getFirstWordPathRelativeTo(relativeTo) {
+ return this._getFirstWordPath(relativeTo)
+ }
+ getFirstWordPath() {
+ return this._getFirstWordPath()
+ }
+ getPathVector() {
+ return this._getPathVector()
+ }
+ getPathVectorRelativeTo(relativeTo) {
+ return this._getPathVector(relativeTo)
+ }
+ _getPathVector(relativeTo) {
+ if (this.isRoot(relativeTo)) return []
+ const path = this.getParent()._getPathVector(relativeTo)
+ path.push(this.getIndex())
+ return path
+ }
+ getIndex() {
+ return this.getParent()._indexOfNode(this)
+ }
+ isTerminal() {
+ return !this.length
+ }
+ _getLineHtml() {
+ return this.getWords()
+ .map((word, index) => `${TreeUtils.stripHtml(word)}`)
+ .join(`${this.getZI()}`)
+ }
+ _getXmlContent(indentCount) {
+ if (this.getContent() !== undefined) return this.getContentWithChildren()
+ return this.length ? `${indentCount === -1 ? "" : "\n"}${this._childrenToXml(indentCount > -1 ? indentCount + 2 : -1)}${" ".repeat(indentCount)}` : ""
+ }
+ _toXml(indentCount) {
+ const indent = " ".repeat(indentCount)
+ const tag = this.getFirstWord()
+ return `${indent}<${tag}>${this._getXmlContent(indentCount)}${indentCount === -1 ? "" : "\n"}`
+ }
+ _toObjectTuple() {
+ const content = this.getContent()
+ const length = this.length
+ const hasChildrenNoContent = content === undefined && length
+ const hasContentAndHasChildren = content !== undefined && length
+ // If the node has a content and a subtree return it as a string, as
+ // Javascript object values can't be both a leaf and a tree.
+ const tupleValue = hasChildrenNoContent ? this.toObject() : hasContentAndHasChildren ? this.getContentWithChildren() : content
+ return [this.getFirstWord(), tupleValue]
+ }
+ _indexOfNode(needleNode) {
+ let result = -1
+ this.find((node, index) => {
+ if (node === needleNode) {
+ result = index
+ return true
+ }
+ })
+ return result
+ }
+ getSlice(startIndexInclusive, stopIndexExclusive) {
+ return new TreeNode(
+ this.slice(startIndexInclusive, stopIndexExclusive)
+ .map(child => child.toString())
+ .join("\n")
+ )
+ }
+ _hasColumns(columns) {
+ const words = this.getWords()
+ return columns.every((searchTerm, index) => searchTerm === words[index])
+ }
+ hasWord(index, word) {
+ return this.getWord(index) === word
+ }
+ getNodeByColumns(...columns) {
+ return this.getTopDownArray().find(node => node._hasColumns(columns))
+ }
+ getNodeByColumn(index, name) {
+ return this.find(node => node.getWord(index) === name)
+ }
+ _getNodesByColumn(index, name) {
+ return this.filter(node => node.getWord(index) === name)
+ }
+ // todo: preserve subclasses!
+ select(columnNames) {
+ columnNames = Array.isArray(columnNames) ? columnNames : [columnNames]
+ const result = new TreeNode()
+ this.forEach(node => {
+ const tree = result.appendLine(node.getLine())
+ columnNames.forEach(name => {
+ const valueNode = node.getNode(name)
+ if (valueNode) tree.appendNode(valueNode)
+ })
+ })
+ return result
+ }
+ // Note: this is for debugging select chains
+ print(message = "") {
+ if (message) console.log(message)
+ console.log(this.toString())
+ return this
+ }
+ // todo: preserve subclasses!
+ where(columnName, operator, fixedValue) {
+ const isArray = Array.isArray(fixedValue)
+ const valueType = isArray ? typeof fixedValue[0] : typeof fixedValue
+ let parser
+ if (valueType === "number") parser = parseFloat
+ const fn = node => {
+ const cell = node.get(columnName)
+ const typedCell = parser ? parser(cell) : cell
+ if (operator === WhereOperators.equal) return fixedValue === typedCell
+ else if (operator === WhereOperators.notEqual) return fixedValue !== typedCell
+ else if (operator === WhereOperators.includes) return typedCell !== undefined && typedCell.includes(fixedValue)
+ else if (operator === WhereOperators.doesNotInclude) return typedCell === undefined || !typedCell.includes(fixedValue)
+ else if (operator === WhereOperators.greaterThan) return typedCell > fixedValue
+ else if (operator === WhereOperators.lessThan) return typedCell < fixedValue
+ else if (operator === WhereOperators.greaterThanOrEqual) return typedCell >= fixedValue
+ else if (operator === WhereOperators.lessThanOrEqual) return typedCell <= fixedValue
+ else if (operator === WhereOperators.empty) return !node.has(columnName)
+ else if (operator === WhereOperators.notEmpty) return node.has(columnName)
+ else if (operator === WhereOperators.in && isArray) return fixedValue.includes(typedCell)
+ else if (operator === WhereOperators.notIn && isArray) return !fixedValue.includes(typedCell)
+ }
+ const result = new TreeNode()
+ this.filter(fn).forEach(node => {
+ result.appendNode(node)
+ })
+ return result
+ }
+ with(firstWord) {
+ return this.filter(node => node.has(firstWord))
+ }
+ first(quantity = 1) {
+ return this.limit(quantity, 0)
+ }
+ last(quantity = 1) {
+ return this.limit(quantity, this.length - quantity)
+ }
+ // todo: preserve subclasses!
+ limit(quantity, offset = 0) {
+ const result = new TreeNode()
+ this.getChildren()
+ .slice(offset, quantity + offset)
+ .forEach(node => {
+ result.appendNode(node)
+ })
+ return result
+ }
+ getChildrenFirstArray() {
+ const arr = []
+ this._getChildrenFirstArray(arr)
+ return arr
+ }
+ _getChildrenFirstArray(arr) {
+ this.forEach(child => {
+ child._getChildrenFirstArray(arr)
+ arr.push(child)
+ })
+ }
+ _getXCoordinate(relativeTo) {
+ return this._getStack(relativeTo).length
+ }
+ getParentFirstArray() {
+ const levels = this._getLevels()
+ const arr = []
+ Object.values(levels).forEach(level => {
+ level.forEach(item => arr.push(item))
+ })
+ return arr
+ }
+ _getLevels() {
+ const levels = {}
+ this.getTopDownArray().forEach(node => {
+ const level = node._getXCoordinate()
+ if (!levels[level]) levels[level] = []
+ levels[level].push(node)
+ })
+ return levels
+ }
+ _getChildrenArray() {
+ if (!this._children) this._children = []
+ return this._children
+ }
+ _getChildren() {
+ return this._getChildrenArray()
+ }
+ getLines() {
+ return this.map(node => node.getLine())
+ }
+ getChildren() {
+ return this._getChildren().slice(0)
+ }
+ get length() {
+ return this._getChildren().length
+ }
+ _nodeAt(index) {
+ if (index < 0) index = this.length + index
+ return this._getChildren()[index]
+ }
+ nodeAt(indexOrIndexArray) {
+ if (typeof indexOrIndexArray === "number") return this._nodeAt(indexOrIndexArray)
+ if (indexOrIndexArray.length === 1) return this._nodeAt(indexOrIndexArray[0])
+ const first = indexOrIndexArray[0]
+ const node = this._nodeAt(first)
+ if (!node) return undefined
+ return node.nodeAt(indexOrIndexArray.slice(1))
+ }
+ _toObject() {
+ const obj = {}
+ this.forEach(node => {
+ const tuple = node._toObjectTuple()
+ obj[tuple[0]] = tuple[1]
+ })
+ return obj
+ }
+ toHtml() {
+ return this._childrenToHtml(0)
+ }
+ _getHtmlJoinByCharacter() {
+ return `${this.getYI()}`
+ }
+ _childrenToHtml(indentCount) {
+ const joinBy = this._getHtmlJoinByCharacter()
+ return this.map(node => node._toHtml(indentCount)).join(joinBy)
+ }
+ _childrenToString(indentCount, language = this) {
+ return this.map(node => node.toString(indentCount, language)).join(language.getYI())
+ }
+ childrenToString(indentCount = 0) {
+ return this._childrenToString(indentCount)
+ }
+ // todo: implement
+ _getChildJoinCharacter() {
+ return "\n"
+ }
+ compile() {
+ return this.map(child => child.compile()).join(this._getChildJoinCharacter())
+ }
+ toXml() {
+ return this._childrenToXml(0)
+ }
+ toDisk(path) {
+ if (!this.isNodeJs()) throw new Error("This method only works in Node.js")
+ const format = TreeNode._getFileFormat(path)
+ const formats = {
+ tree: tree => tree.toString(),
+ csv: tree => tree.toCsv(),
+ tsv: tree => tree.toTsv()
+ }
+ this.require("fs").writeFileSync(path, formats[format](this), "utf8")
+ return this
+ }
+ _lineToYaml(indentLevel, listTag = "") {
+ let prefix = " ".repeat(indentLevel)
+ if (listTag && indentLevel > 1) prefix = " ".repeat(indentLevel - 2) + listTag + " "
+ return prefix + `${this.getFirstWord()}:` + (this.getContent() ? " " + this.getContent() : "")
+ }
+ _isYamlList() {
+ return this.hasDuplicateFirstWords()
+ }
+ toYaml() {
+ return `%YAML 1.2
+ ---\n${this._childrenToYaml(0).join("\n")}`
+ }
+ _childrenToYaml(indentLevel) {
+ if (this._isYamlList()) return this._childrenToYamlList(indentLevel)
+ else return this._childrenToYamlAssociativeArray(indentLevel)
+ }
+ // if your code-to-be-yaml has a list of associative arrays of type N and you don't
+ // want the type N to print
+ _collapseYamlLine() {
+ return false
+ }
+ _toYamlListElement(indentLevel) {
+ const children = this._childrenToYaml(indentLevel + 1)
+ if (this._collapseYamlLine()) {
+ if (indentLevel > 1) return children.join("\n").replace(" ".repeat(indentLevel), " ".repeat(indentLevel - 2) + "- ")
+ return children.join("\n")
+ } else {
+ children.unshift(this._lineToYaml(indentLevel, "-"))
+ return children.join("\n")
+ }
+ }
+ _childrenToYamlList(indentLevel) {
+ return this.map(node => node._toYamlListElement(indentLevel + 2))
+ }
+ _toYamlAssociativeArrayElement(indentLevel) {
+ const children = this._childrenToYaml(indentLevel + 1)
+ children.unshift(this._lineToYaml(indentLevel))
+ return children.join("\n")
+ }
+ _childrenToYamlAssociativeArray(indentLevel) {
+ return this.map(node => node._toYamlAssociativeArrayElement(indentLevel))
+ }
+ toJsonSubset() {
+ return JSON.stringify(this.toObject(), null, " ")
+ }
+ findNodes(firstWordPath) {
+ // todo: can easily speed this up
+ const map = {}
+ if (!Array.isArray(firstWordPath)) firstWordPath = [firstWordPath]
+ firstWordPath.forEach(path => (map[path] = true))
+ return this.getTopDownArray().filter(node => {
+ if (map[node._getFirstWordPath(this)]) return true
+ return false
+ })
+ }
+ format(str) {
+ const that = this
+ return str.replace(/{([^\}]+)}/g, (match, path) => that.get(path) || "")
+ }
+ getColumn(path) {
+ return this.map(node => node.get(path))
+ }
+ getFiltered(fn) {
+ const clone = this.clone()
+ clone
+ .filter((node, index) => !fn(node, index))
+ .forEach(node => {
+ node.destroy()
+ })
+ return clone
+ }
+ getNode(firstWordPath) {
+ return this._getNodeByPath(firstWordPath)
+ }
+ get(firstWordPath) {
+ const node = this._getNodeByPath(firstWordPath)
+ return node === undefined ? undefined : node.getContent()
+ }
+ getNodesByGlobPath(query) {
+ return this._getNodesByGlobPath(query)
+ }
+ _getNodesByGlobPath(globPath) {
+ const xi = this.getXI()
+ if (!globPath.includes(xi)) {
+ if (globPath === "*") return this.getChildren()
+ return this.filter(node => node.getFirstWord() === globPath)
+ }
+ const parts = globPath.split(xi)
+ const current = parts.shift()
+ const rest = parts.join(xi)
+ const matchingNodes = current === "*" ? this.getChildren() : this.filter(child => child.getFirstWord() === current)
+ return [].concat.apply([], matchingNodes.map(node => node._getNodesByGlobPath(rest)))
+ }
+ _getNodeByPath(firstWordPath) {
+ const xi = this.getXI()
+ if (!firstWordPath.includes(xi)) {
+ const index = this.indexOfLast(firstWordPath)
+ return index === -1 ? undefined : this._nodeAt(index)
+ }
+ const parts = firstWordPath.split(xi)
+ const current = parts.shift()
+ const currentNode = this._getChildren()[this._getIndex()[current]]
+ return currentNode ? currentNode._getNodeByPath(parts.join(xi)) : undefined
+ }
+ getNext() {
+ if (this.isRoot()) return this
+ const index = this.getIndex()
+ const parent = this.getParent()
+ const length = parent.length
+ const next = index + 1
+ return next === length ? parent._getChildren()[0] : parent._getChildren()[next]
+ }
+ getPrevious() {
+ if (this.isRoot()) return this
+ const index = this.getIndex()
+ const parent = this.getParent()
+ const length = parent.length
+ const prev = index - 1
+ return prev === -1 ? parent._getChildren()[length - 1] : parent._getChildren()[prev]
+ }
+ _getUnionNames() {
+ if (!this.length) return []
+ const obj = {}
+ this.forEach(node => {
+ if (!node.length) return undefined
+ node.forEach(node => {
+ obj[node.getFirstWord()] = 1
+ })
+ })
+ return Object.keys(obj)
+ }
+ getAncestorNodesByInheritanceViaExtendsKeyword(key) {
+ const ancestorNodes = this._getAncestorNodes((node, id) => node._getNodesByColumn(0, id), node => node.get(key), this)
+ ancestorNodes.push(this)
+ return ancestorNodes
+ }
+ // Note: as you can probably tell by the name of this method, I don't recommend using this as it will likely be replaced by something better.
+ getAncestorNodesByInheritanceViaColumnIndices(thisColumnNumber, extendsColumnNumber) {
+ const ancestorNodes = this._getAncestorNodes((node, id) => node._getNodesByColumn(thisColumnNumber, id), node => node.getWord(extendsColumnNumber), this)
+ ancestorNodes.push(this)
+ return ancestorNodes
+ }
+ _getAncestorNodes(getPotentialParentNodesByIdFn, getParentIdFn, cannotContainNode) {
+ const parentId = getParentIdFn(this)
+ if (!parentId) return []
+ const potentialParentNodes = getPotentialParentNodesByIdFn(this.getParent(), parentId)
+ if (!potentialParentNodes.length) throw new Error(`"${this.getLine()} tried to extend "${parentId}" but "${parentId}" not found.`)
+ if (potentialParentNodes.length > 1) throw new Error(`Invalid inheritance family tree. Multiple unique ids found for "${parentId}"`)
+ const parentNode = potentialParentNodes[0]
+ // todo: detect loops
+ if (parentNode === cannotContainNode) throw new Error(`Loop detected between '${this.getLine()}' and '${parentNode.getLine()}'`)
+ const ancestorNodes = parentNode._getAncestorNodes(getPotentialParentNodesByIdFn, getParentIdFn, cannotContainNode)
+ ancestorNodes.push(parentNode)
+ return ancestorNodes
+ }
+ pathVectorToFirstWordPath(pathVector) {
+ const path = pathVector.slice() // copy array
+ const names = []
+ let node = this
+ while (path.length) {
+ if (!node) return names
+ names.push(node.nodeAt(path[0]).getFirstWord())
+ node = node.nodeAt(path.shift())
+ }
+ return names
+ }
+ toCsv() {
+ return this.toDelimited(",")
+ }
+ _getTypes(header) {
+ const matrix = this._getMatrix(header)
+ const types = header.map(i => "int")
+ matrix.forEach(row => {
+ row.forEach((value, index) => {
+ const type = types[index]
+ if (type === "string") return 1
+ if (value === undefined || value === "") return 1
+ if (type === "float") {
+ if (value.match(/^\-?[0-9]*\.?[0-9]*$/)) return 1
+ types[index] = "string"
+ }
+ if (value.match(/^\-?[0-9]+$/)) return 1
+ types[index] = "string"
+ })
+ })
+ return types
+ }
+ toDataTable(header = this._getUnionNames()) {
+ const types = this._getTypes(header)
+ const parsers = {
+ string: str => str,
+ float: parseFloat,
+ int: parseInt
+ }
+ const cellFn = (cellValue, rowIndex, columnIndex) => (rowIndex ? parsers[types[columnIndex]](cellValue) : cellValue)
+ const arrays = this._toArrays(header, cellFn)
+ arrays.rows.unshift(arrays.header)
+ return arrays.rows
+ }
+ toDelimited(delimiter, header = this._getUnionNames()) {
+ const regex = new RegExp(`(\\n|\\"|\\${delimiter})`)
+ const cellFn = (str, row, column) => (!str.toString().match(regex) ? str : `"` + str.replace(/\"/g, `""`) + `"`)
+ return this._toDelimited(delimiter, header, cellFn)
+ }
+ _getMatrix(columns) {
+ const matrix = []
+ this.forEach(child => {
+ const row = []
+ columns.forEach(col => {
+ row.push(child.get(col))
+ })
+ matrix.push(row)
+ })
+ return matrix
+ }
+ _toArrays(header, cellFn) {
+ const skipHeaderRow = 1
+ const headerArray = header.map((columnName, index) => cellFn(columnName, 0, index))
+ const rows = this.map((node, rowNumber) =>
+ header.map((columnName, columnIndex) => {
+ const childNode = node.getNode(columnName)
+ const content = childNode ? childNode.getContentWithChildren() : ""
+ return cellFn(content, rowNumber + skipHeaderRow, columnIndex)
+ })
+ )
+ return {
+ rows: rows,
+ header: headerArray
+ }
+ }
+ _toDelimited(delimiter, header, cellFn) {
+ const data = this._toArrays(header, cellFn)
+ return data.header.join(delimiter) + "\n" + data.rows.map(row => row.join(delimiter)).join("\n")
+ }
+ toTable() {
+ // Output a table for printing
+ return this._toTable(100, false)
+ }
+ toFormattedTable(maxCharactersPerColumn, alignRight = false) {
+ return this._toTable(maxCharactersPerColumn, alignRight)
+ }
+ _toTable(maxCharactersPerColumn, alignRight = false) {
+ const header = this._getUnionNames()
+ // Set initial column widths
+ const widths = header.map(col => (col.length > maxCharactersPerColumn ? maxCharactersPerColumn : col.length))
+ // Expand column widths if needed
+ this.forEach(node => {
+ if (!node.length) return true
+ header.forEach((col, index) => {
+ const cellValue = node.get(col)
+ if (!cellValue) return true
+ const length = cellValue.toString().length
+ if (length > widths[index]) widths[index] = length > maxCharactersPerColumn ? maxCharactersPerColumn : length
+ })
+ })
+ const cellFn = (cellText, row, col) => {
+ const width = widths[col]
+ // Strip newlines in fixedWidth output
+ const cellValue = cellText.toString().replace(/\n/g, "\\n")
+ const cellLength = cellValue.length
+ if (cellLength > width) return cellValue.substr(0, width) + "..."
+ const padding = " ".repeat(width - cellLength)
+ return alignRight ? padding + cellValue : cellValue + padding
+ }
+ return this._toDelimited(" ", header, cellFn)
+ }
+ toSsv() {
+ return this.toDelimited(" ")
+ }
+ toOutline() {
+ return this._toOutline(node => node.getLine())
+ }
+ toMappedOutline(nodeFn) {
+ return this._toOutline(nodeFn)
+ }
+ // Adapted from: https://github.com/notatestuser/treeify.js
+ _toOutline(nodeFn) {
+ const growBranch = (outlineTreeNode, last, lastStates, nodeFn, callback) => {
+ let lastStatesCopy = lastStates.slice(0)
+ const node = outlineTreeNode.node
+ if (lastStatesCopy.push([outlineTreeNode, last]) && lastStates.length > 0) {
+ let line = ""
+ // firstWordd on the "was last element" states of whatever we're nested within,
+ // we need to append either blankness or a branch to our line
+ lastStates.forEach((lastState, idx) => {
+ if (idx > 0) line += lastState[1] ? " " : "│"
+ })
+ // the prefix varies firstWordd on whether the key contains something to show and
+ // whether we're dealing with the last element in this collection
+ // the extra "-" just makes things stand out more.
+ line += (last ? "└" : "├") + nodeFn(node)
+ callback(line)
+ }
+ if (!node) return
+ const length = node.length
+ let index = 0
+ node.forEach(node => {
+ let lastKey = ++index === length
+ growBranch({ node: node }, lastKey, lastStatesCopy, nodeFn, callback)
+ })
+ }
+ let output = ""
+ growBranch({ node: this }, false, [], nodeFn, line => (output += line + "\n"))
+ return output
+ }
+ copyTo(node, index) {
+ return node._setLineAndChildren(this.getLine(), this.childrenToString(), index)
+ }
+ // Note: Splits using a positive lookahead
+ // this.split("foo").join("\n") === this.toString()
+ split(firstWord) {
+ const constructor = this.constructor
+ const YI = this.getYI()
+ const ZI = this.getZI()
+ // todo: cleanup. the escaping is wierd.
+ return this.toString()
+ .split(new RegExp(`\\${YI}(?=${firstWord}(?:${ZI}|\\${YI}))`, "g"))
+ .map(str => new constructor(str))
+ }
+ toMarkdownTable() {
+ return this.toMarkdownTableAdvanced(this._getUnionNames(), val => val)
+ }
+ toMarkdownTableAdvanced(columns, formatFn) {
+ const matrix = this._getMatrix(columns)
+ const empty = columns.map(col => "-")
+ matrix.unshift(empty)
+ matrix.unshift(columns)
+ const lines = matrix.map((row, rowIndex) => {
+ const formattedValues = row.map((val, colIndex) => formatFn(val, rowIndex, colIndex))
+ return `|${formattedValues.join("|")}|`
+ })
+ return lines.join("\n")
+ }
+ toTsv() {
+ return this.toDelimited("\t")
+ }
+ getYI() {
+ return "\n"
+ }
+ getZI() {
+ return " "
+ }
+ getYIRegex() {
+ return new RegExp(this.getYI(), "g")
+ }
+ getXI() {
+ return " "
+ }
+ _textToContentAndChildrenTuple(text) {
+ const lines = text.split(this.getYIRegex())
+ const firstLine = lines.shift()
+ const children = !lines.length
+ ? undefined
+ : lines
+ .map(line => (line.substr(0, 1) === this.getXI() ? line : this.getXI() + line))
+ .map(line => line.substr(1))
+ .join(this.getYI())
+ return [firstLine, children]
+ }
+ _getLine() {
+ return this._line
+ }
+ _setLine(line = "") {
+ this._line = line
+ if (this._words) delete this._words
+ return this
+ }
+ _clearChildren() {
+ delete this._children
+ this._clearIndex()
+ return this
+ }
+ _setChildren(content, circularCheckArray) {
+ this._clearChildren()
+ if (!content) return this
+ // set from string
+ if (typeof content === "string") return this._parseString(content)
+ // set from tree object
+ if (content instanceof TreeNode) {
+ const me = this
+ content.forEach(node => {
+ me._setLineAndChildren(node.getLine(), node.childrenToString())
+ })
+ return this
+ }
+ // If we set from object, create an array of inserted objects to avoid circular loops
+ if (!circularCheckArray) circularCheckArray = [content]
+ return this._setFromObject(content, circularCheckArray)
+ }
+ _setFromObject(content, circularCheckArray) {
+ for (let firstWord in content) {
+ if (!content.hasOwnProperty(firstWord)) continue
+ // Branch the circularCheckArray, as we only have same branch circular arrays
+ this._appendFromJavascriptObjectTuple(firstWord, content[firstWord], circularCheckArray.slice(0))
+ }
+ return this
+ }
+ // todo: refactor the below.
+ _appendFromJavascriptObjectTuple(firstWord, content, circularCheckArray) {
+ const type = typeof content
+ let line
+ let children
+ if (content === null) line = firstWord + " " + null
+ else if (content === undefined) line = firstWord
+ else if (type === "string") {
+ const tuple = this._textToContentAndChildrenTuple(content)
+ line = firstWord + " " + tuple[0]
+ children = tuple[1]
+ } else if (type === "function") line = firstWord + " " + content.toString()
+ else if (type !== "object") line = firstWord + " " + content
+ else if (content instanceof Date) line = firstWord + " " + content.getTime().toString()
+ else if (content instanceof TreeNode) {
+ line = firstWord
+ children = new TreeNode(content.childrenToString(), content.getLine())
+ } else if (circularCheckArray.indexOf(content) === -1) {
+ circularCheckArray.push(content)
+ line = firstWord
+ const length = content instanceof Array ? content.length : Object.keys(content).length
+ if (length) children = new TreeNode()._setChildren(content, circularCheckArray)
+ } else {
+ // iirc this is return early from circular
+ return
+ }
+ this._setLineAndChildren(line, children)
+ }
+ // todo: protected?
+ _setLineAndChildren(line, children, index = this.length) {
+ const nodeConstructor = this._getParser()._getNodeConstructor(line, this)
+ const newNode = new nodeConstructor(children, line, this)
+ const adjustedIndex = index < 0 ? this.length + index : index
+ this._getChildrenArray().splice(adjustedIndex, 0, newNode)
+ if (this._index) this._makeIndex(adjustedIndex)
+ return newNode
+ }
+ _parseString(str) {
+ const lines = str.split(this.getYIRegex())
+ const parentStack = []
+ let currentIndentCount = -1
+ let lastNode = this
+ lines.forEach(line => {
+ const indentCount = this._getIndentCount(line)
+ if (indentCount > currentIndentCount) {
+ currentIndentCount++
+ parentStack.push(lastNode)
+ } else if (indentCount < currentIndentCount) {
+ // pop things off stack
+ while (indentCount < currentIndentCount) {
+ parentStack.pop()
+ currentIndentCount--
+ }
+ }
+ const lineContent = line.substr(currentIndentCount)
+ const parent = parentStack[parentStack.length - 1]
+ const nodeConstructor = parent._getParser()._getNodeConstructor(lineContent, parent)
+ lastNode = new nodeConstructor(undefined, lineContent, parent)
+ parent._getChildrenArray().push(lastNode)
+ })
+ return this
+ }
+ _getIndex() {
+ // StringMap {firstWord: index}
+ // When there are multiple tails with the same firstWord, _index stores the last content.
+ // todo: change the above behavior: when a collision occurs, create an array.
+ return this._index || this._makeIndex()
+ }
+ getContentsArray() {
+ return this.map(node => node.getContent())
+ }
+ // todo: rename to getChildrenByConstructor(?)
+ getChildrenByNodeConstructor(constructor) {
+ return this.filter(child => child instanceof constructor)
+ }
+ // todo: rename to getNodeByConstructor(?)
+ getNodeByType(constructor) {
+ return this.find(child => child instanceof constructor)
+ }
+ indexOfLast(firstWord) {
+ const result = this._getIndex()[firstWord]
+ return result === undefined ? -1 : result
+ }
+ // todo: renmae to indexOfFirst?
+ indexOf(firstWord) {
+ if (!this.has(firstWord)) return -1
+ const length = this.length
+ const nodes = this._getChildren()
+ for (let index = 0; index < length; index++) {
+ if (nodes[index].getFirstWord() === firstWord) return index
+ }
+ }
+ toObject() {
+ return this._toObject()
+ }
+ getFirstWords() {
+ return this.map(node => node.getFirstWord())
+ }
+ _makeIndex(startAt = 0) {
+ if (!this._index || !startAt) this._index = {}
+ const nodes = this._getChildren()
+ const newIndex = this._index
+ const length = nodes.length
+ for (let index = startAt; index < length; index++) {
+ newIndex[nodes[index].getFirstWord()] = index
+ }
+ return newIndex
+ }
+ _childrenToXml(indentCount) {
+ return this.map(node => node._toXml(indentCount)).join("")
+ }
+ _getIndentCount(str) {
+ let level = 0
+ const edgeChar = this.getXI()
+ while (str[level] === edgeChar) {
+ level++
+ }
+ return level
+ }
+ clone() {
+ return new this.constructor(this.childrenToString(), this.getLine())
+ }
+ // todo: rename to hasFirstWord
+ has(firstWord) {
+ return this._hasFirstWord(firstWord)
+ }
+ _hasFirstWord(firstWord) {
+ return this._getIndex()[firstWord] !== undefined
+ }
+ map(fn) {
+ return this.getChildren().map(fn)
+ }
+ filter(fn) {
+ return this.getChildren().filter(fn)
+ }
+ find(fn) {
+ return this.getChildren().find(fn)
+ }
+ every(fn) {
+ let index = 0
+ for (let node of this.getTopDownArrayIterator()) {
+ if (!fn(node, index)) return false
+ index++
+ }
+ return true
+ }
+ forEach(fn) {
+ this.getChildren().forEach(fn)
+ return this
+ }
+ // todo: protected?
+ _clearIndex() {
+ delete this._index
+ }
+ slice(start, end) {
+ return this.getChildren().slice(start, end)
+ }
+ // todo: make 0 and 1 a param
+ getInheritanceTree() {
+ const paths = {}
+ const result = new TreeNode()
+ this.forEach(node => {
+ const key = node.getWord(0)
+ const parentKey = node.getWord(1)
+ const parentPath = paths[parentKey]
+ paths[key] = parentPath ? [parentPath, key].join(" ") : key
+ result.touchNode(paths[key])
+ })
+ return result
+ }
+ _getGrandParent() {
+ return this.isRoot() || this.getParent().isRoot() ? undefined : this.getParent().getParent()
+ }
+ _getParser() {
+ if (!this._parser) this._parser = this.createParser()
+ return this._parser
+ }
+ createParser() {
+ return new Parser(this.constructor)
+ }
+ static _makeUniqueId() {
+ if (this._uniqueId === undefined) this._uniqueId = 0
+ this._uniqueId++
+ return this._uniqueId
+ }
+ static _getFileFormat(path) {
+ const format = path.split(".").pop()
+ return FileFormat[format] ? format : FileFormat.tree
+ }
+ getMTime() {
+ if (!this._mtime) this._updateMTime()
+ return this._mtime
+ }
+ _getChildrenMTime() {
+ const mTimes = this.map(child => child.getTreeMTime())
+ const cmTime = this._getCMTime()
+ if (cmTime) mTimes.push(cmTime)
+ const newestTime = Math.max.apply(null, mTimes)
+ return this._setCMTime(newestTime || this._getProcessTimeInMilliseconds())._getCMTime()
+ }
+ _getCMTime() {
+ return this._cmtime
+ }
+ _setCMTime(value) {
+ this._cmtime = value
+ return this
+ }
+ getTreeMTime() {
+ const mtime = this.getMTime()
+ const cmtime = this._getChildrenMTime()
+ return Math.max(mtime, cmtime)
+ }
+ _setVirtualParentTree(tree) {
+ this._virtualParentTree = tree
+ return this
+ }
+ _getVirtualParentTreeNode() {
+ return this._virtualParentTree
+ }
+ _setVirtualAncestorNodesByInheritanceViaColumnIndicesAndThenExpand(nodes, thisIdColumnNumber, extendsIdColumnNumber) {
+ const map = {}
+ for (let node of nodes) {
+ const nodeId = node.getWord(thisIdColumnNumber)
+ if (map[nodeId]) throw new Error(`Tried to define a node with id "${nodeId}" but one is already defined.`)
+ map[nodeId] = {
+ nodeId: nodeId,
+ node: node,
+ parentId: node.getWord(extendsIdColumnNumber)
+ }
+ }
+ // Add parent Nodes
+ Object.values(map).forEach(nodeInfo => {
+ const parentId = nodeInfo.parentId
+ const parentNode = map[parentId]
+ if (parentId && !parentNode) throw new Error(`Node "${nodeInfo.nodeId}" tried to extend "${parentId}" but "${parentId}" not found.`)
+ if (parentId) nodeInfo.node._setVirtualParentTree(parentNode.node)
+ })
+ nodes.forEach(node => node._expandFromVirtualParentTree())
+ return this
+ }
+ _expandFromVirtualParentTree() {
+ if (this._isVirtualExpanded) return this
+ this._isExpanding = true
+ let parentNode = this._getVirtualParentTreeNode()
+ if (parentNode) {
+ if (parentNode._isExpanding) throw new Error(`Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`)
+ parentNode._expandFromVirtualParentTree()
+ const clone = this.clone()
+ this._setChildren(parentNode.childrenToString())
+ this.extend(clone)
+ }
+ this._isExpanding = false
+ this._isVirtualExpanded = true
+ }
+ // todo: solve issue related to whether extend should overwrite or append.
+ _expandChildren(thisIdColumnNumber, extendsIdColumnNumber, childrenThatNeedExpanding = this.getChildren()) {
+ return this._setVirtualAncestorNodesByInheritanceViaColumnIndicesAndThenExpand(childrenThatNeedExpanding, thisIdColumnNumber, extendsIdColumnNumber)
+ }
+ // todo: add more testing.
+ // todo: solve issue with where extend should overwrite or append
+ // todo: should take a grammar? to decide whether to overwrite or append.
+ // todo: this is slow.
+ extend(nodeOrStr) {
+ const node = nodeOrStr instanceof TreeNode ? nodeOrStr : new TreeNode(nodeOrStr)
+ const usedFirstWords = new Set()
+ node.forEach(sourceNode => {
+ const firstWord = sourceNode.getFirstWord()
+ let targetNode
+ const isAnArrayNotMap = usedFirstWords.has(firstWord)
+ if (!this.has(firstWord)) {
+ usedFirstWords.add(firstWord)
+ this.appendLineAndChildren(sourceNode.getLine(), sourceNode.childrenToString())
+ return true
+ }
+ if (isAnArrayNotMap) targetNode = this.appendLine(sourceNode.getLine())
+ else {
+ targetNode = this.touchNode(firstWord).setContent(sourceNode.getContent())
+ usedFirstWords.add(firstWord)
+ }
+ if (sourceNode.length) targetNode.extend(sourceNode)
+ })
+ return this
+ }
+ macroExpand(macroDefinitionWord, macroUsageWord) {
+ const clone = this.clone()
+ const defs = clone.findNodes(macroDefinitionWord)
+ const allUses = clone.findNodes(macroUsageWord)
+ const zi = clone.getZI()
+ defs.forEach(def => {
+ const macroName = def.getWord(1)
+ const uses = allUses.filter(node => node.hasWord(1, macroName))
+ const params = def.getWordsFrom(2)
+ const replaceFn = str => {
+ const paramValues = str.split(zi).slice(2)
+ let newTree = def.childrenToString()
+ params.forEach((param, index) => {
+ newTree = newTree.replace(new RegExp(param, "g"), paramValues[index])
+ })
+ return newTree
+ }
+ uses.forEach(node => {
+ node.replaceNode(replaceFn)
+ })
+ def.destroy()
+ })
+ return clone
+ }
+ setChildren(children) {
+ return this._setChildren(children)
+ }
+ _updateMTime() {
+ this._mtime = this._getProcessTimeInMilliseconds()
+ }
+ insertWord(index, word) {
+ const wi = this.getZI()
+ const words = this._getLine().split(wi)
+ words.splice(index, 0, word)
+ this.setLine(words.join(wi))
+ return this
+ }
+ deleteDuplicates() {
+ const set = new Set()
+ this.getTopDownArray().forEach(node => {
+ const str = node.toString()
+ if (set.has(str)) node.destroy()
+ else set.add(str)
+ })
+ return this
+ }
+ setWord(index, word) {
+ const wi = this.getZI()
+ const words = this._getLine().split(wi)
+ words[index] = word
+ this.setLine(words.join(wi))
+ return this
+ }
+ deleteChildren() {
+ return this._clearChildren()
+ }
+ setContent(content) {
+ if (content === this.getContent()) return this
+ const newArray = [this.getFirstWord()]
+ if (content !== undefined) {
+ content = content.toString()
+ if (content.match(this.getYI())) return this.setContentWithChildren(content)
+ newArray.push(content)
+ }
+ this._updateMTime()
+ return this._setLine(newArray.join(this.getZI()))
+ }
+ prependSibling(line, children) {
+ return this.getParent().insertLineAndChildren(line, children, this.getIndex())
+ }
+ appendSibling(line, children) {
+ return this.getParent().insertLineAndChildren(line, children, this.getIndex() + 1)
+ }
+ setContentWithChildren(text) {
+ // todo: deprecate
+ if (!text.includes(this.getYI())) {
+ this._clearChildren()
+ return this.setContent(text)
+ }
+ const lines = text.split(this.getYIRegex())
+ const firstLine = lines.shift()
+ this.setContent(firstLine)
+ // tood: cleanup.
+ const remainingString = lines.join(this.getYI())
+ const children = new TreeNode(remainingString)
+ if (!remainingString) children.appendLine("")
+ this.setChildren(children)
+ return this
+ }
+ setFirstWord(firstWord) {
+ return this.setWord(0, firstWord)
+ }
+ setLine(line) {
+ if (line === this.getLine()) return this
+ this._updateMTime()
+ // todo: clear parent TMTimes
+ this.getParent()._clearIndex()
+ return this._setLine(line)
+ }
+ duplicate() {
+ return this.getParent()._setLineAndChildren(this.getLine(), this.childrenToString(), this.getIndex() + 1)
+ }
+ destroy() {
+ this.getParent()._deleteNode(this)
+ }
+ set(firstWordPath, text) {
+ return this.touchNode(firstWordPath).setContentWithChildren(text)
+ }
+ setFromText(text) {
+ if (this.toString() === text) return this
+ const tuple = this._textToContentAndChildrenTuple(text)
+ this.setLine(tuple[0])
+ return this._setChildren(tuple[1])
+ }
+ // todo: throw error if line contains a \n
+ appendLine(line) {
+ return this._setLineAndChildren(line)
+ }
+ appendLineAndChildren(line, children) {
+ return this._setLineAndChildren(line, children)
+ }
+ getNodesByRegex(regex) {
+ const matches = []
+ regex = regex instanceof RegExp ? [regex] : regex
+ this._getNodesByLineRegex(matches, regex)
+ return matches
+ }
+ getNodesByLinePrefixes(columns) {
+ const matches = []
+ this._getNodesByLineRegex(matches, columns.map(str => new RegExp("^" + str)))
+ return matches
+ }
+ _getNodesByLineRegex(matches, regs) {
+ const rgs = regs.slice(0)
+ const reg = rgs.shift()
+ const candidates = this.filter(child => child.getLine().match(reg))
+ if (!rgs.length) return candidates.forEach(cand => matches.push(cand))
+ candidates.forEach(cand => cand._getNodesByLineRegex(matches, rgs))
+ }
+ concat(node) {
+ if (typeof node === "string") node = new TreeNode(node)
+ return node.map(node => this._setLineAndChildren(node.getLine(), node.childrenToString()))
+ }
+ _deleteByIndexes(indexesToDelete) {
+ this._clearIndex()
+ // note: assumes indexesToDelete is in ascending order
+ indexesToDelete.reverse().forEach(index => this._getChildrenArray().splice(index, 1))
+ return this._setCMTime(this._getProcessTimeInMilliseconds())
+ }
+ _deleteNode(node) {
+ const index = this._indexOfNode(node)
+ return index > -1 ? this._deleteByIndexes([index]) : 0
+ }
+ reverse() {
+ this._clearIndex()
+ this._getChildrenArray().reverse()
+ return this
+ }
+ shift() {
+ if (!this.length) return null
+ const node = this._getChildrenArray().shift()
+ return node.copyTo(new this.constructor(), 0)
+ }
+ sort(fn) {
+ this._getChildrenArray().sort(fn)
+ this._clearIndex()
+ return this
+ }
+ invert() {
+ this.forEach(node => node.getWords().reverse())
+ return this
+ }
+ _rename(oldFirstWord, newFirstWord) {
+ const index = this.indexOf(oldFirstWord)
+ if (index === -1) return this
+ const node = this._getChildren()[index]
+ node.setFirstWord(newFirstWord)
+ this._clearIndex()
+ return this
+ }
+ // Does not recurse.
+ remap(map) {
+ this.forEach(node => {
+ const firstWord = node.getFirstWord()
+ if (map[firstWord] !== undefined) node.setFirstWord(map[firstWord])
+ })
+ return this
+ }
+ rename(oldFirstWord, newFirstWord) {
+ this._rename(oldFirstWord, newFirstWord)
+ return this
+ }
+ renameAll(oldName, newName) {
+ this.findNodes(oldName).forEach(node => node.setFirstWord(newName))
+ return this
+ }
+ _deleteAllChildNodesWithFirstWord(firstWord) {
+ if (!this.has(firstWord)) return this
+ const allNodes = this._getChildren()
+ const indexesToDelete = []
+ allNodes.forEach((node, index) => {
+ if (node.getFirstWord() === firstWord) indexesToDelete.push(index)
+ })
+ return this._deleteByIndexes(indexesToDelete)
+ }
+ delete(path = "") {
+ const xi = this.getXI()
+ if (!path.includes(xi)) return this._deleteAllChildNodesWithFirstWord(path)
+ const parts = path.split(xi)
+ const nextFirstWord = parts.pop()
+ const targetNode = this.getNode(parts.join(xi))
+ return targetNode ? targetNode._deleteAllChildNodesWithFirstWord(nextFirstWord) : 0
+ }
+ deleteColumn(firstWord = "") {
+ this.forEach(node => node.delete(firstWord))
+ return this
+ }
+ _getNonMaps() {
+ const results = this.getTopDownArray().filter(node => node.hasDuplicateFirstWords())
+ if (this.hasDuplicateFirstWords()) results.unshift(this)
+ return results
+ }
+ replaceNode(fn) {
+ const parent = this.getParent()
+ const index = this.getIndex()
+ const newNodes = new TreeNode(fn(this.toString()))
+ const returnedNodes = []
+ newNodes.forEach((child, childIndex) => {
+ const newNode = parent.insertLineAndChildren(child.getLine(), child.childrenToString(), index + childIndex)
+ returnedNodes.push(newNode)
+ })
+ this.destroy()
+ return returnedNodes
+ }
+ insertLineAndChildren(line, children, index) {
+ return this._setLineAndChildren(line, children, index)
+ }
+ insertLine(line, index) {
+ return this._setLineAndChildren(line, undefined, index)
+ }
+ prependLine(line) {
+ return this.insertLine(line, 0)
+ }
+ pushContentAndChildren(content, children) {
+ let index = this.length
+ while (this.has(index.toString())) {
+ index++
+ }
+ const line = index.toString() + (content === undefined ? "" : this.getZI() + content)
+ return this.appendLineAndChildren(line, children)
+ }
+ deleteBlanks() {
+ this.getChildren()
+ .filter(node => node.isBlankLine())
+ .forEach(node => node.destroy())
+ return this
+ }
+ // todo: add "globalReplace" method? Which runs a global regex or string replace on the Tree doc as a string?
+ firstWordSort(firstWordOrder) {
+ return this._firstWordSort(firstWordOrder)
+ }
+ deleteWordAt(wordIndex) {
+ const words = this.getWords()
+ words.splice(wordIndex, 1)
+ return this.setWords(words)
+ }
+ setWords(words) {
+ return this.setLine(words.join(this.getZI()))
+ }
+ setWordsFrom(index, words) {
+ this.setWords(
+ this.getWords()
+ .slice(0, index)
+ .concat(words)
+ )
+ return this
+ }
+ appendWord(word) {
+ const words = this.getWords()
+ words.push(word)
+ return this.setWords(words)
+ }
+ _firstWordSort(firstWordOrder, secondarySortFn) {
+ const nodeAFirst = -1
+ const nodeBFirst = 1
+ const map = {}
+ firstWordOrder.forEach((word, index) => {
+ map[word] = index
+ })
+ this.sort((nodeA, nodeB) => {
+ const valA = map[nodeA.getFirstWord()]
+ const valB = map[nodeB.getFirstWord()]
+ if (valA > valB) return nodeBFirst
+ if (valA < valB) return nodeAFirst
+ return secondarySortFn ? secondarySortFn(nodeA, nodeB) : 0
+ })
+ return this
+ }
+ _touchNode(firstWordPathArray) {
+ let contextNode = this
+ firstWordPathArray.forEach(firstWord => {
+ contextNode = contextNode.getNode(firstWord) || contextNode.appendLine(firstWord)
+ })
+ return contextNode
+ }
+ _touchNodeByString(str) {
+ str = str.replace(this.getYIRegex(), "") // todo: do we want to do this sanitization?
+ return this._touchNode(str.split(this.getZI()))
+ }
+ touchNode(str) {
+ return this._touchNodeByString(str)
+ }
+ appendNode(node) {
+ return this.appendLineAndChildren(node.getLine(), node.childrenToString())
+ }
+ hasLine(line) {
+ return this.getChildren().some(node => node.getLine() === line)
+ }
+ getNodesByLine(line) {
+ return this.filter(node => node.getLine() === line)
+ }
+ toggleLine(line) {
+ const lines = this.getNodesByLine(line)
+ if (lines.length) {
+ lines.map(line => line.destroy())
+ return this
+ }
+ return this.appendLine(line)
+ }
+ // todo: remove?
+ sortByColumns(indexOrIndices) {
+ const indices = indexOrIndices instanceof Array ? indexOrIndices : [indexOrIndices]
+ const length = indices.length
+ this.sort((nodeA, nodeB) => {
+ const wordsA = nodeA.getWords()
+ const wordsB = nodeB.getWords()
+ for (let index = 0; index < length; index++) {
+ const col = indices[index]
+ const av = wordsA[col]
+ const bv = wordsB[col]
+ if (av === undefined) return -1
+ if (bv === undefined) return 1
+ if (av > bv) return 1
+ else if (av < bv) return -1
+ }
+ return 0
+ })
+ return this
+ }
+ getWordsAsSet() {
+ return new Set(this.getWordsFrom(1))
+ }
+ appendWordIfMissing(word) {
+ if (this.getWordsAsSet().has(word)) return this
+ return this.appendWord(word)
+ }
+ // todo: check to ensure identical objects
+ addObjectsAsDelimited(arrayOfObjects, delimiter = TreeUtils._chooseDelimiter(new TreeNode(arrayOfObjects).toString())) {
+ const header = Object.keys(arrayOfObjects[0])
+ .join(delimiter)
+ .replace(/[\n\r]/g, "")
+ const rows = arrayOfObjects.map(item =>
+ Object.values(item)
+ .join(delimiter)
+ .replace(/[\n\r]/g, "")
+ )
+ return this.addUniqueRowsToNestedDelimited(header, rows)
+ }
+ setChildrenAsDelimited(tree, delimiter = TreeUtils._chooseDelimiter(tree.toString())) {
+ tree = tree instanceof TreeNode ? tree : new TreeNode(tree)
+ return this.setChildren(tree.toDelimited(delimiter))
+ }
+ convertChildrenToDelimited(delimiter = TreeUtils._chooseDelimiter(this.childrenToString())) {
+ // todo: handle newlines!!!
+ return this.setChildren(this.toDelimited(delimiter))
+ }
+ addUniqueRowsToNestedDelimited(header, rowsAsStrings) {
+ if (!this.length) this.appendLine(header)
+ // todo: this looks brittle
+ rowsAsStrings.forEach(row => {
+ if (!this.toString().includes(row)) this.appendLine(row)
+ })
+ return this
+ }
+ shiftLeft() {
+ const grandParent = this._getGrandParent()
+ if (!grandParent) return this
+ const parentIndex = this.getParent().getIndex()
+ const newNode = grandParent.insertLineAndChildren(this.getLine(), this.length ? this.childrenToString() : undefined, parentIndex + 1)
+ this.destroy()
+ return newNode
+ }
+ shiftRight() {
+ const olderSibling = this._getClosestOlderSibling()
+ if (!olderSibling) return this
+ const newNode = olderSibling.appendLineAndChildren(this.getLine(), this.length ? this.childrenToString() : undefined)
+ this.destroy()
+ return newNode
+ }
+ shiftYoungerSibsRight() {
+ const nodes = this.getYoungerSiblings()
+ nodes.forEach(node => node.shiftRight())
+ return this
+ }
+ sortBy(nameOrNames) {
+ const names = nameOrNames instanceof Array ? nameOrNames : [nameOrNames]
+ const length = names.length
+ this.sort((nodeA, nodeB) => {
+ if (!nodeB.length && !nodeA.length) return 0
+ else if (!nodeA.length) return -1
+ else if (!nodeB.length) return 1
+ for (let index = 0; index < length; index++) {
+ const firstWord = names[index]
+ const av = nodeA.get(firstWord)
+ const bv = nodeB.get(firstWord)
+ if (av > bv) return 1
+ else if (av < bv) return -1
+ }
+ return 0
+ })
+ return this
+ }
+ static fromCsv(str) {
+ return this.fromDelimited(str, ",", '"')
+ }
+ static fromJsonSubset(str) {
+ return new TreeNode(JSON.parse(str))
+ }
+ static fromSsv(str) {
+ return this.fromDelimited(str, " ", '"')
+ }
+ static fromTsv(str) {
+ return this.fromDelimited(str, "\t", '"')
+ }
+ static fromDelimited(str, delimiter, quoteChar = '"') {
+ const rows = this._getEscapedRows(str, delimiter, quoteChar)
+ return this._rowsToTreeNode(rows, delimiter, true)
+ }
+ static _getEscapedRows(str, delimiter, quoteChar) {
+ return str.includes(quoteChar) ? this._strToRows(str, delimiter, quoteChar) : str.split("\n").map(line => line.split(delimiter))
+ }
+ static fromDelimitedNoHeaders(str, delimiter, quoteChar) {
+ const rows = this._getEscapedRows(str, delimiter, quoteChar)
+ return this._rowsToTreeNode(rows, delimiter, false)
+ }
+ static _strToRows(str, delimiter, quoteChar, newLineChar = "\n") {
+ const rows = [[]]
+ const newLine = "\n"
+ const length = str.length
+ let currentCell = ""
+ let inQuote = str.substr(0, 1) === quoteChar
+ let currentPosition = inQuote ? 1 : 0
+ let nextChar
+ let isLastChar
+ let currentRow = 0
+ let char
+ let isNextCharAQuote
+ while (currentPosition < length) {
+ char = str[currentPosition]
+ isLastChar = currentPosition + 1 === length
+ nextChar = str[currentPosition + 1]
+ isNextCharAQuote = nextChar === quoteChar
+ if (inQuote) {
+ if (char !== quoteChar) currentCell += char
+ else if (isNextCharAQuote) {
+ // Both the current and next char are ", so the " is escaped
+ currentCell += nextChar
+ currentPosition++ // Jump 2
+ } else {
+ // If the current char is a " and the next char is not, it's the end of the quotes
+ inQuote = false
+ if (isLastChar) rows[currentRow].push(currentCell)
+ }
+ } else {
+ if (char === delimiter) {
+ rows[currentRow].push(currentCell)
+ currentCell = ""
+ if (isNextCharAQuote) {
+ inQuote = true
+ currentPosition++ // Jump 2
+ }
+ } else if (char === newLine) {
+ rows[currentRow].push(currentCell)
+ currentCell = ""
+ currentRow++
+ if (nextChar) rows[currentRow] = []
+ if (isNextCharAQuote) {
+ inQuote = true
+ currentPosition++ // Jump 2
+ }
+ } else if (isLastChar) rows[currentRow].push(currentCell + char)
+ else currentCell += char
+ }
+ currentPosition++
+ }
+ return rows
+ }
+ static multiply(nodeA, nodeB) {
+ const productNode = nodeA.clone()
+ productNode.forEach((node, index) => {
+ node.setChildren(node.length ? this.multiply(node, nodeB) : nodeB.clone())
+ })
+ return productNode
+ }
+ // Given an array return a tree
+ static _rowsToTreeNode(rows, delimiter, hasHeaders) {
+ const numberOfColumns = rows[0].length
+ const treeNode = new TreeNode()
+ const names = this._getHeader(rows, hasHeaders)
+ const rowCount = rows.length
+ for (let rowIndex = hasHeaders ? 1 : 0; rowIndex < rowCount; rowIndex++) {
+ let row = rows[rowIndex]
+ // If the row contains too many columns, shift the extra columns onto the last one.
+ // This allows you to not have to escape delimiter characters in the final column.
+ if (row.length > numberOfColumns) {
+ row[numberOfColumns - 1] = row.slice(numberOfColumns - 1).join(delimiter)
+ row = row.slice(0, numberOfColumns)
+ } else if (row.length < numberOfColumns) {
+ // If the row is missing columns add empty columns until it is full.
+ // This allows you to make including delimiters for empty ending columns in each row optional.
+ while (row.length < numberOfColumns) {
+ row.push("")
+ }
+ }
+ const obj = {}
+ row.forEach((cellValue, index) => {
+ obj[names[index]] = cellValue
+ })
+ treeNode.pushContentAndChildren(undefined, obj)
+ }
+ return treeNode
+ }
+ static _initializeXmlParser() {
+ if (this._xmlParser) return
+ const windowObj = window
+ if (typeof windowObj.DOMParser !== "undefined") this._xmlParser = xmlStr => new windowObj.DOMParser().parseFromString(xmlStr, "text/xml")
+ else if (typeof windowObj.ActiveXObject !== "undefined" && new windowObj.ActiveXObject("Microsoft.XMLDOM")) {
+ this._xmlParser = xmlStr => {
+ const xmlDoc = new windowObj.ActiveXObject("Microsoft.XMLDOM")
+ xmlDoc.async = "false"
+ xmlDoc.loadXML(xmlStr)
+ return xmlDoc
+ }
+ } else throw new Error("No XML parser found")
+ }
+ static fromXml(str) {
+ this._initializeXmlParser()
+ const xml = this._xmlParser(str)
+ try {
+ return this._treeNodeFromXml(xml).getNode("children")
+ } catch (err) {
+ return this._treeNodeFromXml(this._parseXml2(str)).getNode("children")
+ }
+ }
+ static _zipObject(keys, values) {
+ const obj = {}
+ keys.forEach((key, index) => (obj[key] = values[index]))
+ return obj
+ }
+ static fromShape(shapeArr, rootNode = new TreeNode()) {
+ const part = shapeArr.shift()
+ if (part !== undefined) {
+ for (let index = 0; index < part; index++) {
+ rootNode.appendLine(index.toString())
+ }
+ }
+ if (shapeArr.length) rootNode.forEach(node => TreeNode.fromShape(shapeArr.slice(0), node))
+ return rootNode
+ }
+ static fromDataTable(table) {
+ const header = table.shift()
+ return new TreeNode(table.map(row => this._zipObject(header, row)))
+ }
+ static _parseXml2(str) {
+ const el = document.createElement("div")
+ el.innerHTML = str
+ return el
+ }
+ // todo: cleanup typings
+ static _treeNodeFromXml(xml) {
+ const result = new TreeNode()
+ const children = new TreeNode()
+ // Set attributes
+ if (xml.attributes) {
+ for (let index = 0; index < xml.attributes.length; index++) {
+ result.set(xml.attributes[index].name, xml.attributes[index].value)
+ }
+ }
+ if (xml.data) children.pushContentAndChildren(xml.data)
+ // Set content
+ if (xml.childNodes && xml.childNodes.length > 0) {
+ for (let index = 0; index < xml.childNodes.length; index++) {
+ const child = xml.childNodes[index]
+ if (child.tagName && child.tagName.match(/parsererror/i)) throw new Error("Parse Error")
+ if (child.childNodes.length > 0 && child.tagName) children.appendLineAndChildren(child.tagName, this._treeNodeFromXml(child))
+ else if (child.tagName) children.appendLine(child.tagName)
+ else if (child.data) {
+ const data = child.data.trim()
+ if (data) children.pushContentAndChildren(data)
+ }
+ }
+ }
+ if (children.length > 0) result.touchNode("children").setChildren(children)
+ return result
+ }
+ static _getHeader(rows, hasHeaders) {
+ const numberOfColumns = rows[0].length
+ const headerRow = hasHeaders ? rows[0] : []
+ const ZI = " "
+ const ziRegex = new RegExp(ZI, "g")
+ if (hasHeaders) {
+ // Strip any ZIs from column names in the header row.
+ // This makes the mapping not quite 1 to 1 if there are any ZIs in names.
+ for (let index = 0; index < numberOfColumns; index++) {
+ headerRow[index] = headerRow[index].replace(ziRegex, "")
+ }
+ } else {
+ // If str has no headers, create them as 0,1,2,3
+ for (let index = 0; index < numberOfColumns; index++) {
+ headerRow.push(index.toString())
+ }
+ }
+ return headerRow
+ }
+ static nest(str, xValue) {
+ const YI = "\n"
+ const XI = " "
+ const indent = YI + XI.repeat(xValue)
+ return str ? indent + str.replace(/\n/g, indent) : ""
+ }
+ static fromDisk(path) {
+ const format = this._getFileFormat(path)
+ const content = require("fs").readFileSync(path, "utf8")
+ const methods = {
+ tree: content => new TreeNode(content),
+ csv: content => this.fromCsv(content),
+ tsv: content => this.fromTsv(content)
+ }
+ return methods[format](content)
+ }
+ }
+ TreeNode.Parser = Parser
+ TreeNode.iris = `sepal_length,sepal_width,petal_length,petal_width,species
+ 6.1,3,4.9,1.8,virginica
+ 5.6,2.7,4.2,1.3,versicolor
+ 5.6,2.8,4.9,2,virginica
+ 6.2,2.8,4.8,1.8,virginica
+ 7.7,3.8,6.7,2.2,virginica
+ 5.3,3.7,1.5,0.2,setosa
+ 6.2,3.4,5.4,2.3,virginica
+ 4.9,2.5,4.5,1.7,virginica
+ 5.1,3.5,1.4,0.2,setosa
+ 5,3.4,1.5,0.2,setosa`
+ TreeNode.getVersion = () => "40.0.0"
+ class AbstractExtendibleTreeNode extends TreeNode {
+ _getFromExtended(firstWordPath) {
+ const hit = this._getNodeFromExtended(firstWordPath)
+ return hit ? hit.get(firstWordPath) : undefined
+ }
+ _getFamilyTree() {
+ const tree = new TreeNode()
+ this.forEach(node => {
+ const path = node._getAncestorsArray().map(node => node._getId())
+ path.reverse()
+ tree.touchNode(path.join(" "))
+ })
+ return tree
+ }
+ // todo: be more specific with the param
+ _getChildrenByNodeConstructorInExtended(constructor) {
+ return TreeUtils.flatten(this._getAncestorsArray().map(node => node.getChildrenByNodeConstructor(constructor)))
+ }
+ _getExtendedParent() {
+ return this._getAncestorsArray()[1]
+ }
+ _hasFromExtended(firstWordPath) {
+ return !!this._getNodeFromExtended(firstWordPath)
+ }
+ _getNodeFromExtended(firstWordPath) {
+ return this._getAncestorsArray().find(node => node.has(firstWordPath))
+ }
+ _doesExtend(nodeTypeId) {
+ return this._getAncestorSet().has(nodeTypeId)
+ }
+ _getAncestorSet() {
+ if (!this._cache_ancestorSet) this._cache_ancestorSet = new Set(this._getAncestorsArray().map(def => def._getId()))
+ return this._cache_ancestorSet
+ }
+ // Note: the order is: [this, parent, grandParent, ...]
+ _getAncestorsArray(cannotContainNodes) {
+ this._initAncestorsArrayCache(cannotContainNodes)
+ return this._cache_ancestorsArray
+ }
+ _getIdThatThisExtends() {
+ return this.get(TreeNotationConstants.extends)
+ }
+ _initAncestorsArrayCache(cannotContainNodes) {
+ if (this._cache_ancestorsArray) return undefined
+ if (cannotContainNodes && cannotContainNodes.includes(this)) throw new Error(`Loop detected: '${this.getLine()}' is the ancestor of one of its ancestors.`)
+ cannotContainNodes = cannotContainNodes || [this]
+ let ancestors = [this]
+ const extendedId = this._getIdThatThisExtends()
+ if (extendedId) {
+ const parentNode = this._getIdToNodeMap()[extendedId]
+ if (!parentNode) throw new Error(`${extendedId} not found`)
+ ancestors = ancestors.concat(parentNode._getAncestorsArray(cannotContainNodes))
+ }
+ this._cache_ancestorsArray = ancestors
+ }
+ }
+ class ExtendibleTreeNode extends AbstractExtendibleTreeNode {
+ _getIdToNodeMap() {
+ if (!this.isRoot()) return this.getRootNode()._getIdToNodeMap()
+ if (!this._nodeMapCache) {
+ this._nodeMapCache = {}
+ this.forEach(child => {
+ this._nodeMapCache[child._getId()] = child
+ })
+ }
+ return this._nodeMapCache
+ }
+ _getId() {
+ return this.getWord(0)
+ }
+ }
+ window.TreeNode = TreeNode
+ window.ExtendibleTreeNode = ExtendibleTreeNode
+ window.AbstractExtendibleTreeNode = AbstractExtendibleTreeNode
+ var GrammarConstantsCompiler
+ ;(function(GrammarConstantsCompiler) {
+ GrammarConstantsCompiler["stringTemplate"] = "stringTemplate"
+ GrammarConstantsCompiler["indentCharacter"] = "indentCharacter"
+ GrammarConstantsCompiler["catchAllCellDelimiter"] = "catchAllCellDelimiter"
+ GrammarConstantsCompiler["openChildren"] = "openChildren"
+ GrammarConstantsCompiler["joinChildrenWith"] = "joinChildrenWith"
+ GrammarConstantsCompiler["closeChildren"] = "closeChildren"
+ })(GrammarConstantsCompiler || (GrammarConstantsCompiler = {}))
+ var PreludeCellTypeIds
+ ;(function(PreludeCellTypeIds) {
+ PreludeCellTypeIds["anyCell"] = "anyCell"
+ PreludeCellTypeIds["keywordCell"] = "keywordCell"
+ PreludeCellTypeIds["extraWordCell"] = "extraWordCell"
+ PreludeCellTypeIds["floatCell"] = "floatCell"
+ PreludeCellTypeIds["numberCell"] = "numberCell"
+ PreludeCellTypeIds["bitCell"] = "bitCell"
+ PreludeCellTypeIds["boolCell"] = "boolCell"
+ PreludeCellTypeIds["intCell"] = "intCell"
+ })(PreludeCellTypeIds || (PreludeCellTypeIds = {}))
+ var GrammarConstantsConstantTypes
+ ;(function(GrammarConstantsConstantTypes) {
+ GrammarConstantsConstantTypes["boolean"] = "boolean"
+ GrammarConstantsConstantTypes["string"] = "string"
+ GrammarConstantsConstantTypes["int"] = "int"
+ GrammarConstantsConstantTypes["float"] = "float"
+ })(GrammarConstantsConstantTypes || (GrammarConstantsConstantTypes = {}))
+ var GrammarBundleFiles
+ ;(function(GrammarBundleFiles) {
+ GrammarBundleFiles["package"] = "package.json"
+ GrammarBundleFiles["readme"] = "readme.md"
+ GrammarBundleFiles["indexHtml"] = "index.html"
+ GrammarBundleFiles["indexJs"] = "index.js"
+ GrammarBundleFiles["testJs"] = "test.js"
+ })(GrammarBundleFiles || (GrammarBundleFiles = {}))
+ var GrammarConstants
+ ;(function(GrammarConstants) {
+ // node types
+ GrammarConstants["extensions"] = "extensions"
+ GrammarConstants["toolingDirective"] = "tooling"
+ GrammarConstants["todoComment"] = "todo"
+ GrammarConstants["version"] = "version"
+ GrammarConstants["nodeType"] = "nodeType"
+ GrammarConstants["cellType"] = "cellType"
+ GrammarConstants["grammarFileExtension"] = "grammar"
+ GrammarConstants["nodeTypeSuffix"] = "Node"
+ GrammarConstants["cellTypeSuffix"] = "Cell"
+ // error check time
+ GrammarConstants["regex"] = "regex"
+ GrammarConstants["reservedWords"] = "reservedWords"
+ GrammarConstants["enumFromCellTypes"] = "enumFromCellTypes"
+ GrammarConstants["enum"] = "enum"
+ // baseNodeTypes
+ GrammarConstants["baseNodeType"] = "baseNodeType"
+ GrammarConstants["blobNode"] = "blobNode"
+ GrammarConstants["errorNode"] = "errorNode"
+ // parse time
+ GrammarConstants["extends"] = "extends"
+ GrammarConstants["abstract"] = "abstract"
+ GrammarConstants["root"] = "root"
+ GrammarConstants["match"] = "match"
+ GrammarConstants["pattern"] = "pattern"
+ GrammarConstants["inScope"] = "inScope"
+ GrammarConstants["cells"] = "cells"
+ GrammarConstants["catchAllCellType"] = "catchAllCellType"
+ GrammarConstants["catchAllNodeType"] = "catchAllNodeType"
+ GrammarConstants["constants"] = "constants"
+ GrammarConstants["required"] = "required"
+ GrammarConstants["single"] = "single"
+ GrammarConstants["tags"] = "tags"
+ // default catchAll nodeType
+ GrammarConstants["BlobNode"] = "BlobNode"
+ GrammarConstants["defaultRootNode"] = "defaultRootNode"
+ // code
+ GrammarConstants["javascript"] = "javascript"
+ // compile time
+ GrammarConstants["compilerNodeType"] = "compiler"
+ GrammarConstants["compilesTo"] = "compilesTo"
+ // develop time
+ GrammarConstants["description"] = "description"
+ GrammarConstants["example"] = "example"
+ GrammarConstants["frequency"] = "frequency"
+ GrammarConstants["highlightScope"] = "highlightScope"
+ })(GrammarConstants || (GrammarConstants = {}))
+ // todo: can we merge these methods into base TreeNode and ditch this class?
+ class GrammarBackedNode extends TreeNode {
+ getAutocompleteResults(partialWord, cellIndex) {
+ return cellIndex === 0 ? this._getAutocompleteResultsForFirstWord(partialWord) : this._getAutocompleteResultsForCell(partialWord, cellIndex)
+ }
+ getChildInstancesOfNodeTypeId(nodeTypeId) {
+ return this.filter(node => node.doesExtend(nodeTypeId))
+ }
+ doesExtend(nodeTypeId) {
+ return this.getDefinition()._doesExtend(nodeTypeId)
+ }
+ _getErrorNodeErrors() {
+ return [this.getFirstWord() ? new UnknownNodeTypeError(this) : new BlankLineError(this)]
+ }
+ generateSimulatedData(nodeCount = 1) {
+ const lines = []
+ const cells = this._getGrammarBackedCellArray()
+ while (nodeCount > 0) {
+ lines.push(cells.map(cell => cell.generateSimulatedData()).join(" "))
+ nodeCount--
+ }
+ return lines.join("\n")
+ }
+ _getBlobNodeCatchAllNodeType() {
+ return BlobNode
+ }
+ _getAutocompleteResultsForFirstWord(partialWord) {
+ let defs = Object.values(this.getDefinition().getFirstWordMapWithDefinitions())
+ if (partialWord)
+ defs = defs.filter(def => {
+ const word = def._getFirstWordMatch()
+ return word ? word.includes(partialWord) : false
+ })
+ return defs.map(def => {
+ const id = def._getFirstWordMatch()
+ const description = def.getDescription()
+ return {
+ text: id,
+ displayText: id + (description ? " " + description : "")
+ }
+ })
+ }
+ _getAutocompleteResultsForCell(partialWord, cellIndex) {
+ // todo: root should be [] correct?
+ const cell = this._getGrammarBackedCellArray()[cellIndex]
+ return cell ? cell.getAutoCompleteWords(partialWord) : []
+ }
+ _getGrammarBackedCellArray() {
+ return []
+ }
+ getRunTimeEnumOptions(cell) {
+ return undefined
+ }
+ sortNodesByInScopeOrder() {
+ const nodeTypeOrder = this.getDefinition()._getMyInScopeNodeTypeIds()
+ if (!nodeTypeOrder.length) return this
+ const orderMap = {}
+ nodeTypeOrder.forEach((word, index) => {
+ orderMap[word] = index
+ })
+ this.sort(
+ TreeUtils.makeSortByFn(runtimeNode => {
+ return orderMap[runtimeNode.getDefinition().getNodeTypeIdFromDefinition()]
+ })
+ )
+ return this
+ }
+ _getRequiredNodeErrors(errors = []) {
+ Object.values(this.getDefinition().getFirstWordMapWithDefinitions()).forEach(def => {
+ if (def.isRequired()) {
+ if (!this.getChildren().some(node => node.getDefinition() === def))
+ errors.push(new MissingRequiredNodeTypeError(this, def.getNodeTypeIdFromDefinition()))
+ }
+ })
+ return errors
+ }
+ }
+ class TypedWord {
+ constructor(node, cellIndex, type) {
+ this._node = node
+ this._cellIndex = cellIndex
+ this._type = type
+ }
+ replace(newWord) {
+ this._node.setWord(this._cellIndex, newWord)
+ }
+ get word() {
+ return this._node.getWord(this._cellIndex)
+ }
+ get type() {
+ return this._type
+ }
+ toString() {
+ return this.word + ":" + this.type
+ }
+ }
+ class GrammarBackedRootNode extends GrammarBackedNode {
+ getRootProgramNode() {
+ return this
+ }
+ createParser() {
+ return new TreeNode.Parser(BlobNode)
+ }
+ getAllTypedWords() {
+ const words = []
+ this.getTopDownArray().forEach(node => {
+ node.getWordTypes().forEach((cell, index) => {
+ words.push(new TypedWord(node, index, cell.getCellTypeId()))
+ })
+ })
+ return words
+ }
+ findAllWordsWithCellType(cellTypeId) {
+ return this.getAllTypedWords().filter(typedWord => typedWord.type === cellTypeId)
+ }
+ findAllNodesWithNodeType(nodeTypeId) {
+ return this.getTopDownArray().filter(node => node.getDefinition().getNodeTypeIdFromDefinition() === nodeTypeId)
+ }
+ getDefinition() {
+ return this.getGrammarProgramRoot()
+ }
+ getInPlaceCellTypeTree() {
+ return this.getTopDownArray()
+ .map(child => child.getIndentation() + child.getLineCellTypes())
+ .join("\n")
+ }
+ getParseTable(maxColumnWidth = 40) {
+ const tree = new TreeNode(this.getInPlaceCellTypeTree())
+ return new TreeNode(
+ tree.getTopDownArray().map((node, lineNumber) => {
+ const sourceNode = this.nodeAtLine(lineNumber)
+ const errs = sourceNode.getErrors()
+ const errorCount = errs.length
+ const obj = {
+ lineNumber: lineNumber,
+ source: sourceNode.getIndentation() + sourceNode.getLine(),
+ nodeType: sourceNode.constructor.name,
+ cellTypes: node.getContent(),
+ errorCount: errorCount
+ }
+ if (errorCount) obj.errorMessages = errs.map(err => err.getMessage()).join(";")
+ return obj
+ })
+ ).toFormattedTable(maxColumnWidth)
+ }
+ getErrors() {
+ return this._getRequiredNodeErrors(super.getErrors())
+ }
+ // Helper method for selecting potential nodeTypes needed to update grammar file.
+ getInvalidNodeTypes() {
+ return Array.from(
+ new Set(
+ this.getAllErrors()
+ .filter(err => err instanceof UnknownNodeTypeError)
+ .map(err => err.getNode().getFirstWord())
+ )
+ )
+ }
+ getAllSuggestions() {
+ return new TreeNode(
+ this.getAllWordBoundaryCoordinates().map(coordinate => {
+ const results = this.getAutocompleteResultsAt(coordinate.y, coordinate.x)
+ return {
+ line: coordinate.y,
+ char: coordinate.x,
+ word: results.word,
+ suggestions: results.matches.map(m => m.text).join(" ")
+ }
+ })
+ ).toTable()
+ }
+ getAutocompleteResultsAt(lineIndex, charIndex) {
+ const lineNode = this.nodeAtLine(lineIndex) || this
+ const nodeInScope = lineNode.getNodeInScopeAtCharIndex(charIndex)
+ // todo: add more tests
+ // todo: second param this.childrenToString()
+ // todo: change to getAutocomplete definitions
+ const wordIndex = lineNode.getWordIndexAtCharacterIndex(charIndex)
+ const wordProperties = lineNode.getWordProperties(wordIndex)
+ return {
+ startCharIndex: wordProperties.startCharIndex,
+ endCharIndex: wordProperties.endCharIndex,
+ word: wordProperties.word,
+ matches: nodeInScope.getAutocompleteResults(wordProperties.word, wordIndex)
+ }
+ }
+ getSortedByInheritance() {
+ const clone = new ExtendibleTreeNode(this.clone())
+ const familyTree = new GrammarProgram(clone.toString()).getNodeTypeFamilyTree()
+ const rank = {}
+ familyTree.getTopDownArray().forEach((node, index) => {
+ rank[node.getWord(0)] = index
+ })
+ const nodeAFirst = -1
+ const nodeBFirst = 1
+ clone.sort((nodeA, nodeB) => {
+ const nodeARank = rank[nodeA.getWord(0)]
+ const nodeBRank = rank[nodeB.getWord(0)]
+ return nodeARank < nodeBRank ? nodeAFirst : nodeBFirst
+ })
+ return clone
+ }
+ getNodeTypeUsage(filepath = "") {
+ // returns a report on what nodeTypes from its language the program uses
+ const usage = new TreeNode()
+ const grammarProgram = this.getGrammarProgramRoot()
+ grammarProgram.getValidConcreteAndAbstractNodeTypeDefinitions().forEach(def => {
+ usage.appendLine([def.getNodeTypeIdFromDefinition(), "line-id", "nodeType", def.getRequiredCellTypeIds().join(" ")].join(" "))
+ })
+ this.getTopDownArray().forEach((node, lineNumber) => {
+ const stats = usage.getNode(node.getNodeTypeId())
+ stats.appendLine([filepath + "-" + lineNumber, node.getWords().join(" ")].join(" "))
+ })
+ return usage
+ }
+ getInPlaceHighlightScopeTree() {
+ return this.getTopDownArray()
+ .map(child => child.getIndentation() + child.getLineHighlightScopes())
+ .join("\n")
+ }
+ getInPlaceCellTypeTreeWithNodeConstructorNames() {
+ return this.getTopDownArray()
+ .map(child => child.constructor.name + this.getZI() + child.getIndentation() + child.getLineCellTypes())
+ .join("\n")
+ }
+ getTreeWithNodeTypes() {
+ return this.getTopDownArray()
+ .map(child => child.constructor.name + this.getZI() + child.getIndentation() + child.getLine())
+ .join("\n")
+ }
+ getCellHighlightScopeAtPosition(lineIndex, wordIndex) {
+ this._initCellTypeCache()
+ const typeNode = this._cache_highlightScopeTree.getTopDownArray()[lineIndex - 1]
+ return typeNode ? typeNode.getWord(wordIndex - 1) : undefined
+ }
+ _initCellTypeCache() {
+ const treeMTime = this.getTreeMTime()
+ if (this._cache_programCellTypeStringMTime === treeMTime) return undefined
+ this._cache_typeTree = new TreeNode(this.getInPlaceCellTypeTree())
+ this._cache_highlightScopeTree = new TreeNode(this.getInPlaceHighlightScopeTree())
+ this._cache_programCellTypeStringMTime = treeMTime
+ }
+ }
+ class GrammarBackedNonRootNode extends GrammarBackedNode {
+ getRootProgramNode() {
+ return this.getParent().getRootProgramNode()
+ }
+ createParser() {
+ return new TreeNode.Parser(
+ this.getParent()
+ ._getParser()
+ ._getCatchAllNodeConstructor(this.getParent()),
+ {}
+ )
+ }
+ getNodeTypeId() {
+ return this.getDefinition().getNodeTypeIdFromDefinition()
+ }
+ getDefinition() {
+ return this.getRootProgramNode()
+ .getGrammarProgramRoot()
+ .getNodeTypeDefinitionByNodeTypeId(this.constructor.name)
+ }
+ getGrammarProgramRoot() {
+ return this.getRootProgramNode().getGrammarProgramRoot()
+ }
+ getWordTypes() {
+ return this._getGrammarBackedCellArray().filter(cell => cell.getWord() !== undefined)
+ }
+ _getGrammarBackedCellArray() {
+ const definition = this.getDefinition()
+ const grammarProgram = definition.getLanguageDefinitionProgram()
+ const requiredCellTypeIds = definition.getRequiredCellTypeIds()
+ const numberOfRequiredCells = requiredCellTypeIds.length
+ const catchAllCellTypeId = definition.getCatchAllCellTypeId()
+ const actualWordCountOrRequiredCellCount = Math.max(this.getWords().length, numberOfRequiredCells)
+ const cells = []
+ // A for loop instead of map because "numberOfCellsToFill" can be longer than words.length
+ for (let cellIndex = 0; cellIndex < actualWordCountOrRequiredCellCount; cellIndex++) {
+ const isCatchAll = cellIndex >= numberOfRequiredCells
+ let cellTypeId
+ if (isCatchAll) cellTypeId = catchAllCellTypeId
+ else cellTypeId = requiredCellTypeIds[cellIndex]
+ let cellTypeDefinition = grammarProgram.getCellTypeDefinitionById(cellTypeId)
+ let cellConstructor
+ if (cellTypeDefinition) cellConstructor = cellTypeDefinition.getCellConstructor()
+ else if (cellTypeId) cellConstructor = GrammarUnknownCellTypeCell
+ else {
+ cellConstructor = GrammarExtraWordCellTypeCell
+ cellTypeId = PreludeCellTypeIds.extraWordCell
+ cellTypeDefinition = grammarProgram.getCellTypeDefinitionById(cellTypeId)
+ }
+ cells[cellIndex] = new cellConstructor(this, cellIndex, cellTypeDefinition, cellTypeId, isCatchAll)
+ }
+ return cells
+ }
+ // todo: just make a fn that computes proper spacing and then is given a node to print
+ getLineCellTypes() {
+ return this._getGrammarBackedCellArray()
+ .map(slot => slot.getCellTypeId())
+ .join(" ")
+ }
+ getLineHighlightScopes(defaultScope = "source") {
+ return this._getGrammarBackedCellArray()
+ .map(slot => slot.getHighlightScope() || defaultScope)
+ .join(" ")
+ }
+ getErrors() {
+ const errors = this._getGrammarBackedCellArray()
+ .map(check => check.getErrorIfAny())
+ .filter(i => i)
+ const firstWord = this.getFirstWord()
+ if (this.getDefinition().has(GrammarConstants.single))
+ this.getParent()
+ .findNodes(firstWord)
+ .forEach((node, index) => {
+ if (index) errors.push(new NodeTypeUsedMultipleTimesError(node))
+ })
+ return this._getRequiredNodeErrors(errors)
+ }
+ _getCompiledIndentation() {
+ const indentCharacter = this.getDefinition()._getCompilerObject()[GrammarConstantsCompiler.indentCharacter]
+ const indent = this.getIndentation()
+ return indentCharacter !== undefined ? indentCharacter.repeat(indent.length) : indent
+ }
+ _getCompiledLine() {
+ const compiler = this.getDefinition()._getCompilerObject()
+ const catchAllCellDelimiter = compiler[GrammarConstantsCompiler.catchAllCellDelimiter]
+ const str = compiler[GrammarConstantsCompiler.stringTemplate]
+ return str !== undefined ? TreeUtils.formatStr(str, catchAllCellDelimiter, this.cells) : this.getLine()
+ }
+ compile() {
+ const def = this.getDefinition()
+ if (def.isTerminalNodeType()) return this._getCompiledIndentation() + this._getCompiledLine()
+ const compiler = def._getCompilerObject()
+ const openChildrenString = compiler[GrammarConstantsCompiler.openChildren] || ""
+ const closeChildrenString = compiler[GrammarConstantsCompiler.closeChildren] || ""
+ const childJoinCharacter = compiler[GrammarConstantsCompiler.joinChildrenWith] || "\n"
+ const compiledLine = this._getCompiledLine()
+ const indent = this._getCompiledIndentation()
+ const compiledChildren = this.map(child => child.compile()).join(childJoinCharacter)
+ return `${indent}${compiledLine}${openChildrenString}
+ ${compiledChildren}
+ ${indent}${closeChildrenString}`
+ }
+ // todo: remove
+ get cells() {
+ const cells = {}
+ this._getGrammarBackedCellArray().forEach(cell => {
+ const cellTypeId = cell.getCellTypeId()
+ if (!cell.isCatchAll()) cells[cellTypeId] = cell.getParsed()
+ else {
+ if (!cells[cellTypeId]) cells[cellTypeId] = []
+ cells[cellTypeId].push(cell.getParsed())
+ }
+ })
+ return cells
+ }
+ }
+ class BlobNode extends GrammarBackedNonRootNode {
+ createParser() {
+ return new TreeNode.Parser(BlobNode, {})
+ }
+ getErrors() {
+ return []
+ }
+ }
+ class UnknownNodeTypeNode extends GrammarBackedNonRootNode {
+ createParser() {
+ return new TreeNode.Parser(UnknownNodeTypeNode, {})
+ }
+ getErrors() {
+ return [new UnknownNodeTypeError(this)]
+ }
+ }
+ /*
+ A cell contains a word but also the type information for that word.
+ */
+ class AbstractGrammarBackedCell {
+ constructor(node, index, typeDef, cellTypeId, isCatchAll) {
+ this._typeDef = typeDef
+ this._node = node
+ this._isCatchAll = isCatchAll
+ this._index = index
+ this._cellTypeId = cellTypeId
+ this._word = node.getWord(index)
+ }
+ getCellTypeId() {
+ return this._cellTypeId
+ }
+ getNode() {
+ return this._node
+ }
+ getCellIndex() {
+ return this._index
+ }
+ isCatchAll() {
+ return this._isCatchAll
+ }
+ getHighlightScope() {
+ const definition = this._getCellTypeDefinition()
+ if (definition) return definition.getHighlightScope() // todo: why the undefined?
+ }
+ getAutoCompleteWords(partialWord = "") {
+ const cellDef = this._getCellTypeDefinition()
+ let words = cellDef ? cellDef._getAutocompleteWordOptions(this.getNode().getRootProgramNode()) : []
+ const runTimeOptions = this.getNode().getRunTimeEnumOptions(this)
+ if (runTimeOptions) words = runTimeOptions.concat(words)
+ if (partialWord) words = words.filter(word => word.includes(partialWord))
+ return words.map(word => {
+ return {
+ text: word,
+ displayText: word
+ }
+ })
+ }
+ getWord() {
+ return this._word
+ }
+ _getCellTypeDefinition() {
+ return this._typeDef
+ }
+ _getFullLine() {
+ return this.getNode().getLine()
+ }
+ _getErrorContext() {
+ return this._getFullLine().split(" ")[0] // todo: XI
+ }
+ isValid() {
+ const runTimeOptions = this.getNode().getRunTimeEnumOptions(this)
+ if (runTimeOptions) return runTimeOptions.includes(this._word)
+ return this._getCellTypeDefinition().isValid(this._word, this.getNode().getRootProgramNode()) && this._isValid()
+ }
+ getErrorIfAny() {
+ if (this._word !== undefined && this.isValid()) return undefined
+ // todo: refactor invalidwordError. We want better error messages.
+ return this._word === undefined ? new MissingWordError(this) : new InvalidWordError(this)
+ }
+ }
+ AbstractGrammarBackedCell.parserFunctionName = ""
+ class GrammarIntCell extends AbstractGrammarBackedCell {
+ _isValid() {
+ const num = parseInt(this._word)
+ if (isNaN(num)) return false
+ return num.toString() === this._word
+ }
+ generateSimulatedData() {
+ return TreeUtils.getRandomString(2, "0123456789".split(""))
+ }
+ getRegexString() {
+ return "-?[0-9]+"
+ }
+ getParsed() {
+ return parseInt(this._word)
+ }
+ }
+ GrammarIntCell.defaultHighlightScope = "constant.numeric.integer"
+ GrammarIntCell.parserFunctionName = "parseInt"
+ class GrammarBitCell extends AbstractGrammarBackedCell {
+ _isValid() {
+ const str = this._word
+ return str === "0" || str === "1"
+ }
+ generateSimulatedData() {
+ return TreeUtils.getRandomString(1, "01".split(""))
+ }
+ getRegexString() {
+ return "[01]"
+ }
+ getParsed() {
+ return !!parseInt(this._word)
+ }
+ }
+ GrammarBitCell.defaultHighlightScope = "constant.numeric"
+ class GrammarFloatCell extends AbstractGrammarBackedCell {
+ _isValid() {
+ const num = parseFloat(this._word)
+ return !isNaN(num) && /^-?\d*(\.\d+)?$/.test(this._word)
+ }
+ generateSimulatedData() {
+ return TreeUtils.getRandomString(2, "0123456789".split("")) + "." + TreeUtils.getRandomString(2, "0123456789".split(""))
+ }
+ getRegexString() {
+ return "-?d*(.d+)?"
+ }
+ getParsed() {
+ return parseFloat(this._word)
+ }
+ }
+ GrammarFloatCell.defaultHighlightScope = "constant.numeric.float"
+ GrammarFloatCell.parserFunctionName = "parseFloat"
+ // ErrorCellType => grammar asks for a '' cell type here but the grammar does not specify a '' cell type. (todo: bring in didyoumean?)
+ class GrammarBoolCell extends AbstractGrammarBackedCell {
+ constructor() {
+ super(...arguments)
+ this._trues = new Set(["1", "true", "t", "yes"])
+ this._falses = new Set(["0", "false", "f", "no"])
+ }
+ _isValid() {
+ const str = this._word.toLowerCase()
+ return this._trues.has(str) || this._falses.has(str)
+ }
+ generateSimulatedData() {
+ return TreeUtils.getRandomString(1, ["1", "true", "t", "yes", "0", "false", "f", "no"])
+ }
+ _getOptions() {
+ return Array.from(this._trues).concat(Array.from(this._falses))
+ }
+ getRegexString() {
+ return "(?:" + this._getOptions().join("|") + ")"
+ }
+ getParsed() {
+ return this._trues.has(this._word.toLowerCase())
+ }
+ }
+ GrammarBoolCell.defaultHighlightScope = "constant.numeric"
+ class GrammarAnyCell extends AbstractGrammarBackedCell {
+ _isValid() {
+ return true
+ }
+ generateSimulatedData() {
+ return TreeUtils.getRandomString(10)
+ }
+ getRegexString() {
+ return "[^ ]+"
+ }
+ getParsed() {
+ return this._word
+ }
+ }
+ class GrammarKeywordCell extends GrammarAnyCell {}
+ GrammarKeywordCell.defaultHighlightScope = "keyword"
+ class GrammarExtraWordCellTypeCell extends AbstractGrammarBackedCell {
+ _isValid() {
+ return false
+ }
+ generateSimulatedData() {
+ return ""
+ }
+ getParsed() {
+ return this._word
+ }
+ getErrorIfAny() {
+ return new ExtraWordError(this)
+ }
+ }
+ class GrammarUnknownCellTypeCell extends AbstractGrammarBackedCell {
+ _isValid() {
+ return false
+ }
+ generateSimulatedData() {
+ return ""
+ }
+ getParsed() {
+ return this._word
+ }
+ getErrorIfAny() {
+ return new UnknownCellTypeError(this)
+ }
+ }
+ class AbstractTreeError {
+ constructor(node) {
+ this._node = node
+ }
+ getLineIndex() {
+ return this.getLineNumber() - 1
+ }
+ getLineNumber() {
+ return this.getNode()._getLineNumber() // todo: handle sourcemaps
+ }
+ isCursorOnWord(lineIndex, characterIndex) {
+ return lineIndex === this.getLineIndex() && this._doesCharacterIndexFallOnWord(characterIndex)
+ }
+ _doesCharacterIndexFallOnWord(characterIndex) {
+ return this.getCellIndex() === this.getNode().getWordIndexAtCharacterIndex(characterIndex)
+ }
+ // convenience method. may be removed.
+ isBlankLineError() {
+ return false
+ }
+ // convenience method. may be removed.
+ isMissingWordError() {
+ return false
+ }
+ getIndent() {
+ return this.getNode().getIndentation()
+ }
+ getCodeMirrorLineWidgetElement(onApplySuggestionCallBack = () => {}) {
+ const suggestion = this.getSuggestionMessage()
+ if (this.isMissingWordError()) return this._getCodeMirrorLineWidgetElementCellTypeHints()
+ if (suggestion) return this._getCodeMirrorLineWidgetElementWithSuggestion(onApplySuggestionCallBack, suggestion)
+ return this._getCodeMirrorLineWidgetElementWithoutSuggestion()
+ }
+ getNodeTypeId() {
+ return this.getNode()
+ .getDefinition()
+ .getNodeTypeIdFromDefinition()
+ }
+ _getCodeMirrorLineWidgetElementCellTypeHints() {
+ const el = document.createElement("div")
+ el.appendChild(
+ document.createTextNode(
+ this.getIndent() +
+ this.getNode()
+ .getDefinition()
+ .getLineHints()
+ )
+ )
+ el.className = "LintCellTypeHints"
+ return el
+ }
+ _getCodeMirrorLineWidgetElementWithoutSuggestion() {
+ const el = document.createElement("div")
+ el.appendChild(document.createTextNode(this.getIndent() + this.getMessage()))
+ el.className = "LintError"
+ return el
+ }
+ _getCodeMirrorLineWidgetElementWithSuggestion(onApplySuggestionCallBack, suggestion) {
+ const el = document.createElement("div")
+ el.appendChild(document.createTextNode(this.getIndent() + `${this.getErrorTypeName()}. Suggestion: ${suggestion}`))
+ el.className = "LintErrorWithSuggestion"
+ el.onclick = () => {
+ this.applySuggestion()
+ onApplySuggestionCallBack()
+ }
+ return el
+ }
+ getLine() {
+ return this.getNode().getLine()
+ }
+ getExtension() {
+ return this.getNode()
+ .getGrammarProgramRoot()
+ .getExtensionName()
+ }
+ getNode() {
+ return this._node
+ }
+ getErrorTypeName() {
+ return this.constructor.name.replace("Error", "")
+ }
+ getCellIndex() {
+ return 0
+ }
+ toObject() {
+ return {
+ type: this.getErrorTypeName(),
+ line: this.getLineNumber(),
+ cell: this.getCellIndex(),
+ suggestion: this.getSuggestionMessage(),
+ path: this.getNode().getFirstWordPath(),
+ message: this.getMessage()
+ }
+ }
+ hasSuggestion() {
+ return this.getSuggestionMessage() !== ""
+ }
+ getSuggestionMessage() {
+ return ""
+ }
+ toString() {
+ return this.getMessage()
+ }
+ applySuggestion() {}
+ getMessage() {
+ return `${this.getErrorTypeName()} at line ${this.getLineNumber()} cell ${this.getCellIndex()}.`
+ }
+ }
+ class AbstractCellError extends AbstractTreeError {
+ constructor(cell) {
+ super(cell.getNode())
+ this._cell = cell
+ }
+ getCell() {
+ return this._cell
+ }
+ getCellIndex() {
+ return this._cell.getCellIndex()
+ }
+ _getWordSuggestion() {
+ return TreeUtils.didYouMean(
+ this.getCell().getWord(),
+ this.getCell()
+ .getAutoCompleteWords()
+ .map(option => option.text)
+ )
+ }
+ }
+ class UnknownNodeTypeError extends AbstractTreeError {
+ getMessage() {
+ const node = this.getNode()
+ const parentNode = node.getParent()
+ const options = parentNode._getParser().getFirstWordOptions()
+ return super.getMessage() + ` Invalid nodeType "${node.getFirstWord()}". Valid nodeTypes are: ${TreeUtils._listToEnglishText(options, 7)}.`
+ }
+ _getWordSuggestion() {
+ const node = this.getNode()
+ const parentNode = node.getParent()
+ return TreeUtils.didYouMean(node.getFirstWord(), parentNode.getAutocompleteResults("", 0).map(option => option.text))
+ }
+ getSuggestionMessage() {
+ const suggestion = this._getWordSuggestion()
+ const node = this.getNode()
+ if (suggestion) return `Change "${node.getFirstWord()}" to "${suggestion}"`
+ return ""
+ }
+ applySuggestion() {
+ const suggestion = this._getWordSuggestion()
+ if (suggestion) this.getNode().setWord(this.getCellIndex(), suggestion)
+ return this
+ }
+ }
+ class BlankLineError extends UnknownNodeTypeError {
+ getMessage() {
+ return super.getMessage() + ` Line: "${this.getNode().getLine()}". Blank lines are errors.`
+ }
+ // convenience method
+ isBlankLineError() {
+ return true
+ }
+ getSuggestionMessage() {
+ return `Delete line ${this.getLineNumber()}`
+ }
+ applySuggestion() {
+ this.getNode().destroy()
+ return this
+ }
+ }
+ class MissingRequiredNodeTypeError extends AbstractTreeError {
+ constructor(node, missingNodeTypeId) {
+ super(node)
+ this._missingNodeTypeId = missingNodeTypeId
+ }
+ getMessage() {
+ return super.getMessage() + ` A "${this._missingNodeTypeId}" is required.`
+ }
+ }
+ class NodeTypeUsedMultipleTimesError extends AbstractTreeError {
+ getMessage() {
+ return super.getMessage() + ` Multiple "${this.getNode().getFirstWord()}" found.`
+ }
+ getSuggestionMessage() {
+ return `Delete line ${this.getLineNumber()}`
+ }
+ applySuggestion() {
+ return this.getNode().destroy()
+ }
+ }
+ class UnknownCellTypeError extends AbstractCellError {
+ getMessage() {
+ return super.getMessage() + ` No cellType "${this.getCell().getCellTypeId()}" found. Language grammar for "${this.getExtension()}" may need to be fixed.`
+ }
+ }
+ class InvalidWordError extends AbstractCellError {
+ getMessage() {
+ return super.getMessage() + ` "${this.getCell().getWord()}" does not fit in cellType "${this.getCell().getCellTypeId()}".`
+ }
+ getSuggestionMessage() {
+ const suggestion = this._getWordSuggestion()
+ if (suggestion) return `Change "${this.getCell().getWord()}" to "${suggestion}"`
+ return ""
+ }
+ applySuggestion() {
+ const suggestion = this._getWordSuggestion()
+ if (suggestion) this.getNode().setWord(this.getCellIndex(), suggestion)
+ return this
+ }
+ }
+ class ExtraWordError extends AbstractCellError {
+ getMessage() {
+ return super.getMessage() + ` Extra word "${this.getCell().getWord()}" in ${this.getNodeTypeId()}.`
+ }
+ getSuggestionMessage() {
+ return `Delete word "${this.getCell().getWord()}" at cell ${this.getCellIndex()}`
+ }
+ applySuggestion() {
+ return this.getNode().deleteWordAt(this.getCellIndex())
+ }
+ }
+ class MissingWordError extends AbstractCellError {
+ // todo: autocomplete suggestion
+ getMessage() {
+ return super.getMessage() + ` Missing word for cell "${this.getCell().getCellTypeId()}".`
+ }
+ isMissingWordError() {
+ return true
+ }
+ }
+ // todo: add standard types, enum types, from disk types
+ class AbstractGrammarWordTestNode extends TreeNode {}
+ class GrammarRegexTestNode extends AbstractGrammarWordTestNode {
+ isValid(str) {
+ if (!this._regex) this._regex = new RegExp("^" + this.getContent() + "$")
+ return !!str.match(this._regex)
+ }
+ }
+ class GrammarReservedWordsTestNode extends AbstractGrammarWordTestNode {
+ isValid(str) {
+ if (!this._set) this._set = new Set(this.getContent().split(" "))
+ return !this._set.has(str)
+ }
+ }
+ // todo: remove in favor of custom word type constructors
+ class EnumFromCellTypesTestNode extends AbstractGrammarWordTestNode {
+ _getEnumFromCellTypes(programRootNode) {
+ const cellTypeIds = this.getWordsFrom(1)
+ const enumGroup = cellTypeIds.join(" ")
+ // note: hack where we store it on the program. otherwise has global effects.
+ if (!programRootNode._enumMaps) programRootNode._enumMaps = {}
+ if (programRootNode._enumMaps[enumGroup]) return programRootNode._enumMaps[enumGroup]
+ const wordIndex = 1
+ const map = {}
+ const cellTypeMap = {}
+ cellTypeIds.forEach(typeId => (cellTypeMap[typeId] = true))
+ programRootNode
+ .getAllTypedWords()
+ .filter(typedWord => cellTypeMap[typedWord.type])
+ .forEach(typedWord => {
+ map[typedWord.word] = true
+ })
+ programRootNode._enumMaps[enumGroup] = map
+ return map
+ }
+ // todo: remove
+ isValid(str, programRootNode) {
+ return this._getEnumFromCellTypes(programRootNode)[str] === true
+ }
+ }
+ class GrammarEnumTestNode extends AbstractGrammarWordTestNode {
+ isValid(str) {
+ // enum c c++ java
+ return !!this.getOptions()[str]
+ }
+ getOptions() {
+ if (!this._map) this._map = TreeUtils.arrayToMap(this.getWordsFrom(1))
+ return this._map
+ }
+ }
+ class cellTypeDefinitionNode extends AbstractExtendibleTreeNode {
+ createParser() {
+ const types = {}
+ types[GrammarConstants.regex] = GrammarRegexTestNode
+ types[GrammarConstants.reservedWords] = GrammarReservedWordsTestNode
+ types[GrammarConstants.enumFromCellTypes] = EnumFromCellTypesTestNode
+ types[GrammarConstants.enum] = GrammarEnumTestNode
+ types[GrammarConstants.highlightScope] = TreeNode
+ types[GrammarConstants.todoComment] = TreeNode
+ types[GrammarConstants.description] = TreeNode
+ types[GrammarConstants.extends] = TreeNode
+ return new TreeNode.Parser(undefined, types)
+ }
+ _getId() {
+ return this.getWord(0)
+ }
+ _getIdToNodeMap() {
+ return this._getRootProgramNode().getCellTypeDefinitions()
+ }
+ getGetter(wordIndex) {
+ const wordToNativeJavascriptTypeParser = this.getCellConstructor().parserFunctionName
+ return `get ${this.getCellTypeId()}() {
+ return ${wordToNativeJavascriptTypeParser ? wordToNativeJavascriptTypeParser + `(this.getWord(${wordIndex}))` : `this.getWord(${wordIndex})`}
+ }`
+ }
+ getCatchAllGetter(wordIndex) {
+ const wordToNativeJavascriptTypeParser = this.getCellConstructor().parserFunctionName
+ return `get ${this.getCellTypeId()}() {
+ return ${
+ wordToNativeJavascriptTypeParser
+ ? `this.getWordsFrom(${wordIndex}).map(val => ${wordToNativeJavascriptTypeParser}(val))`
+ : `this.getWordsFrom(${wordIndex})`
+ }
+ }`
+ }
+ // `this.getWordsFrom(${requireds.length + 1})`
+ // todo: cleanup typings. todo: remove this hidden logic. have a "baseType" property?
+ getCellConstructor() {
+ return this._getPreludeKind() || GrammarAnyCell
+ }
+ _getPreludeKind() {
+ const kinds = {}
+ kinds[PreludeCellTypeIds.anyCell] = GrammarAnyCell
+ kinds[PreludeCellTypeIds.keywordCell] = GrammarKeywordCell
+ kinds[PreludeCellTypeIds.floatCell] = GrammarFloatCell
+ kinds[PreludeCellTypeIds.numberCell] = GrammarFloatCell
+ kinds[PreludeCellTypeIds.bitCell] = GrammarBitCell
+ kinds[PreludeCellTypeIds.boolCell] = GrammarBoolCell
+ kinds[PreludeCellTypeIds.intCell] = GrammarIntCell
+ return kinds[this.getWord(0)] || kinds[this._getExtendedCellTypeId()]
+ }
+ _getExtendedCellTypeId() {
+ return this.get(GrammarConstants.extends)
+ }
+ getHighlightScope() {
+ const hs = this._getFromExtended(GrammarConstants.highlightScope)
+ if (hs) return hs
+ const preludeKind = this._getPreludeKind()
+ if (preludeKind) return preludeKind.defaultHighlightScope
+ }
+ _getEnumOptions() {
+ const enumNode = this._getNodeFromExtended(GrammarConstants.enum)
+ if (!enumNode) return undefined
+ // we sort by longest first to capture longest match first. todo: add test
+ const options = Object.keys(enumNode.getNode(GrammarConstants.enum).getOptions())
+ options.sort((a, b) => b.length - a.length)
+ return options
+ }
+ _getEnumFromCellTypeOptions(program) {
+ const node = this._getNodeFromExtended(GrammarConstants.enumFromCellTypes)
+ return node ? Object.keys(node.getNode(GrammarConstants.enumFromCellTypes)._getEnumFromCellTypes(program)) : undefined
+ }
+ _getRootProgramNode() {
+ return this.getParent()
+ }
+ _getAutocompleteWordOptions(program) {
+ return this._getEnumOptions() || this._getEnumFromCellTypeOptions(program) || []
+ }
+ getRegexString() {
+ // todo: enum
+ const enumOptions = this._getEnumOptions()
+ return this._getFromExtended(GrammarConstants.regex) || (enumOptions ? "(?:" + enumOptions.join("|") + ")" : "[^ ]*")
+ }
+ isValid(str, programRootNode) {
+ return this._getChildrenByNodeConstructorInExtended(AbstractGrammarWordTestNode).every(node => node.isValid(str, programRootNode))
+ }
+ getCellTypeId() {
+ return this.getWord(0)
+ }
+ }
+ class GrammarExampleNode extends TreeNode {}
+ class GrammarCompilerNode extends TreeNode {
+ createParser() {
+ const types = [
+ GrammarConstantsCompiler.stringTemplate,
+ GrammarConstantsCompiler.indentCharacter,
+ GrammarConstantsCompiler.catchAllCellDelimiter,
+ GrammarConstantsCompiler.joinChildrenWith,
+ GrammarConstantsCompiler.openChildren,
+ GrammarConstantsCompiler.closeChildren
+ ]
+ const map = {}
+ types.forEach(type => {
+ map[type] = TreeNode
+ })
+ return new TreeNode.Parser(undefined, map)
+ }
+ }
+ class GrammarNodeTypeConstant extends TreeNode {
+ getGetter() {
+ return `get ${this.getIdentifier()}() { return ${this.getConstantValueAsJsText()} }`
+ }
+ getIdentifier() {
+ return this.getWord(1)
+ }
+ getConstantValueAsJsText() {
+ const words = this.getWordsFrom(2)
+ return words.length > 1 ? `[${words.join(",")}]` : words[0]
+ }
+ getConstantValue() {
+ return JSON.parse(this.getConstantValueAsJsText())
+ }
+ }
+ class GrammarNodeTypeConstantInt extends GrammarNodeTypeConstant {}
+ class GrammarNodeTypeConstantString extends GrammarNodeTypeConstant {
+ getConstantValueAsJsText() {
+ return "`" + TreeUtils.escapeBackTicks(this.getConstantValue()) + "`"
+ }
+ getConstantValue() {
+ return this.length ? this.childrenToString() : this.getWordsFrom(2).join(" ")
+ }
+ }
+ class GrammarNodeTypeConstantFloat extends GrammarNodeTypeConstant {}
+ class GrammarNodeTypeConstantBoolean extends GrammarNodeTypeConstant {}
+ class AbstractGrammarDefinitionNode extends AbstractExtendibleTreeNode {
+ createParser() {
+ // todo: some of these should just be on nonRootNodes
+ const types = [
+ GrammarConstants.frequency,
+ GrammarConstants.inScope,
+ GrammarConstants.cells,
+ GrammarConstants.extends,
+ GrammarConstants.description,
+ GrammarConstants.catchAllNodeType,
+ GrammarConstants.catchAllCellType,
+ GrammarConstants.extensions,
+ GrammarConstants.version,
+ GrammarConstants.tags,
+ GrammarConstants.match,
+ GrammarConstants.pattern,
+ GrammarConstants.baseNodeType,
+ GrammarConstants.required,
+ GrammarConstants.root,
+ GrammarConstants.compilesTo,
+ GrammarConstants.abstract,
+ GrammarConstants.javascript,
+ GrammarConstants.single,
+ GrammarConstants.todoComment
+ ]
+ const map = {}
+ types.forEach(type => {
+ map[type] = TreeNode
+ })
+ map[GrammarConstantsConstantTypes.boolean] = GrammarNodeTypeConstantBoolean
+ map[GrammarConstantsConstantTypes.int] = GrammarNodeTypeConstantInt
+ map[GrammarConstantsConstantTypes.string] = GrammarNodeTypeConstantString
+ map[GrammarConstantsConstantTypes.float] = GrammarNodeTypeConstantFloat
+ map[GrammarConstants.compilerNodeType] = GrammarCompilerNode
+ map[GrammarConstants.example] = GrammarExampleNode
+ return new TreeNode.Parser(undefined, map)
+ }
+ _getId() {
+ return this.getWord(0)
+ }
+ getConstantsObject() {
+ const obj = this._getUniqueConstantNodes()
+ Object.keys(obj).forEach(key => {
+ obj[key] = obj[key].getConstantValue()
+ })
+ return obj
+ }
+ _getUniqueConstantNodes(extended = true) {
+ const obj = {}
+ const items = extended ? this._getChildrenByNodeConstructorInExtended(GrammarNodeTypeConstant) : this.getChildrenByNodeConstructor(GrammarNodeTypeConstant)
+ items.reverse() // Last definition wins.
+ items.forEach(node => {
+ obj[node.getIdentifier()] = node
+ })
+ return obj
+ }
+ getExamples() {
+ return this._getChildrenByNodeConstructorInExtended(GrammarExampleNode)
+ }
+ getNodeTypeIdFromDefinition() {
+ return this.getWord(0)
+ }
+ // todo: remove? just reused nodeTypeId
+ _getGeneratedClassName() {
+ return this.getNodeTypeIdFromDefinition()
+ }
+ _hasValidNodeTypeId() {
+ return !!this._getGeneratedClassName()
+ }
+ _isAbstract() {
+ return this.has(GrammarConstants.abstract)
+ }
+ _getConstructorDefinedInGrammar() {
+ if (!this._cache_definedNodeConstructor)
+ this._cache_definedNodeConstructor = this.getLanguageDefinitionProgram()._getCompiledLoadedNodeTypes()[this.getNodeTypeIdFromDefinition()]
+ return this._cache_definedNodeConstructor
+ }
+ _getFirstWordMatch() {
+ if (this._getRegexMatch())
+ // todo: enforce firstWordMatch and regexMatch as being XOR
+ return undefined
+ return this.get(GrammarConstants.match) || this._getNodeTypeIdWithoutNodeTypeSuffix()
+ }
+ _getNodeTypeIdWithoutNodeTypeSuffix() {
+ return this.getNodeTypeIdFromDefinition().replace(GrammarProgram.nodeTypeSuffixRegex, "")
+ }
+ _getRegexMatch() {
+ return this.get(GrammarConstants.pattern)
+ }
+ getLanguageDefinitionProgram() {
+ return this.getParent()
+ }
+ _getCustomJavascriptMethods() {
+ const hasJsCode = this.has(GrammarConstants.javascript)
+ return hasJsCode ? this.getNode(GrammarConstants.javascript).childrenToString() : ""
+ }
+ getFirstWordMapWithDefinitions() {
+ if (!this._cache_firstWordToNodeDefMap) this._cache_firstWordToNodeDefMap = this._createParserInfo(this._getInScopeNodeTypeIds()).firstWordMap
+ return this._cache_firstWordToNodeDefMap
+ }
+ // todo: remove
+ getRunTimeFirstWordsInScope() {
+ return this._getParser().getFirstWordOptions()
+ }
+ getRequiredCellTypeIds() {
+ const parameters = this._getFromExtended(GrammarConstants.cells)
+ return parameters ? parameters.split(" ") : []
+ }
+ // todo: what happens when you have a cell getter and constant with same name?
+ _getCellGettersAndNodeTypeConstants() {
+ // todo: add cellType parsings
+ const grammarProgram = this.getLanguageDefinitionProgram()
+ const requiredCells = this.get(GrammarConstants.cells)
+ const getters = (requiredCells ? requiredCells.split(" ") : []).map((cellTypeId, index) => {
+ const cellTypeDef = grammarProgram.getCellTypeDefinitionById(cellTypeId)
+ if (!cellTypeDef) throw new Error(`No cellType "${cellTypeId}" found`)
+ return cellTypeDef.getGetter(index)
+ })
+ const catchAllCellTypeId = this.get(GrammarConstants.catchAllCellType)
+ if (catchAllCellTypeId) getters.push(grammarProgram.getCellTypeDefinitionById(catchAllCellTypeId).getCatchAllGetter(getters.length))
+ // Constants
+ Object.values(this._getUniqueConstantNodes(false)).forEach(node => {
+ getters.push(node.getGetter())
+ })
+ return getters.join("\n")
+ }
+ getCatchAllCellTypeId() {
+ return this._getFromExtended(GrammarConstants.catchAllCellType)
+ }
+ _createParserInfo(nodeTypeIdsInScope) {
+ const result = {
+ firstWordMap: {},
+ regexTests: []
+ }
+ if (!nodeTypeIdsInScope.length) return result
+ const allProgramNodeTypeDefinitionsMap = this._getProgramNodeTypeDefinitionCache()
+ Object.keys(allProgramNodeTypeDefinitionsMap)
+ .filter(nodeTypeId => allProgramNodeTypeDefinitionsMap[nodeTypeId].isOrExtendsANodeTypeInScope(nodeTypeIdsInScope))
+ .filter(nodeTypeId => !allProgramNodeTypeDefinitionsMap[nodeTypeId]._isAbstract())
+ .forEach(nodeTypeId => {
+ const def = allProgramNodeTypeDefinitionsMap[nodeTypeId]
+ const regex = def._getRegexMatch()
+ const firstWord = def._getFirstWordMatch()
+ if (regex) result.regexTests.push({ regex: regex, nodeConstructor: def.getNodeTypeIdFromDefinition() })
+ else result.firstWordMap[firstWord] = def
+ })
+ return result
+ }
+ getTopNodeTypeIds() {
+ const arr = Object.values(this.getFirstWordMapWithDefinitions())
+ arr.sort(TreeUtils.makeSortByFn(definition => definition.getFrequency()))
+ arr.reverse()
+ return arr.map(definition => definition.getNodeTypeIdFromDefinition())
+ }
+ _getMyInScopeNodeTypeIds() {
+ const nodeTypesNode = this.getNode(GrammarConstants.inScope)
+ return nodeTypesNode ? nodeTypesNode.getWordsFrom(1) : []
+ }
+ _getInScopeNodeTypeIds() {
+ // todo: allow multiple of these if we allow mixins?
+ const ids = this._getMyInScopeNodeTypeIds()
+ const parentDef = this._getExtendedParent()
+ return parentDef ? ids.concat(parentDef._getInScopeNodeTypeIds()) : ids
+ }
+ isRequired() {
+ return this._hasFromExtended(GrammarConstants.required)
+ }
+ getNodeTypeDefinitionByNodeTypeId(nodeTypeId) {
+ // todo: return catch all?
+ const def = this._getProgramNodeTypeDefinitionCache()[nodeTypeId]
+ if (def) return def
+ // todo: cleanup
+ this.getLanguageDefinitionProgram()._addDefaultCatchAllBlobNode()
+ return this._getProgramNodeTypeDefinitionCache()[nodeTypeId]
+ }
+ isDefined(nodeTypeId) {
+ return !!this._getProgramNodeTypeDefinitionCache()[nodeTypeId]
+ }
+ _getIdToNodeMap() {
+ return this._getProgramNodeTypeDefinitionCache()
+ }
+ _amIRoot() {
+ if (this._cache_isRoot === undefined) this._cache_isRoot = this._getLanguageRootNode() === this
+ return this._cache_isRoot
+ }
+ _getLanguageRootNode() {
+ return this.getParent()._getRootNodeTypeDefinitionNode()
+ }
+ _isErrorNodeType() {
+ return this.get(GrammarConstants.baseNodeType) === GrammarConstants.errorNode
+ }
+ _isBlobNodeType() {
+ // Do not check extended classes. Only do once.
+ return this.get(GrammarConstants.baseNodeType) === GrammarConstants.blobNode
+ }
+ _getErrorMethodToJavascript() {
+ if (this._isBlobNodeType()) return "getErrors() { return [] }" // Skips parsing child nodes for perf gains.
+ if (this._isErrorNodeType()) return "getErrors() { return this._getErrorNodeErrors() }"
+ return ""
+ }
+ _getParserToJavascript() {
+ if (this._isBlobNodeType())
+ // todo: do we need this?
+ return "createParser() { return new jtree.TreeNode.Parser(this._getBlobNodeCatchAllNodeType())}"
+ const parserInfo = this._createParserInfo(this._getMyInScopeNodeTypeIds())
+ const myFirstWordMap = parserInfo.firstWordMap
+ const regexRules = parserInfo.regexTests
+ // todo: use constants in first word maps?
+ // todo: cache the super extending?
+ const firstWords = Object.keys(myFirstWordMap)
+ const hasFirstWords = firstWords.length
+ const catchAllConstructor = this._getCatchAllNodeConstructorToJavascript()
+ if (!hasFirstWords && !catchAllConstructor && !regexRules.length) return ""
+ const firstWordsStr = hasFirstWords
+ ? `Object.assign(Object.assign({}, super.createParser()._getFirstWordMap()), {` +
+ firstWords.map(firstWord => `"${firstWord}" : ${myFirstWordMap[firstWord].getNodeTypeIdFromDefinition()}`).join(",\n") +
+ "})"
+ : "undefined"
+ const regexStr = regexRules.length
+ ? `[${regexRules
+ .map(rule => {
+ return `{regex: /${rule.regex}/, nodeConstructor: ${rule.nodeConstructor}}`
+ })
+ .join(",")}]`
+ : "undefined"
+ const catchAllStr = catchAllConstructor ? catchAllConstructor : this._amIRoot() ? `this._getBlobNodeCatchAllNodeType()` : "undefined"
+ return `createParser() {
+ return new jtree.TreeNode.Parser(${catchAllStr}, ${firstWordsStr}, ${regexStr})
+ }`
+ }
+ _getCatchAllNodeConstructorToJavascript() {
+ if (this._isBlobNodeType()) return "this._getBlobNodeCatchAllNodeType()"
+ const nodeTypeId = this.get(GrammarConstants.catchAllNodeType)
+ if (!nodeTypeId) return ""
+ const nodeDef = this.getNodeTypeDefinitionByNodeTypeId(nodeTypeId)
+ if (!nodeDef) throw new Error(`No definition found for nodeType id "${nodeTypeId}"`)
+ return nodeDef._getGeneratedClassName()
+ }
+ _nodeDefToJavascriptClass() {
+ const components = [
+ this._getParserToJavascript(),
+ this._getErrorMethodToJavascript(),
+ this._getCellGettersAndNodeTypeConstants(),
+ this._getCustomJavascriptMethods()
+ ].filter(code => code)
+ const extendedDef = this._getExtendedParent()
+ const rootNode = this._getLanguageRootNode()
+ const amIRoot = this._amIRoot()
+ // todo: cleanup? If we have 2 roots, and the latter extends the first, the first should extent GBRootNode. Otherwise, the first should not extend RBRootNode.
+ const doesRootExtendMe = this.has(GrammarConstants.root) && rootNode._getAncestorSet().has(this._getGeneratedClassName())
+ const extendsClassName = extendedDef
+ ? extendedDef._getGeneratedClassName()
+ : amIRoot || doesRootExtendMe
+ ? "jtree.GrammarBackedRootNode"
+ : "jtree.GrammarBackedNonRootNode"
+ if (amIRoot) {
+ components.push(`getGrammarProgramRoot() {
+ if (!this._cachedGrammarProgramRoot)
+ this._cachedGrammarProgramRoot = new jtree.GrammarProgram(\`${TreeUtils.escapeBackTicks(
+ this.getParent()
+ .toString()
+ .replace(/\\/g, "\\\\")
+ )}\`)
+ return this._cachedGrammarProgramRoot
+ }`)
+ const nodeTypeMap = this.getLanguageDefinitionProgram()
+ .getValidConcreteAndAbstractNodeTypeDefinitions()
+ .map(def => {
+ const id = def.getNodeTypeIdFromDefinition()
+ return `"${id}": ${id}`
+ })
+ .join(",\n")
+ components.push(`static getNodeTypeMap() { return {${nodeTypeMap} }}`)
+ }
+ return `class ${this._getGeneratedClassName()} extends ${extendsClassName} {
+ ${components.join("\n")}
+ }`
+ }
+ _getCompilerObject() {
+ let obj = {}
+ const items = this._getChildrenByNodeConstructorInExtended(GrammarCompilerNode)
+ items.reverse() // Last definition wins.
+ items.forEach(node => {
+ obj = Object.assign(obj, node.toObject()) // todo: what about multiline strings?
+ })
+ return obj
+ }
+ // todo: improve layout (use bold?)
+ getLineHints() {
+ const catchAllCellTypeId = this.getCatchAllCellTypeId()
+ return `${this.getNodeTypeIdFromDefinition()}: ${this.getRequiredCellTypeIds().join(" ")}${catchAllCellTypeId ? ` ${catchAllCellTypeId}...` : ""}`
+ }
+ isOrExtendsANodeTypeInScope(firstWordsInScope) {
+ const chain = this._getNodeTypeInheritanceSet()
+ return firstWordsInScope.some(firstWord => chain.has(firstWord))
+ }
+ isTerminalNodeType() {
+ return !this._getFromExtended(GrammarConstants.inScope) && !this._getFromExtended(GrammarConstants.catchAllNodeType)
+ }
+ getMatchBlock() {
+ const defaultHighlightScope = "source"
+ const program = this.getLanguageDefinitionProgram()
+ const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
+ const regexMatch = this._getRegexMatch()
+ const firstWordMatch = this._getFirstWordMatch()
+ const match = regexMatch ? `'${regexMatch}'` : `'^ *${escapeRegExp(firstWordMatch)}(?: |$)'`
+ const requiredCellTypeIds = this.getRequiredCellTypeIds()
+ const catchAllCellTypeId = this.getCatchAllCellTypeId()
+ const firstCellTypeDef = program.getCellTypeDefinitionById(requiredCellTypeIds[0])
+ const firstWordHighlightScope = (firstCellTypeDef ? firstCellTypeDef.getHighlightScope() : defaultHighlightScope) + "." + this.getNodeTypeIdFromDefinition()
+ const topHalf = ` '${this.getNodeTypeIdFromDefinition()}':
+ - match: ${match}
+ scope: ${firstWordHighlightScope}`
+ if (catchAllCellTypeId) requiredCellTypeIds.push(catchAllCellTypeId)
+ if (!requiredCellTypeIds.length) return topHalf
+ const captures = requiredCellTypeIds
+ .map((cellTypeId, index) => {
+ const cellTypeDefinition = program.getCellTypeDefinitionById(cellTypeId) // todo: cleanup
+ if (!cellTypeDefinition) throw new Error(`No ${GrammarConstants.cellType} ${cellTypeId} found`) // todo: standardize error/capture error at grammar time
+ return ` ${index + 1}: ${(cellTypeDefinition.getHighlightScope() || defaultHighlightScope) + "." + cellTypeDefinition.getCellTypeId()}`
+ })
+ .join("\n")
+ const cellTypesToRegex = cellTypeIds => cellTypeIds.map(cellTypeId => `({{${cellTypeId}}})?`).join(" ?")
+ return `${topHalf}
+ push:
+ - match: ${cellTypesToRegex(requiredCellTypeIds)}
+ captures:
+ ${captures}
+ - match: $
+ pop: true`
+ }
+ _getNodeTypeInheritanceSet() {
+ if (!this._cache_nodeTypeInheritanceSet) this._cache_nodeTypeInheritanceSet = new Set(this.getAncestorNodeTypeIdsArray())
+ return this._cache_nodeTypeInheritanceSet
+ }
+ getAncestorNodeTypeIdsArray() {
+ if (!this._cache_ancestorNodeTypeIdsArray) {
+ this._cache_ancestorNodeTypeIdsArray = this._getAncestorsArray().map(def => def.getNodeTypeIdFromDefinition())
+ this._cache_ancestorNodeTypeIdsArray.reverse()
+ }
+ return this._cache_ancestorNodeTypeIdsArray
+ }
+ _getProgramNodeTypeDefinitionCache() {
+ return this.getLanguageDefinitionProgram()._getProgramNodeTypeDefinitionCache()
+ }
+ getDescription() {
+ return this._getFromExtended(GrammarConstants.description) || ""
+ }
+ getFrequency() {
+ const val = this._getFromExtended(GrammarConstants.frequency)
+ return val ? parseFloat(val) : 0
+ }
+ _getExtendedNodeTypeId() {
+ const ancestorIds = this.getAncestorNodeTypeIdsArray()
+ if (ancestorIds.length > 1) return ancestorIds[ancestorIds.length - 2]
+ }
+ }
+ // todo: remove?
+ class nodeTypeDefinitionNode extends AbstractGrammarDefinitionNode {}
+ // GrammarProgram is a constructor that takes a grammar file, and builds a new
+ // constructor for new language that takes files in that language to execute, compile, etc.
+ class GrammarProgram extends AbstractGrammarDefinitionNode {
+ createParser() {
+ const map = {}
+ map[GrammarConstants.toolingDirective] = TreeNode
+ map[GrammarConstants.todoComment] = TreeNode
+ return new TreeNode.Parser(UnknownNodeTypeNode, map, [
+ { regex: GrammarProgram.nodeTypeFullRegex, nodeConstructor: nodeTypeDefinitionNode },
+ { regex: GrammarProgram.cellTypeFullRegex, nodeConstructor: cellTypeDefinitionNode }
+ ])
+ }
+ _getCompiledLoadedNodeTypes() {
+ if (!this._cache_compiledLoadedNodeTypes) {
+ if (this.isNodeJs()) {
+ const code = this.toNodeJsJavascript(__dirname + "/../index.js")
+ try {
+ const rootNode = this._importNodeJsRootNodeTypeConstructor(code)
+ this._cache_compiledLoadedNodeTypes = rootNode.getNodeTypeMap()
+ if (!this._cache_compiledLoadedNodeTypes) throw new Error(`Failed to getNodeTypeMap`)
+ } catch (err) {
+ console.log(err)
+ console.log(`Error in code: `)
+ console.log(code)
+ }
+ } else
+ this._cache_compiledLoadedNodeTypes = this._importBrowserRootNodeTypeConstructor(this.toBrowserJavascript(), this.getGrammarName()).getNodeTypeMap()
+ }
+ return this._cache_compiledLoadedNodeTypes
+ }
+ _importNodeJsRootNodeTypeConstructor(code) {
+ const vm = require("vm")
+ // todo: cleanup up
+ try {
+ global.jtree = require(__dirname + "/../index.js")
+ global.require = require
+ global.module = {}
+ return vm.runInThisContext(code)
+ } catch (err) {
+ console.log(`Error in compiled grammar code for language "${this.getGrammarName()}":`)
+ console.log(
+ code
+ .split("\n")
+ .map((line, index) => index + 1 + " " + line)
+ .join("\n")
+ )
+ console.log(err)
+ throw err
+ }
+ }
+ _importBrowserRootNodeTypeConstructor(code, name) {
+ const script = document.createElement("script")
+ script.innerHTML = code
+ document.head.appendChild(script)
+ return window[name]
+ }
+ // todo: better formalize the source maps pattern somewhat used here by getAllErrors
+ // todo: move this to Grammar.grammar (or just get the bootstrapping done.)
+ getErrorsInGrammarExamples() {
+ const programConstructor = this.getRootConstructor()
+ const errors = []
+ this.getValidConcreteAndAbstractNodeTypeDefinitions().forEach(def =>
+ def.getExamples().forEach(example => {
+ const exampleProgram = new programConstructor(example.childrenToString())
+ exampleProgram.getAllErrors(example._getLineNumber() + 1).forEach(err => {
+ errors.push(err)
+ })
+ })
+ )
+ return errors
+ }
+ toReadMe() {
+ const languageName = this.getExtensionName()
+ const rootNodeDef = this._getRootNodeTypeDefinitionNode()
+ const cellTypes = this.getCellTypeDefinitions()
+ const nodeTypeFamilyTree = this.getNodeTypeFamilyTree()
+ const exampleNode = rootNodeDef.getExamples()[0]
+ return `title ${languageName} Readme
+
+ paragraph ${rootNodeDef.getDescription()}
+
+ subtitle Quick Example
+
+ code
+ ${exampleNode ? exampleNode.childrenToString(1) : ""}
+
+ subtitle Quick facts about ${languageName}
+
+ list
+ - ${languageName} has ${nodeTypeFamilyTree.getTopDownArray().length} node types.
+ - ${languageName} has ${Object.keys(cellTypes).length} cell types
+ - The source code for ${languageName} is ${this.getTopDownArray().length} lines long.
+
+ subtitle Installing
+
+ code
+ npm install .
+
+ subtitle Testing
+
+ code
+ node test.js
+
+ subtitle Node Types
+
+ code
+ ${nodeTypeFamilyTree.toString(1)}
+
+ subtitle Cell Types
+
+ code
+ ${new TreeNode(Object.keys(cellTypes).join("\n")).toString(1)}
+
+ subtitle Road Map
+
+ paragraph Here are the "todos" present in the source code for ${languageName}:
+
+ list
+ ${this.getTopDownArray()
+ .filter(node => node.getWord(0) === "todo")
+ .map(node => ` - ${node.getLine()}`)
+ .join("\n")}
+
+ paragraph This readme was auto-generated using the
+ link https://github.com/treenotation/jtree JTree library.`
+ }
+ toBundle() {
+ const files = {}
+ const rootNodeDef = this._getRootNodeTypeDefinitionNode()
+ const languageName = this.getExtensionName()
+ const example = rootNodeDef.getExamples()[0]
+ const sampleCode = example ? example.childrenToString() : ""
+ files[GrammarBundleFiles.package] = JSON.stringify(
+ {
+ name: languageName,
+ private: true,
+ dependencies: {
+ jtree: TreeNode.getVersion()
+ }
+ },
+ null,
+ 2
+ )
+ files[GrammarBundleFiles.readme] = this.toReadMe()
+ const testCode = `const program = new ${languageName}(sampleCode)
+ const errors = program.getAllErrors()
+ console.log("Sample program compiled with " + errors.length + " errors.")
+ if (errors.length)
+ console.log(errors.map(error => error.getMessage()))`
+ const nodePath = `${languageName}.node.js`
+ files[nodePath] = this.toNodeJsJavascript()
+ files[GrammarBundleFiles.indexJs] = `module.exports = require("./${nodePath}")`
+ const browserPath = `${languageName}.browser.js`
+ files[browserPath] = this.toBrowserJavascript()
+ files[GrammarBundleFiles.indexHtml] = `
+
+ `
+ const samplePath = "sample." + this.getExtensionName()
+ files[samplePath] = sampleCode.toString()
+ files[GrammarBundleFiles.testJs] = `const ${languageName} = require("./index.js")
+ /*keep-line*/ const sampleCode = require("fs").readFileSync("${samplePath}", "utf8")
+ ${testCode}`
+ return files
+ }
+ getTargetExtension() {
+ return this._getRootNodeTypeDefinitionNode().get(GrammarConstants.compilesTo)
+ }
+ getCellTypeDefinitions() {
+ if (!this._cache_cellTypes) this._cache_cellTypes = this._getCellTypeDefinitions()
+ return this._cache_cellTypes
+ }
+ getCellTypeDefinitionById(cellTypeId) {
+ // todo: return unknownCellTypeDefinition? or is that handled somewhere else?
+ return this.getCellTypeDefinitions()[cellTypeId]
+ }
+ getNodeTypeFamilyTree() {
+ const tree = new TreeNode()
+ Object.values(this.getValidConcreteAndAbstractNodeTypeDefinitions()).forEach(node => {
+ const path = node.getAncestorNodeTypeIdsArray().join(" ")
+ tree.touchNode(path)
+ })
+ return tree
+ }
+ _getCellTypeDefinitions() {
+ const types = {}
+ // todo: add built in word types?
+ this.getChildrenByNodeConstructor(cellTypeDefinitionNode).forEach(type => (types[type.getCellTypeId()] = type))
+ return types
+ }
+ getLanguageDefinitionProgram() {
+ return this
+ }
+ getValidConcreteAndAbstractNodeTypeDefinitions() {
+ return this.getChildrenByNodeConstructor(nodeTypeDefinitionNode).filter(node => node._hasValidNodeTypeId())
+ }
+ _getRootNodeTypeDefinitionNode() {
+ if (!this._cache_rootNodeTypeNode) {
+ this.forEach(def => {
+ if (def instanceof AbstractGrammarDefinitionNode && def.has(GrammarConstants.root) && def._hasValidNodeTypeId()) this._cache_rootNodeTypeNode = def
+ })
+ }
+ // By default, have a very permissive basic root node.
+ // todo: whats the best design pattern to use for this sort of thing?
+ if (!this._cache_rootNodeTypeNode) {
+ this._cache_rootNodeTypeNode = this.concat(`${GrammarConstants.defaultRootNode}
+ ${GrammarConstants.root}
+ ${GrammarConstants.catchAllNodeType} ${GrammarConstants.BlobNode}`)[0]
+ this._addDefaultCatchAllBlobNode()
+ }
+ return this._cache_rootNodeTypeNode
+ }
+ // todo: whats the best design pattern to use for this sort of thing?
+ _addDefaultCatchAllBlobNode() {
+ delete this._cache_nodeTypeDefinitions
+ this.concat(`${GrammarConstants.BlobNode}
+ ${GrammarConstants.baseNodeType} ${GrammarConstants.blobNode}`)
+ }
+ getExtensionName() {
+ return this.getGrammarName().replace(GrammarProgram.nodeTypeSuffixRegex, "")
+ }
+ getGrammarName() {
+ return this._getRootNodeTypeDefinitionNode().getNodeTypeIdFromDefinition()
+ }
+ _getMyInScopeNodeTypeIds() {
+ const nodeTypesNode = this._getRootNodeTypeDefinitionNode().getNode(GrammarConstants.inScope)
+ return nodeTypesNode ? nodeTypesNode.getWordsFrom(1) : []
+ }
+ _getInScopeNodeTypeIds() {
+ const nodeTypesNode = this._getRootNodeTypeDefinitionNode().getNode(GrammarConstants.inScope)
+ return nodeTypesNode ? nodeTypesNode.getWordsFrom(1) : []
+ }
+ _initProgramNodeTypeDefinitionCache() {
+ if (this._cache_nodeTypeDefinitions) return undefined
+ this._cache_nodeTypeDefinitions = {}
+ this.getChildrenByNodeConstructor(nodeTypeDefinitionNode).forEach(nodeTypeDefinitionNode => {
+ this._cache_nodeTypeDefinitions[nodeTypeDefinitionNode.getNodeTypeIdFromDefinition()] = nodeTypeDefinitionNode
+ })
+ }
+ _getProgramNodeTypeDefinitionCache() {
+ this._initProgramNodeTypeDefinitionCache()
+ return this._cache_nodeTypeDefinitions
+ }
+ _getRootConstructor() {
+ const def = this._getRootNodeTypeDefinitionNode()
+ return def._getConstructorDefinedInGrammar()
+ }
+ getRootConstructor() {
+ if (!this._cache_rootConstructorClass) this._cache_rootConstructorClass = this._getRootConstructor()
+ return this._cache_rootConstructorClass
+ }
+ _getFileExtensions() {
+ return this._getRootNodeTypeDefinitionNode().get(GrammarConstants.extensions)
+ ? this._getRootNodeTypeDefinitionNode()
+ .get(GrammarConstants.extensions)
+ .split(" ")
+ .join(",")
+ : this.getExtensionName()
+ }
+ toNodeJsJavascript(jtreePath = "jtree") {
+ return this._rootNodeDefToJavascriptClass(jtreePath, true).trim()
+ }
+ toBrowserJavascript() {
+ return this._rootNodeDefToJavascriptClass("", false).trim()
+ }
+ _getProperName() {
+ return TreeUtils.ucfirst(this.getExtensionName())
+ }
+ _rootNodeDefToJavascriptClass(jtreePath, forNodeJs = true) {
+ const defs = this.getValidConcreteAndAbstractNodeTypeDefinitions()
+ // todo: throw if there is no root node defined
+ const nodeTypeClasses = defs.map(def => def._nodeDefToJavascriptClass()).join("\n\n")
+ const rootName = this._getRootNodeTypeDefinitionNode()._getGeneratedClassName()
+ if (!rootName) throw new Error(`Root Node Type Has No Name`)
+ let exportScript = ""
+ if (forNodeJs) {
+ exportScript = `module.exports = ${rootName};
+ ${rootName}`
+ } else {
+ exportScript = `window.${rootName} = ${rootName}`
+ }
+ // todo: we can expose the previous "constants" export, if needed, via the grammar, which we preserve.
+ return `{
+ "use strict";
+
+ ${forNodeJs ? `const {jtree} = require("${jtreePath}")` : ""}
+
+ ${nodeTypeClasses}
+
+ ${exportScript}
+ }
+ `
+ }
+ toSublimeSyntaxFile() {
+ const cellTypeDefs = this.getCellTypeDefinitions()
+ const variables = Object.keys(cellTypeDefs)
+ .map(name => ` ${name}: '${cellTypeDefs[name].getRegexString()}'`)
+ .join("\n")
+ const defs = this.getValidConcreteAndAbstractNodeTypeDefinitions().filter(kw => !kw._isAbstract())
+ const nodeTypeContexts = defs.map(def => def.getMatchBlock()).join("\n\n")
+ const includes = defs.map(nodeTypeDef => ` - include: '${nodeTypeDef.getNodeTypeIdFromDefinition()}'`).join("\n")
+ return `%YAML 1.2
+ ---
+ name: ${this.getExtensionName()}
+ file_extensions: [${this._getFileExtensions()}]
+ scope: source.${this.getExtensionName()}
+
+ variables:
+ ${variables}
+
+ contexts:
+ main:
+ ${includes}
+
+ ${nodeTypeContexts}`
+ }
+ }
+ GrammarProgram.makeNodeTypeId = str =>
+ TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(GrammarProgram.nodeTypeSuffixRegex, "") + GrammarConstants.nodeTypeSuffix
+ GrammarProgram.makeCellTypeId = str =>
+ TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(str).replace(GrammarProgram.cellTypeSuffixRegex, "") + GrammarConstants.cellTypeSuffix
+ GrammarProgram.nodeTypeSuffixRegex = new RegExp(GrammarConstants.nodeTypeSuffix + "$")
+ GrammarProgram.nodeTypeFullRegex = new RegExp("^[a-zA-Z0-9_]+" + GrammarConstants.nodeTypeSuffix + "$")
+ GrammarProgram.cellTypeSuffixRegex = new RegExp(GrammarConstants.cellTypeSuffix + "$")
+ GrammarProgram.cellTypeFullRegex = new RegExp("^[a-zA-Z0-9_]+" + GrammarConstants.cellTypeSuffix + "$")
+ GrammarProgram._languages = {}
+ GrammarProgram._nodeTypes = {}
+ window.GrammarConstants = GrammarConstants
+ window.PreludeCellTypeIds = PreludeCellTypeIds
+ window.GrammarProgram = GrammarProgram
+ window.GrammarBackedRootNode = GrammarBackedRootNode
+ window.GrammarBackedNonRootNode = GrammarBackedNonRootNode
+ class Upgrader extends TreeNode {
+ upgradeManyInPlace(globPatterns, fromVersion, toVersion) {
+ this._upgradeMany(globPatterns, fromVersion, toVersion).forEach(file => file.tree.toDisk(file.path))
+ return this
+ }
+ upgradeManyPreview(globPatterns, fromVersion, toVersion) {
+ return this._upgradeMany(globPatterns, fromVersion, toVersion)
+ }
+ _upgradeMany(globPatterns, fromVersion, toVersion) {
+ const glob = this.require("glob")
+ const files = TreeUtils.flatten(globPatterns.map(pattern => glob.sync(pattern)))
+ console.log(`${files.length} files to upgrade`)
+ return files.map(path => {
+ console.log("Upgrading " + path)
+ return {
+ tree: this.upgrade(TreeNode.fromDisk(path), fromVersion, toVersion),
+ path: path
+ }
+ })
+ }
+ upgrade(code, fromVersion, toVersion) {
+ const updateFromMap = this.getUpgradeFromMap()
+ const semver = this.require("semver")
+ let fromMap
+ while ((fromMap = updateFromMap[fromVersion])) {
+ const toNextVersion = Object.keys(fromMap)[0] // todo: currently we just assume 1 step at a time
+ if (semver.lt(toVersion, toNextVersion)) break
+ const fn = Object.values(fromMap)[0]
+ code = fn(code)
+ fromVersion = toNextVersion
+ }
+ return code
+ }
+ }
+ window.Upgrader = Upgrader
+ class UnknownGrammarProgram extends TreeNode {
+ _inferRootNodeForAPrefixLanguage(grammarName) {
+ grammarName = GrammarProgram.makeNodeTypeId(grammarName)
+ const rootNode = new TreeNode(`${grammarName}
+ ${GrammarConstants.root}`)
+ // note: right now we assume 1 global cellTypeMap and nodeTypeMap per grammar. But we may have scopes in the future?
+ const rootNodeNames = this.getFirstWords()
+ .filter(word => word)
+ .map(word => GrammarProgram.makeNodeTypeId(word))
+ rootNode
+ .nodeAt(0)
+ .touchNode(GrammarConstants.inScope)
+ .setWordsFrom(1, Array.from(new Set(rootNodeNames)))
+ return rootNode
+ }
+ _renameIntegerKeywords(clone) {
+ // todo: why are we doing this?
+ for (let node of clone.getTopDownArrayIterator()) {
+ const firstWordIsAnInteger = !!node.getFirstWord().match(/^\d+$/)
+ const parentFirstWord = node.getParent().getFirstWord()
+ if (firstWordIsAnInteger && parentFirstWord) node.setFirstWord(GrammarProgram.makeNodeTypeId(parentFirstWord + "Child"))
+ }
+ }
+ _getKeywordMaps(clone) {
+ const keywordsToChildKeywords = {}
+ const keywordsToNodeInstances = {}
+ for (let node of clone.getTopDownArrayIterator()) {
+ const firstWord = node.getFirstWord()
+ if (!keywordsToChildKeywords[firstWord]) keywordsToChildKeywords[firstWord] = {}
+ if (!keywordsToNodeInstances[firstWord]) keywordsToNodeInstances[firstWord] = []
+ keywordsToNodeInstances[firstWord].push(node)
+ node.forEach(child => {
+ keywordsToChildKeywords[firstWord][child.getFirstWord()] = true
+ })
+ }
+ return { keywordsToChildKeywords: keywordsToChildKeywords, keywordsToNodeInstances: keywordsToNodeInstances }
+ }
+ _inferNodeTypeDef(firstWord, globalCellTypeMap, childFirstWords, instances) {
+ const xi = this.getXI()
+ const nodeTypeId = GrammarProgram.makeNodeTypeId(firstWord)
+ const nodeDefNode = new TreeNode(nodeTypeId).nodeAt(0)
+ const childNodeTypeIds = childFirstWords.map(word => GrammarProgram.makeNodeTypeId(word))
+ if (childNodeTypeIds.length) nodeDefNode.touchNode(GrammarConstants.inScope).setWordsFrom(1, childNodeTypeIds)
+ const cellsForAllInstances = instances
+ .map(line => line.getContent())
+ .filter(line => line)
+ .map(line => line.split(xi))
+ const instanceCellCounts = new Set(cellsForAllInstances.map(cells => cells.length))
+ const maxCellsOnLine = Math.max(...Array.from(instanceCellCounts))
+ const minCellsOnLine = Math.min(...Array.from(instanceCellCounts))
+ let catchAllCellType
+ let cellTypeIds = []
+ for (let cellIndex = 0; cellIndex < maxCellsOnLine; cellIndex++) {
+ const cellType = this._getBestCellType(firstWord, instances.length, maxCellsOnLine, cellsForAllInstances.map(cells => cells[cellIndex]))
+ if (!globalCellTypeMap.has(cellType.cellTypeId)) globalCellTypeMap.set(cellType.cellTypeId, cellType.cellTypeDefinition)
+ cellTypeIds.push(cellType.cellTypeId)
+ }
+ if (maxCellsOnLine > minCellsOnLine) {
+ //columns = columns.slice(0, min)
+ catchAllCellType = cellTypeIds.pop()
+ while (cellTypeIds[cellTypeIds.length - 1] === catchAllCellType) {
+ cellTypeIds.pop()
+ }
+ }
+ const needsMatchProperty = TreeUtils._replaceNonAlphaNumericCharactersWithCharCodes(firstWord) !== firstWord
+ if (needsMatchProperty) nodeDefNode.set(GrammarConstants.match, firstWord)
+ if (catchAllCellType) nodeDefNode.set(GrammarConstants.catchAllCellType, catchAllCellType)
+ const cellLine = cellTypeIds.slice()
+ cellLine.unshift(PreludeCellTypeIds.keywordCell)
+ if (cellLine.length > 0) nodeDefNode.set(GrammarConstants.cells, cellLine.join(xi))
+ //if (!catchAllCellType && cellTypeIds.length === 1) nodeDefNode.set(GrammarConstants.cells, cellTypeIds[0])
+ // Todo: add conditional frequencies
+ return nodeDefNode.getParent().toString()
+ }
+ // inferGrammarFileForAnSSVLanguage(grammarName: string): string {
+ // grammarName = GrammarProgram.makeNodeTypeId(grammarName)
+ // const rootNode = new TreeNode(`${grammarName}
+ // ${GrammarConstants.root}`)
+ // // note: right now we assume 1 global cellTypeMap and nodeTypeMap per grammar. But we may have scopes in the future?
+ // const rootNodeNames = this.getFirstWords().map(word => GrammarProgram.makeNodeTypeId(word))
+ // rootNode
+ // .nodeAt(0)
+ // .touchNode(GrammarConstants.inScope)
+ // .setWordsFrom(1, Array.from(new Set(rootNodeNames)))
+ // return rootNode
+ // }
+ inferGrammarFileForAKeywordLanguage(grammarName) {
+ const clone = this.clone()
+ this._renameIntegerKeywords(clone)
+ const { keywordsToChildKeywords, keywordsToNodeInstances } = this._getKeywordMaps(clone)
+ const globalCellTypeMap = new Map()
+ globalCellTypeMap.set(PreludeCellTypeIds.keywordCell, undefined)
+ const nodeTypeDefs = Object.keys(keywordsToChildKeywords)
+ .filter(word => word)
+ .map(firstWord =>
+ this._inferNodeTypeDef(firstWord, globalCellTypeMap, Object.keys(keywordsToChildKeywords[firstWord]), keywordsToNodeInstances[firstWord])
+ )
+ const cellTypeDefs = []
+ globalCellTypeMap.forEach((def, id) => cellTypeDefs.push(def ? def : id))
+ const yi = this.getYI()
+ return [this._inferRootNodeForAPrefixLanguage(grammarName).toString(), cellTypeDefs.join(yi), nodeTypeDefs.join(yi)].filter(def => def).join("\n")
+ }
+ _getBestCellType(firstWord, instanceCount, maxCellsOnLine, allValues) {
+ const asSet = new Set(allValues)
+ const xi = this.getXI()
+ const values = Array.from(asSet).filter(c => c)
+ const every = fn => {
+ for (let index = 0; index < values.length; index++) {
+ if (!fn(values[index])) return false
+ }
+ return true
+ }
+ if (every(str => str === "0" || str === "1")) return { cellTypeId: PreludeCellTypeIds.bitCell }
+ if (
+ every(str => {
+ const num = parseInt(str)
+ if (isNaN(num)) return false
+ return num.toString() === str
+ })
+ ) {
+ return { cellTypeId: PreludeCellTypeIds.intCell }
+ }
+ if (every(str => str.match(/^-?\d*.?\d+$/))) return { cellTypeId: PreludeCellTypeIds.floatCell }
+ const bools = new Set(["1", "0", "true", "false", "t", "f", "yes", "no"])
+ if (every(str => bools.has(str.toLowerCase()))) return { cellTypeId: PreludeCellTypeIds.boolCell }
+ // todo: cleanup
+ const enumLimit = 30
+ if (instanceCount > 1 && maxCellsOnLine === 1 && allValues.length > asSet.size && asSet.size < enumLimit)
+ return {
+ cellTypeId: GrammarProgram.makeCellTypeId(firstWord),
+ cellTypeDefinition: `${GrammarProgram.makeCellTypeId(firstWord)}
+ enum ${values.join(xi)}`
+ }
+ return { cellTypeId: PreludeCellTypeIds.anyCell }
+ }
+ }
+ window.UnknownGrammarProgram = UnknownGrammarProgram
+ // Adapted from https://github.com/NeekSandhu/codemirror-textmate/blob/master/src/tmToCm.ts
+ var CmToken
+ ;(function(CmToken) {
+ CmToken["Atom"] = "atom"
+ CmToken["Attribute"] = "attribute"
+ CmToken["Bracket"] = "bracket"
+ CmToken["Builtin"] = "builtin"
+ CmToken["Comment"] = "comment"
+ CmToken["Def"] = "def"
+ CmToken["Error"] = "error"
+ CmToken["Header"] = "header"
+ CmToken["HR"] = "hr"
+ CmToken["Keyword"] = "keyword"
+ CmToken["Link"] = "link"
+ CmToken["Meta"] = "meta"
+ CmToken["Number"] = "number"
+ CmToken["Operator"] = "operator"
+ CmToken["Property"] = "property"
+ CmToken["Qualifier"] = "qualifier"
+ CmToken["Quote"] = "quote"
+ CmToken["String"] = "string"
+ CmToken["String2"] = "string-2"
+ CmToken["Tag"] = "tag"
+ CmToken["Type"] = "type"
+ CmToken["Variable"] = "variable"
+ CmToken["Variable2"] = "variable-2"
+ CmToken["Variable3"] = "variable-3"
+ })(CmToken || (CmToken = {}))
+ const tmToCm = {
+ comment: {
+ $: CmToken.Comment
+ },
+ constant: {
+ // TODO: Revision
+ $: CmToken.Def,
+ character: {
+ escape: {
+ $: CmToken.String2
+ }
+ },
+ language: {
+ $: CmToken.Atom
+ },
+ numeric: {
+ $: CmToken.Number
+ },
+ other: {
+ email: {
+ link: {
+ $: CmToken.Link
+ }
+ },
+ symbol: {
+ // TODO: Revision
+ $: CmToken.Def
+ }
+ }
+ },
+ entity: {
+ name: {
+ class: {
+ $: CmToken.Def
+ },
+ function: {
+ $: CmToken.Def
+ },
+ tag: {
+ $: CmToken.Tag
+ },
+ type: {
+ $: CmToken.Type,
+ class: {
+ $: CmToken.Variable
+ }
+ }
+ },
+ other: {
+ "attribute-name": {
+ $: CmToken.Attribute
+ },
+ "inherited-class": {
+ // TODO: Revision
+ $: CmToken.Def
+ }
+ },
+ support: {
+ function: {
+ // TODO: Revision
+ $: CmToken.Def
+ }
+ }
+ },
+ invalid: {
+ $: CmToken.Error,
+ illegal: { $: CmToken.Error },
+ deprecated: {
+ $: CmToken.Error
+ }
+ },
+ keyword: {
+ $: CmToken.Keyword,
+ operator: {
+ $: CmToken.Operator
+ },
+ other: {
+ "special-method": CmToken.Def
+ }
+ },
+ punctuation: {
+ $: CmToken.Operator,
+ definition: {
+ comment: {
+ $: CmToken.Comment
+ },
+ tag: {
+ $: CmToken.Bracket
+ }
+ // 'template-expression': {
+ // $: CodeMirrorToken.Operator,
+ // },
+ }
+ // terminator: {
+ // $: CodeMirrorToken.Operator,
+ // },
+ },
+ storage: {
+ $: CmToken.Keyword
+ },
+ string: {
+ $: CmToken.String,
+ regexp: {
+ $: CmToken.String2
+ }
+ },
+ support: {
+ class: {
+ $: CmToken.Def
+ },
+ constant: {
+ $: CmToken.Variable2
+ },
+ function: {
+ $: CmToken.Def
+ },
+ type: {
+ $: CmToken.Type
+ },
+ variable: {
+ $: CmToken.Variable2,
+ property: {
+ $: CmToken.Property
+ }
+ }
+ },
+ variable: {
+ $: CmToken.Def,
+ language: {
+ // TODO: Revision
+ $: CmToken.Variable3
+ },
+ other: {
+ object: {
+ $: CmToken.Variable,
+ property: {
+ $: CmToken.Property
+ }
+ },
+ property: {
+ $: CmToken.Property
+ }
+ },
+ parameter: {
+ $: CmToken.Def
+ }
+ }
+ }
+ const textMateScopeToCodeMirrorStyle = (scopeSegments, styleTree = tmToCm) => {
+ const matchingBranch = styleTree[scopeSegments.shift()]
+ return matchingBranch ? textMateScopeToCodeMirrorStyle(scopeSegments, matchingBranch) || matchingBranch.$ || null : null
+ }
+ class TreeNotationCodeMirrorMode {
+ constructor(name, getProgramConstructorMethod, getProgramCodeMethod, codeMirrorLib = undefined) {
+ this._name = name
+ this._getProgramConstructorMethod = getProgramConstructorMethod
+ this._getProgramCodeMethod = getProgramCodeMethod || (instance => (instance ? instance.getValue() : this._originalValue))
+ this._codeMirrorLib = codeMirrorLib
+ }
+ _getParsedProgram() {
+ const source = this._getProgramCodeMethod(this._cmInstance) || ""
+ if (!this._cachedProgram || this._cachedSource !== source) {
+ this._cachedSource = source
+ this._cachedProgram = new (this._getProgramConstructorMethod())(source)
+ }
+ return this._cachedProgram
+ }
+ _getExcludedIntelliSenseTriggerKeys() {
+ return {
+ "8": "backspace",
+ "9": "tab",
+ "13": "enter",
+ "16": "shift",
+ "17": "ctrl",
+ "18": "alt",
+ "19": "pause",
+ "20": "capslock",
+ "27": "escape",
+ "33": "pageup",
+ "34": "pagedown",
+ "35": "end",
+ "36": "home",
+ "37": "left",
+ "38": "up",
+ "39": "right",
+ "40": "down",
+ "45": "insert",
+ "46": "delete",
+ "91": "left window key",
+ "92": "right window key",
+ "93": "select",
+ "112": "f1",
+ "113": "f2",
+ "114": "f3",
+ "115": "f4",
+ "116": "f5",
+ "117": "f6",
+ "118": "f7",
+ "119": "f8",
+ "120": "f9",
+ "121": "f10",
+ "122": "f11",
+ "123": "f12",
+ "144": "numlock",
+ "145": "scrolllock"
+ }
+ }
+ token(stream, state) {
+ return this._advanceStreamAndReturnTokenType(stream, state)
+ }
+ fromTextAreaWithAutocomplete(area, options) {
+ this._originalValue = area.value
+ const defaultOptions = {
+ lineNumbers: true,
+ mode: this._name,
+ tabSize: 1,
+ indentUnit: 1,
+ hintOptions: {
+ hint: (cmInstance, options) => this.codeMirrorAutocomplete(cmInstance, options)
+ }
+ }
+ Object.assign(defaultOptions, options)
+ this._cmInstance = this._getCodeMirrorLib().fromTextArea(area, defaultOptions)
+ this._enableAutoComplete(this._cmInstance)
+ return this._cmInstance
+ }
+ _enableAutoComplete(cmInstance) {
+ const excludedKeys = this._getExcludedIntelliSenseTriggerKeys()
+ const codeMirrorLib = this._getCodeMirrorLib()
+ cmInstance.on("keyup", (cm, event) => {
+ // https://stackoverflow.com/questions/13744176/codemirror-autocomplete-after-any-keyup
+ if (!cm.state.completionActive && !excludedKeys[event.keyCode.toString()])
+ // Todo: get typings for CM autocomplete
+ codeMirrorLib.commands.autocomplete(cm, null, { completeSingle: false })
+ })
+ }
+ _getCodeMirrorLib() {
+ return this._codeMirrorLib
+ }
+ async codeMirrorAutocomplete(cmInstance, options) {
+ const cursor = cmInstance.getDoc().getCursor()
+ const codeMirrorLib = this._getCodeMirrorLib()
+ const result = await this._getParsedProgram().getAutocompleteResultsAt(cursor.line, cursor.ch)
+ // It seems to be better UX if there's only 1 result, and its the word the user entered, to close autocomplete
+ if (result.matches.length === 1 && result.matches[0].text === result.word) return null
+ return result.matches.length
+ ? {
+ list: result.matches,
+ from: codeMirrorLib.Pos(cursor.line, result.startCharIndex),
+ to: codeMirrorLib.Pos(cursor.line, result.endCharIndex)
+ }
+ : null
+ }
+ register() {
+ const codeMirrorLib = this._getCodeMirrorLib()
+ codeMirrorLib.defineMode(this._name, () => this)
+ codeMirrorLib.defineMIME("text/" + this._name, this._name)
+ return this
+ }
+ _advanceStreamAndReturnTokenType(stream, state) {
+ let nextCharacter = stream.next()
+ const lineNumber = stream.lineOracle.line + 1 // state.lineIndex
+ while (typeof nextCharacter === "string") {
+ const peek = stream.peek()
+ if (nextCharacter === " ") {
+ if (peek === undefined || peek === "\n") {
+ stream.skipToEnd() // advance string to end
+ this._incrementLine(state)
+ }
+ return "bracket"
+ }
+ if (peek === " ") {
+ state.cellIndex++
+ return this._getCellStyle(lineNumber, state.cellIndex)
+ }
+ nextCharacter = stream.next()
+ }
+ state.cellIndex++
+ const style = this._getCellStyle(lineNumber, state.cellIndex)
+ this._incrementLine(state)
+ return style
+ }
+ _getCellStyle(lineIndex, cellIndex) {
+ const program = this._getParsedProgram()
+ // todo: if the current word is an error, don't show red?
+ if (!program.getCellHighlightScopeAtPosition) console.log(program)
+ const highlightScope = program.getCellHighlightScopeAtPosition(lineIndex, cellIndex)
+ const style = highlightScope ? textMateScopeToCodeMirrorStyle(highlightScope.split(".")) : undefined
+ return style || "noHighlightScopeDefinedInGrammar"
+ }
+ // todo: remove.
+ startState() {
+ return {
+ cellIndex: 0
+ }
+ }
+ _incrementLine(state) {
+ state.cellIndex = 0
+ }
+ }
+ window.TreeNotationCodeMirrorMode = TreeNotationCodeMirrorMode
+ class jtree {}
+ jtree.GrammarBackedRootNode = GrammarBackedRootNode
+ jtree.GrammarBackedNonRootNode = GrammarBackedNonRootNode
+ jtree.Utils = TreeUtils
+ jtree.TreeNode = TreeNode
+ jtree.ExtendibleTreeNode = ExtendibleTreeNode
+ jtree.GrammarProgram = GrammarProgram
+ jtree.UnknownGrammarProgram = UnknownGrammarProgram
+ jtree.TreeNotationCodeMirrorMode = TreeNotationCodeMirrorMode
+ jtree.getVersion = () => TreeNode.getVersion()
+ window.jtree = jtree
Breck Yunits
Breck Yunits
5 years ago
Added github link
github-fork.svg
Changed around line 1
+
+
+
+
+
+
index.html
Changed around line 57
+
Breck Yunits
Breck Yunits
5 years ago
fixes from rlabrecque
index.html
Changed around line 43
- Go
- C++
+ C++
+ Go*
+

* Implementations wanted!

Breck Yunits
Breck Yunits
5 years ago
More demos
demos/aFutureWithoutCompilers.svg
Changed around line 1
+
+
+ Untitled 12
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/cleanData.svg
Changed around line 1
+
+
+ Untitled 10
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/complexityCanBeCounted.svg
Changed around line 1
+
+
+ Untitled 13
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
index.html
Changed around line 6
- if (!event.target.href)
- return true
+ if (!event.target.href) return true
Changed around line 17
- What is Tree Notation?
+ Why Tree Notation?
Breck Yunits
Breck Yunits
5 years ago
More demos on homepage
demos/helloWorld3D.svg
Changed around line 1
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/noSyntaxChars.svg
Changed around line 1
+
+
+ Untitled 8
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/paper.svg
Changed around line 1
+
+
+ Untitled 4
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/programWithPictures.svg
Changed around line 1
+
+
+ Untitled 7
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
demos/programsAreAlsoSpreadsheets.svg
Changed around line 1
+
+
+ Untitled 5
+ Drawing exported from Concepts: Smarter Sketching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
index.css
Changed around line 25: a {
+ #heroImage {
+ height: 400px;
+ }
index.html
Changed around line 4
+
-
+
+

+ What is Tree Notation?
+

Changed around line 51
+
Breck Yunits
Breck Yunits
5 years ago
style cleanup
favicon.ico
index.css
Changed around line 13: body {
- font-weight: 100;
+ font-weight: 300;
+ }
+ body h1 {
+ font-weight: 300;
+ text-decoration: none;
+ }
+ a:hover {
+ color: rgba(14, 72, 78, 1);
- background: #c1c1c1;
+ background: #eee;
Breck Yunits
Breck Yunits
5 years ago
Added libraries link
index.css
Changed around line 18: body {
+ .libraries a {
+ display: block;
+ }
index.html
Changed around line 22
+

Libraries

Breck Yunits
Breck Yunits
5 years ago
jtree links
Breck Yunits
Breck Yunits
5 years ago
Create CNAME
CNAME
Changed around line 1
+ treenotation.org
Breck Yunits
Breck Yunits
5 years ago
Started new website
README.md
Changed around line 1
- # treenotation.org
- The future of communication
-
- New website coming soon.
+ This repo contains the source code for https://treenotation.org
demos/helloWorld3D.svg
Changed around line 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
github.png
index.css
Changed around line 1
+ html {
+ box-sizing: border-box;
+ }
+ body {
+ margin: auto;
+ text-align: center;
+ width: 600px;
+ }
+ *,
+ *:before,
+ *:after {
+ box-sizing: inherit;
+ }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
+ font-weight: 100;
+ }
+ a {
+ color: rgba(65, 124, 129, 1);
+ }
+ body {
+ background: #c1c1c1;
+ color: rgba(1, 47, 52, 1);
+ }
+ .primaryColorLightest {
+ color: rgba(104, 151, 155, 1);
+ }
+ .primaryColorLight {
+ color: rgba(65, 124, 129, 1);
+ }
+ .primaryColorMid {
+ color: rgba(35, 98, 103, 1);
+ }
+ .primaryColorDark {
+ color: rgba(14, 72, 78, 1);
+ }
+ .primaryColorDarkest {
+ color: rgba(1, 47, 52, 1);
+ }
+ .section {
+ padding: 20px;
+ }
index.html
Changed around line 1
+
+
+
+
+
+ Tree Notation
+
+
+

Tree Notation

+
+

+

+

+ FAQ
+

+

+
+
+

+

+
+

+
+
reddit.png
treeNotationLab.png
Breck Yunits
Breck Yunits
5 years ago
Update README.md
README.md
Changed around line 1
+
+ New website coming soon.
Breck Yunits
Breck Yunits
5 years ago
Initial commit
README.md
Changed around line 1
+ # treenotation.org
+ The future of communication