Skip to content

Instantly share code, notes, and snippets.

@kenwebb
Last active December 16, 2024 17:49
Show Gist options
  • Save kenwebb/64973dc6f7188262c3072570d48f2ea5 to your computer and use it in GitHub Desktop.
Save kenwebb/64973dc6f7188262c3072570d48f2ea5 to your computer and use it in GitHub Desktop.
JavaScript - Abstract Syntax Tree (AST) 2
<?xml version="1.0" encoding="UTF-8"?>
<!--Xholon Workbook http://www.primordion.com/Xholon/gwt/ MIT License, Copyright (C) Ken Webb, Mon Dec 16 2024 12:49:09 GMT-0500 (Eastern Standard Time)-->
<XholonWorkbook>
<Notes><![CDATA[
Xholon
------
Title: JavaScript - Abstract Syntax Tree (AST) 2
Description:
Url: http://www.primordion.com/Xholon/gwt/
InternalName: 64973dc6f7188262c3072570d48f2ea5 based on 59bf08805972a394fe1e6311bb29f68a
Keywords:
My Notes
--------
10 Dec 2024
http://127.0.0.1:8080/Xholon.html?app=JavaScript+-+Abstract+Syntax+Tree+(AST)&src=lstr&gui=clsc&jslib=acorn
Note: the URL must have "&jslib=acorn" at the end
Learn how to parse JavaScript code using an AST tool such as a parser.
I want to use this capability in my "Industrial Dynamics - Chapter 15 - Xholonish - SubtreeGenerator" workbook.
16 Dec 2024
This is version 2; same model but lots of details changed.
Instead of using me.UOR, this workbook uses just UOR.
### acorn
see ~/nodespace/acorn/READMEksw.txt for details on what I did to install and use acorn
it works in Dev Tools
### References
(1) search: JavaScript Abstract Syntax Tree
(2) https://astexplorer.net/
Type JavaScript into left editor, and see the AST in the right pane.
The following code worked:
UOR.K=UOR.J+(DT)*(RRR.JK-SSR.JK) // L
IAR.K=IAR.J+(DT)*(SRR.JK-SSR.JK) // L
SSR.KL=UOR.K/DUR // R
RSR.K=RSR.J+(DT)*(1/DRR)*(RRR.JK-RSR.J) // L
IDR.K=(AIR)*(RSR.K) // A
ISR.K=IDR.K-IAR.K // A
PDR.KL=(ISR.K/DIR)+RSR.K // R
UOD.K=UOD.J+(DT)*(PDR.JK-SRR.JK) // L
SRR.KL=UOD.K/DUD // R
INSTP.K=STEP(20,1) // timing
RRR.KL=100.0+INSTP.K // R
- each line is an Expression Statement with its subtree
- it uses Parser: acorn-8.7.0
(3) https://www.digitalocean.com/community/tutorials/js-traversing-ast
Read JavaScript Source Code, Using an AST
Published on February 9, 2019
small example
Open Source ECMAScript parsers
they use the acorn parser
I should be able to use this page to help me get started.
(4) https://dev.to/marvinjude/abstract-syntax-trees-and-practical-applications-in-javascript-4a3
Abstract Syntax Trees and Practical Applications in JavaScript
Jude Agboola
Posted on Oct 21, 2023
- it uses astexplorer as a learning tool
- he uses the swc parser, rather than acorn
- the examples he works through are not that relevant to my needs
(5) https://swc.rs/
SWC
Rust-based platform for the Web
SWC is an extensible Rust-based platform for the next generation of fast developer tools.
It's used by tools like Next.js, Parcel, and Deno, as well as companies like Vercel, ByteDance, Tencent, Shopify, Trip.com, and more.
SWC can be used for both compilation and bundling.
For compilation, it takes JavaScript / TypeScript files using modern JavaScript features and outputs valid code that is supported by all major browsers.
) https://swc.rs/docs/usage/core#parse
(6) https://github.com/acornjs/acorn
A small, fast, JavaScript-based JavaScript parser
) https://github.com/acornjs/acorn/tree/master/acorn-walk/
]]></Notes>
<_-.XholonClass>
<PhysicalSystem/>
<AstParser/>
<!-- System Dynamics node types: Level Rate Auxiliary Constant -->
<SdNode superClass="Script">
<SdLevel/>
<SdRate/>
<SdAux/>
<SdConst/>
</SdNode>
</_-.XholonClass>
<xholonClassDetails>
<Avatar><Color>black</Color></Avatar>
<PhysicalSystem><Color>white</Color></PhysicalSystem>
<SdLevel><Color>yellowgreen</Color></SdLevel>
<SdRate><Color>orangered</Color></SdRate>
<SdAux><Color>purple</Color></SdAux>
<SdConst><Color>gold</Color></SdConst>
<SdLevel><DefaultContent><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode.parent();
me.println(me.name());
//me.J = Number(me.J);
//me.K = Number(me.K);
//TESTING += me.role();
//JKL.J += 11;
//console.log(JKL);
},
act: function() {
// do the DYNAMO function
me.println(me.role() + "." + me.prop + ": " + me.K);
switch (me.role()) {
case "UOR":
// UOR.K=UOR.J+(DT)*(RRR.JK-SSR.JK) // L
UOR.K=UOR.J+(DT.vall)*(RRR.JK-SSR.JK);
break;
case "IAR":
// IAR.K=IAR.J+(DT)*(SRR.JK-SSR.JK) // L
IAR.K=IAR.J+(DT.vall)*(SRR.JK-SSR.JK);
break;
case "RSR":
// RSR.K=RSR.J+(DT)*(1/DRR)*(RRR.JK-RSR.J) // L
RSR.K=RSR.J+(DT.vall)*(1/DRR.vall)*(RRR.JK-RSR.J)
break;
case "UOD":
// UOD.K=UOD.J+(DT)*(PDR.JK-SRR.JK) // L
UOD.K=UOD.J+(DT.vall)*(PDR.JK-SRR.JK)
break;
default: break;
}
me.println("UOR .J .K " + UOR.J + " " + UOR.K); // YES
},
preAct: function() {
//me.J = me.K;
switch (me.role()) {
case "UOR": UOR.J = UOR.K; break;
case "IAR": IAR.J = IAR.K; break;
case "RSR": RSR.J = RSR.K; break;
case "UOD": UOD.J = UOD.K; break;
default: break;
}
}
}
//# sourceURL=SdLevel.js
]]></DefaultContent></SdLevel>
<SdRate><DefaultContent><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode.parent();
me.println(me.name());
//me.JK = Number(me.JK);
//me.KL = Number(me.KL);
},
act: function() {
// do the DYNAMO function
me.println(me.role() + "." + me.prop + ": " + me.KL);
switch (me.role()) {
case "SSR":
// SSR.KL=UOR.K/DUR // R
SSR.KL=UOR.K/DUR.vall
break;
case "PDR":
// PDR.KL=(ISR.K/DIR)+RSR.K // R
PDR.KL=(ISR.K/DIR.vall)+RSR.K
break;
case "SRR":
// SRR.KL=UOD.K/DUD // R
SRR.KL=UOD.K/DUD.vall
break;
default: break;
}
},
preAct: function() {
//me.JK = me.KL;
switch (me.role()) {
case "SSR": SSR.JK = SSR.KL; break;
case "PDR": PDR.JK = PDR.KL; break;
case "SRR": SRR.JK = SRR.KL; break;
default: break;
}
}
}
//# sourceURL=SdRate.js
]]></DefaultContent></SdRate>
<SdAux><DefaultContent><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode.parent();
me.println(me.name());
//me.J = Number(me.J);
//me.K = Number(me.K);
},
act: function() {
// do the DYNAMO function
me.println(me.role() + "." + me.prop + ": " + me.K);
switch (me.role()) {
case "IDR":
// IDR.K=(AIR)*(RSR.K) // A
IDR.K=AIR.vall*RSR.K
break;
case "ISR":
// ISR.K=IDR.K-IAR.K // A
ISR.K=IDR.K-IAR.K
break;
case "INSTP":
// INSTP.K=STEP(20,1) // timing
INSTP.K = 1
break;
default: break;
}
},
preAct: function() {
//me.J = me.K;
}
}
//# sourceURL=SdAux.js
]]></DefaultContent></SdAux>
<SdConst><DefaultContent><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode.parent();
me.println(me.name());
//me.J = Number(me.J);
//me.K = Number(me.K);
}
/*act: function() {
// do the DYNAMO function
me.println(me.role() + "." + me.prop + ": " + me.K);
switch (me.role()) {
case "IDR":
// IDR.K=(AIR)*(RSR.K) // A
IDR.K=AIR.vall*RSR.K
break;
case "ISR":
// ISR.K=IDR.K-IAR.K // A
ISR.K=IDR.K-IAR.K
break;
case "INSTP":
// INSTP.K=STEP(20,1) // timing
INSTP.K = 1
break;
default: break;
}
},*/
/*preAct: function() {
me.J = me.K;
}*/
}
//# sourceURL=SdConstbehavior.js
]]></DefaultContent></SdConst>
</xholonClassDetails>
<PhysicalSystem>
<!-- DYNAMO constants
AIR=3.0
DIR=2.0
DRR=2.0
DT =0.5
DUD=2.0
DUR=1.0
-->
<!--<SdConst roleName="AIR" vall="3.0"/>
<SdConst roleName="DIR" vall="2.0"/>
<SdConst roleName="DRR" vall="2.0"/>
<SdConst roleName="DT" vall="0.5"/>
<SdConst roleName="DUD" vall="2.0"/>
<SdConst roleName="DUR" vall="1.0"/>-->
<AstParser>
<Attribute_String>
AIR.vall=1.5 // constant
DIR.vall=1.5 // constant
DRR.vall=1.5 // constant
DT.vall=1.5 // constant
DUD.vall=1.5 // constant
DUR.vall=1.5 // constant
UOR.K=UOR.J+(DT)*(RRR.JK-SSR.JK) // L
IAR.K=IAR.J+(DT)*(SRR.JK-SSR.JK) // L
SSR.KL=UOR.K/DUR // R
RSR.K=RSR.J+(DT)*(1/DRR)*(RRR.JK-RSR.J) // L
IDR.K=(AIR)*(RSR.K) // A
ISR.K=IDR.K-IAR.K // A
PDR.KL=(ISR.K/DIR)+RSR.K // R
UOD.K=UOD.J+(DT)*(PDR.JK-SRR.JK) // L
SRR.KL=UOD.K/DUD // R
INSTP.K=STEP(20,1) // timing
RRR.KL=100.0+INSTP.K // R
</Attribute_String>
</AstParser>
</PhysicalSystem>
<AstParserbehavior implName="org.primordion.xholon.base.Behavior_gwtjs"><![CDATA[
// 16 Dec 2024 Note: I had written 6 lines of code before the above line; it contained less-than and greater-than symbols; failed to load all the behavior nodes
// and, after fixing that line, all 6 lines were lost when I loaded the workbook
// LESSON - DO NOT include any text before the opening XML tag
//TESTING = "AstParser"; // this is available in all my Xholon behavior nodes
//NODES = this;
//JKL = {J:101, K:102}; // yes - JKL is available in all my Xholon behavior nodes
// I need to manually (for now) add the following:
// levels
UOR = {J:0, K:0};
IAR = {J:0, K:0};
RSR = {J:0, K:0};
UOD = {J:0, K:0};
// rates
SSR = {JK:0, KL:0};
PDR = {JK:0, KL:0};
SRR = {JK:0, KL:0};
// aux
IDR = {J:0, K:0};
ISR = {J:0, K:0};
INSTP = {J:0, K:0};
// constants
AIR = {vall: 3.0};
DIR = {vall: 3.0};
DRR = {vall: 3.0};
DT = {vall: 3.0};
DUD = {vall: 3.0};
DUR = {vall: 3.0};
var me, code, beh = {
postConfigure: function() {
me = this.cnode.parent();
me.println(me.name());
code = me.first().text();
me.println(code);
//console.log(JKL); // NODES, ABC, $wnd.ABC, DEF, GHI
const ast = $wnd.acorn.Parser.parse(code, {ecmaVersion: 2020});
const leftobjnamearr = [];
const leftpropnamearr = [];
const allportsarr = []; // an array where each item is itself an array
console.log(ast);
const body = ast.body; // an array
for (var i = 0; i < body.length; i++) {
const item = body[i];
const portsarr = [];
const extype = item.expression.type;
const exoperator = item.expression.operator;
const exleft = item.expression.left;
const exright = item.expression.right;
const leftobjname = item.expression.left.object.name;
const leftpropname = item.expression.left.property.name;
const rightoperator = item.expression.right.operator;
// add object names to portsarr
this.collectPorts(exright, portsarr);
leftobjnamearr.push(leftobjname);
leftpropnamearr.push(leftpropname);
me.println("type:" + extype + " operator:" + exoperator + " obj.prop:" + leftobjname + "." + leftpropname + " rightop:" + rightoperator);
allportsarr.push(portsarr);
}
me.println(leftobjnamearr.join(","));
const nodetype = (nodename, prop, myportsarr) => {
//const myportsarr = myportsstr.split(",");
let ntype = "SdNode";
switch (prop) {
case "K": ntype = myportsarr[0] == nodename ? "SdLevel" : "SdAux"; break;
case "KL": ntype = "SdRate"; break;
default: ntype = "SdConst"; break;
}
return ntype;
}
// <Script>initializerscriptstr</Script/>
// $wnd.xh.root().append("<Script>UOR2 = {J:1, K:2}; IAR2 = {J:3.0, K:4.0}; SSR2 = {KL:5.0}; IDR2 = {K:6.0}; console.log(UOR2, IAR2, SSR2, IDR2)</Script>"); // TEST this works!
// TODO use J K or JK KL depending on leftpropnamearr[index]
const initializerscriptstr =
leftobjnamearr.map((item, index) => item + "={" + (leftpropnamearr[index] == "K" ? "J" : "JK") + ":11," + leftpropnamearr[index] + ":12" + "}", "").join("\n");
console.log(initializerscriptstr);
const xstr = "<Script>" + initializerscriptstr + "</Script>";
console.log(xstr);
$wnd.xh.root().append(xstr);
const nodes = "<_-.nodes>\n"
+ leftobjnamearr.map((item, index) => '<' + nodetype(item, leftpropnamearr[index], allportsarr[index]) + ' roleName="' + item
+ '" prop="' + leftpropnamearr[index]
+ '" ports="' + allportsarr[index]
+ '"/>', "").join("\n")
+ "\n</_-.nodes>";
me.println(nodes);
me.parent().append(nodes);
let newnode = me.next();
while (newnode) {
let portsarr = newnode.ports.split(",");
me.println(portsarr.length);
//if (portsarr[0]) {
for (var i = 0; i < portsarr.length; i++) {
const xpathexpr = "../SdNode[@roleName='" + portsarr[i] + "']";
me.println(xpathexpr);
const remoteNode = newnode.xpath(xpathexpr);
newnode[portsarr[i]] = remoteNode;
}
newnode[newnode.prop] = 0; // J K JK KL
switch (newnode.prop) {
case "K": newnode["J"] = 0; break;
case "KL": newnode["JK"] = 0; break;
default: break;
}
newnode = newnode.next();
}
//$wnd.xh.root().append("<Script>UOR2 = {J:1, K:2}; IAR2 = {J:3.0, K:4.0}; SSR2 = {KL:5.0}; IDR2 = {K:6.0}; console.log(UOR2, IAR2, SSR2, IDR2)</Script>"); // TEST this works!
},
act: function() {
//me.println(me.name());
//me.println(TESTING);
},
// anode an AST node item.expression.right
// ports arr
collectPorts: function(node, parr) {
if (node.type == "Identifier") {
parr.push(node.name);
}
else if (node.object?.type == "Identifier") {
parr.push(node.object.name);
}
if (node.left) {
this.collectPorts(node.left, parr);
}
if (node.right) {
this.collectPorts(node.right, parr)
}
}
}
//# sourceURL=AstParserbehavior.js
]]></AstParserbehavior>
<SvgClient><Attribute_String roleName="svgUri"><![CDATA[data:image/svg+xml,
<svg width="100" height="50" xmlns="http://www.w3.org/2000/svg">
<g>
<title>AstParser</title>
<rect id="PhysicalSystem/AstParser" fill="#98FB98" height="50" width="50" x="25" y="0"/>
<g>
<title>UOR</title>
<rect id="PhysicalSystem/XhNode[@roleName='UOR']" fill="#6AB06A" height="50" width="10" x="80" y="0"/>
</g>
</g>
</svg>
]]></Attribute_String><Attribute_String roleName="setup">${MODELNAME_DEFAULT},${SVGURI_DEFAULT}</Attribute_String></SvgClient>
</XholonWorkbook>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment