diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..a2bbb7f75
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/.vscode/
+
diff --git a/code.pyret.org/package-lock.json b/code.pyret.org/package-lock.json
index 7c73e2f13..86f0adb98 100644
--- a/code.pyret.org/package-lock.json
+++ b/code.pyret.org/package-lock.json
@@ -123,6 +123,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
@@ -7835,6 +7836,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~6.20.0"
}
@@ -8121,6 +8123,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -8174,6 +8177,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -9701,6 +9705,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -11769,6 +11774,7 @@
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
@@ -12745,7 +12751,6 @@
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
"integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
@@ -12766,7 +12771,6 @@
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
@@ -12781,7 +12785,6 @@
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
@@ -15124,7 +15127,8 @@
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/js-md5": {
"version": "0.4.2",
@@ -17286,6 +17290,7 @@
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"license": "MIT",
+ "peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
@@ -17724,6 +17729,21 @@
"node": ">=4"
}
},
+ "node_modules/pyret-lang/node_modules/asn1.js": {
+ "version": "4.10.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/asn1.js/node_modules/bn.js": {
+ "version": "4.12.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
"node_modules/pyret-lang/node_modules/assert": {
"version": "1.5.1",
"inBundle": true,
@@ -17955,25 +17975,6 @@
"node": ">= 0.12"
}
},
- "node_modules/pyret-lang/node_modules/browserify-sign/node_modules/elliptic": {
- "version": "6.6.1",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.11.9",
- "brorand": "^1.1.0",
- "hash.js": "^1.0.0",
- "hmac-drbg": "^1.0.1",
- "inherits": "^2.0.4",
- "minimalistic-assert": "^1.0.1",
- "minimalistic-crypto-utils": "^1.0.1"
- }
- },
- "node_modules/pyret-lang/node_modules/browserify-sign/node_modules/elliptic/node_modules/bn.js": {
- "version": "4.12.2",
- "inBundle": true,
- "license": "MIT"
- },
"node_modules/pyret-lang/node_modules/browserify-zlib": {
"version": "0.2.0",
"inBundle": true,
@@ -18116,6 +18117,20 @@
"inBundle": true,
"license": "MIT"
},
+ "node_modules/pyret-lang/node_modules/create-ecdh": {
+ "version": "4.0.4",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.5.3"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/create-ecdh/node_modules/bn.js": {
+ "version": "4.12.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
"node_modules/pyret-lang/node_modules/create-hash": {
"version": "1.2.0",
"inBundle": true,
@@ -18166,69 +18181,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/bn.js": {
- "version": "4.12.2",
- "inBundle": true,
- "license": "MIT"
- },
- "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/create-ecdh": {
- "version": "4.0.4",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.1.0",
- "elliptic": "^6.5.3"
- }
- },
- "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/diffie-hellman": {
- "version": "5.0.3",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.1.0",
- "miller-rabin": "^4.0.0",
- "randombytes": "^2.0.0"
- }
- },
- "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/elliptic": {
- "version": "6.6.1",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.11.9",
- "brorand": "^1.1.0",
- "hash.js": "^1.0.0",
- "hmac-drbg": "^1.0.1",
- "inherits": "^2.0.4",
- "minimalistic-assert": "^1.0.1",
- "minimalistic-crypto-utils": "^1.0.1"
- }
- },
- "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/miller-rabin": {
- "version": "4.0.1",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.0.0",
- "brorand": "^1.0.1"
- },
- "bin": {
- "miller-rabin": "bin/miller-rabin"
- }
- },
- "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/public-encrypt": {
- "version": "4.0.3",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.1.0",
- "browserify-rsa": "^4.0.0",
- "create-hash": "^1.1.0",
- "parse-asn1": "^5.0.0",
- "randombytes": "^2.0.1",
- "safe-buffer": "^5.1.2"
- }
- },
"node_modules/pyret-lang/node_modules/dash-ast": {
"version": "1.0.0",
"inBundle": true,
@@ -18313,6 +18265,21 @@
"node": ">=0.8.0"
}
},
+ "node_modules/pyret-lang/node_modules/diffie-hellman": {
+ "version": "5.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.1.0",
+ "miller-rabin": "^4.0.0",
+ "randombytes": "^2.0.0"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/diffie-hellman/node_modules/bn.js": {
+ "version": "4.12.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
"node_modules/pyret-lang/node_modules/domain-browser": {
"version": "1.2.0",
"inBundle": true,
@@ -18343,6 +18310,25 @@
"readable-stream": "^2.0.2"
}
},
+ "node_modules/pyret-lang/node_modules/elliptic": {
+ "version": "6.6.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.11.9",
+ "brorand": "^1.1.0",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.1",
+ "inherits": "^2.0.4",
+ "minimalistic-assert": "^1.0.1",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/elliptic/node_modules/bn.js": {
+ "version": "4.12.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
"node_modules/pyret-lang/node_modules/es-define-property": {
"version": "1.0.1",
"inBundle": true,
@@ -18800,6 +18786,23 @@
"safe-buffer": "^5.1.2"
}
},
+ "node_modules/pyret-lang/node_modules/miller-rabin": {
+ "version": "4.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.0.0",
+ "brorand": "^1.0.1"
+ },
+ "bin": {
+ "miller-rabin": "bin/miller-rabin"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/miller-rabin/node_modules/bn.js": {
+ "version": "4.12.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
"node_modules/pyret-lang/node_modules/minimalistic-assert": {
"version": "1.0.1",
"inBundle": true,
@@ -18942,21 +18945,6 @@
"node": ">= 0.10"
}
},
- "node_modules/pyret-lang/node_modules/parse-asn1/node_modules/asn1.js": {
- "version": "4.10.1",
- "inBundle": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.0.0",
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0"
- }
- },
- "node_modules/pyret-lang/node_modules/parse-asn1/node_modules/bn.js": {
- "version": "4.12.2",
- "inBundle": true,
- "license": "MIT"
- },
"node_modules/pyret-lang/node_modules/path-browserify": {
"version": "1.0.1",
"inBundle": true,
@@ -18984,20 +18972,49 @@
}
},
"node_modules/pyret-lang/node_modules/pbkdf2": {
- "version": "3.1.2",
+ "version": "3.1.3",
"inBundle": true,
"license": "MIT",
"dependencies": {
- "create-hash": "^1.1.2",
- "create-hmac": "^1.1.4",
- "ripemd160": "^2.0.1",
- "safe-buffer": "^5.0.1",
- "sha.js": "^2.4.8"
+ "create-hash": "~1.1.3",
+ "create-hmac": "^1.1.7",
+ "ripemd160": "=2.0.1",
+ "safe-buffer": "^5.2.1",
+ "sha.js": "^2.4.11",
+ "to-buffer": "^1.2.0"
},
"engines": {
"node": ">=0.12"
}
},
+ "node_modules/pyret-lang/node_modules/pbkdf2/node_modules/create-hash": {
+ "version": "1.1.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/pbkdf2/node_modules/hash-base": {
+ "version": "2.0.2",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.1"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/pbkdf2/node_modules/ripemd160": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "hash-base": "^2.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
"node_modules/pyret-lang/node_modules/possible-typed-array-names": {
"version": "1.1.0",
"inBundle": true,
@@ -19019,6 +19036,24 @@
"inBundle": true,
"license": "MIT"
},
+ "node_modules/pyret-lang/node_modules/public-encrypt": {
+ "version": "4.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.1.0",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "parse-asn1": "^5.0.0",
+ "randombytes": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/public-encrypt/node_modules/bn.js": {
+ "version": "4.12.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
"node_modules/pyret-lang/node_modules/punycode": {
"version": "1.4.1",
"inBundle": true,
@@ -19201,15 +19236,22 @@
}
},
"node_modules/pyret-lang/node_modules/sha.js": {
- "version": "2.4.11",
+ "version": "2.4.12",
"inBundle": true,
"license": "(MIT AND BSD-3-Clause)",
"dependencies": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
+ "inherits": "^2.0.4",
+ "safe-buffer": "^5.2.1",
+ "to-buffer": "^1.2.0"
},
"bin": {
"sha.js": "bin.js"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/pyret-lang/node_modules/shasum-object": {
@@ -19461,11 +19503,42 @@
"node": ">=0.6.0"
}
},
+ "node_modules/pyret-lang/node_modules/to-buffer": {
+ "version": "1.2.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "isarray": "^2.0.5",
+ "safe-buffer": "^5.2.1",
+ "typed-array-buffer": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/pyret-lang/node_modules/to-buffer/node_modules/isarray": {
+ "version": "2.0.5",
+ "inBundle": true,
+ "license": "MIT"
+ },
"node_modules/pyret-lang/node_modules/tty-browserify": {
"version": "0.0.1",
"inBundle": true,
"license": "MIT"
},
+ "node_modules/pyret-lang/node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/pyret-lang/node_modules/typedarray": {
"version": "0.0.6",
"inBundle": true,
@@ -19754,6 +19827,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-15.7.0.tgz",
"integrity": "sha512-5/MMRYmpmM0sMTHGLossnJCrmXQIiJilD6y3YN3TzAwGFj6zdnMtFv6xmi65PHKRV+pehIHpT7oy67Sr6s9AHA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"create-react-class": "^15.6.0",
"fbjs": "^0.8.9",
@@ -20336,6 +20410,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -23051,6 +23126,7 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
@@ -23098,6 +23174,7 @@
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1",
diff --git a/code.pyret.org/src/web/close.html b/code.pyret.org/src/web/close.html
index fcaf23f08..0a9fe8a89 100644
--- a/code.pyret.org/src/web/close.html
+++ b/code.pyret.org/src/web/close.html
@@ -2,8 +2,39 @@
diff --git a/code.pyret.org/src/web/editor.html b/code.pyret.org/src/web/editor.html
index db564355c..f8ebe386e 100644
--- a/code.pyret.org/src/web/editor.html
+++ b/code.pyret.org/src/web/editor.html
@@ -156,7 +156,7 @@
aria-label="Contribute detailed usage information"/>
Contribute detailed usage information.
- ?
diff --git a/code.pyret.org/src/web/faq.html b/code.pyret.org/src/web/faq.html
index 10864f872..347b190ac 100644
--- a/code.pyret.org/src/web/faq.html
+++ b/code.pyret.org/src/web/faq.html
@@ -42,15 +42,7 @@ What permissions does code.pyret.org ask for and why?
Know who you are on Google and View your email address :
The site needs to know your Google identity because this allows us to give
persistent access to saving to Drive that doesn't expire or require popping up
-new windows while you're editing.
-
-Manage your photos and videos and View the photos, videos, and
-albums in your Google Drive : This is used to import images into programs
-from your Drive (which can be useful for customizing games, for example).
-
-
-View and manage your spreadsheets on Google Drive : This enables
-importing tables and working with data sources in your Drive.
+new windows while you're editing.
Add itself to Google Drive : This lets you right-click on Pyret
programs in Google Drive and "Open with Pyret."
diff --git a/code.pyret.org/src/web/js/cpo-main.js b/code.pyret.org/src/web/js/cpo-main.js
index 33f023239..41eedfa16 100644
--- a/code.pyret.org/src/web/js/cpo-main.js
+++ b/code.pyret.org/src/web/js/cpo-main.js
@@ -150,6 +150,15 @@
// NOTE(joe): this function just allocates a closure, so it's stack-safe
var onCompile = gmf(cpo, "make-on-compile").app(runtime.makeFunction(saveGDriveCachedFile, "save-gdrive-cached-file"));
+ function maybeAppendSlash(s) {
+ if(s.endsWith("/")) { return s; }
+ return s + "/";
+ }
+
+ function urlResolve(path, base) {
+ return new URL(path, base).href;
+ }
+
// NOTE(joe/ben): this function _used_ to be trivially stack safe, but files
// need to resolve their absolute path to calculate their URI, which
// requires an RPC, so this function is no-longer trivially flat
@@ -198,7 +207,7 @@
return arr[0];
}
else if (protocol === "url-file") {
- return arr[0] + "/" + arr[1];
+ return urlResolve(arr[1], maybeAppendSlash(arr[0]));
}
else {
console.error("Unknown import: ", dependency);
@@ -256,7 +265,7 @@
return runtime.getField(runtime.getField(urlLoc, "values"), "url-locator").app(arr[0], replGlobals);
}
else if (protocol === "url-file") {
- const fullUrl = arr[0] + "/" + arr[1];
+ const fullUrl = urlResolve(arr[1], maybeAppendSlash(arr[0]));
switch(urlFileMode) {
case "all-remote":
fetch(fullUrl).then(async (response) => {
diff --git a/code.pyret.org/src/web/js/dashboard/StudentDashboard.js b/code.pyret.org/src/web/js/dashboard/StudentDashboard.js
index 1fbd9b4d1..b64edeb72 100644
--- a/code.pyret.org/src/web/js/dashboard/StudentDashboard.js
+++ b/code.pyret.org/src/web/js/dashboard/StudentDashboard.js
@@ -228,7 +228,7 @@ class StudentDashboard extends Component {
diff --git a/code.pyret.org/src/web/js/google-apis/api-wrapper.js b/code.pyret.org/src/web/js/google-apis/api-wrapper.js
index abcdda5f1..7cb72cf12 100644
--- a/code.pyret.org/src/web/js/google-apis/api-wrapper.js
+++ b/code.pyret.org/src/web/js/google-apis/api-wrapper.js
@@ -51,15 +51,73 @@ function reauth(immediate, useFullScopes) {
if(useFullScopes) {
path += "&scopes=full";
}
- // Need to do a login to get a cookie for this user; do it in a popup
- window.addEventListener('message', function(e) {
+
+ // Track whether we've already resolved to avoid double-resolution
+ var resolved = false;
+ function resolveOnce(method) {
+ if (!resolved) {
+ console.log("INFO: Popup login resolved by: ", method);
+ resolved = true;
+ // NOTE(joe): A useful thing to do for testing is to comment out this
+ // cleanup(), and check which of the 3 methods are returning success
+ // here. cleanup() will stop others from triggering.
+ cleanup();
+ d.resolve(reauth(true, useFullScopes));
+ }
+ else {
+ console.log("INFO: Popup login resolved again (ignored): ", method);
+ }
+ }
+
+ // Cleanup function to remove all listeners
+ var channel = null;
+ function cleanup() {
+ window.removeEventListener('message', messageHandler);
+ window.removeEventListener('storage', storageHandler);
+ try { localStorage.removeItem('pyret_auth_complete'); } catch (err) {}
+ if (channel) {
+ try { channel.close(); }
+ finally { channel = null; }
+ }
+ }
+
+ // Method 1: Traditional postMessage (works when COOP allows window.opener)
+ function messageHandler(e) {
// e.domain appears to not be defined in Firefox
if ((e.domain || e.origin) === document.location.origin) {
- d.resolve(reauth(true, useFullScopes));
- } else {
- d.resolve(null);
+ resolveOnce("postMessage");
}
- });
+ }
+ window.addEventListener('message', messageHandler);
+
+ // Method 2: BroadcastChannel (works even when COOP severs window.opener)
+ // This is the fallback for environments like GoGuardian that inject COOP headers
+ if (typeof BroadcastChannel !== 'undefined') {
+ try {
+ channel = new BroadcastChannel('pyret_auth');
+ channel.onmessage = function(e) {
+ if (e.data && e.data.type === 'auth_complete') {
+ resolveOnce("Broadcast");
+ }
+ };
+ } catch (e) {
+ console.warn("BroadcastChannel setup failed:", e);
+ }
+ }
+
+ // Method 3: localStorage fallback for very old browsers without BroadcastChannel
+ function storageHandler(e) {
+ if (e.key === 'pyret_auth_complete') {
+ resolveOnce("localStorage");
+ // Clean up the flag
+ try { localStorage.removeItem('pyret_auth_complete'); } catch (err) {}
+ }
+ }
+ // Clear any stale auth flag before opening popup
+ try { localStorage.removeItem('pyret_auth_complete'); } catch (e) {}
+ window.addEventListener('storage', storageHandler);
+
+ // Need to do a login to get a cookie for this user; do it in a popup
window.open(path);
} else {
// The user is logged in, but needs an access token from our server
diff --git a/code.pyret.org/src/web/js/ide.js b/code.pyret.org/src/web/js/ide.js
index fe9bb6128..8b3f876f4 100644
--- a/code.pyret.org/src/web/js/ide.js
+++ b/code.pyret.org/src/web/js/ide.js
@@ -1,4 +1,3 @@
-import React from 'react';
import { init, LanguageError, UserError, HoverHighlight } from 'pyret-ide';
import seedrandom from 'seedrandom';
import sExpression from 's-expression';
diff --git a/code.pyret.org/src/web/js/output-ui.js b/code.pyret.org/src/web/js/output-ui.js
index acba54a8d..87bef7851 100644
--- a/code.pyret.org/src/web/js/output-ui.js
+++ b/code.pyret.org/src/web/js/output-ui.js
@@ -520,7 +520,7 @@
while (todo.length > 0) {
var first = todo.pop();
if (runtime.hasField(first, "l")) {
- // not every AST item has an "l" field (eg. s-global, s-type-global, s-base)
+ // not every AST item has an "l" field (eg. s-defined-module, s-defined-value, s-defined-var, etc)
var l = runtime.getField(first, "l");
if (isSrcloc.app(l)) { // just extra checking; should be unnecessary
if (runtime.equal_always(l, loc)) { // stack-safe because srclocs are flat data
diff --git a/code.pyret.org/src/web/js/trove/world.js b/code.pyret.org/src/web/js/trove/world.js
index 7eded91a2..f4a1f0d02 100644
--- a/code.pyret.org/src/web/js/trove/world.js
+++ b/code.pyret.org/src/web/js/trove/world.js
@@ -532,8 +532,7 @@
}
var ctx = reusableCanvas.getContext("2d");
ctx.save();
- ctx.fillStyle = "rgba(255,255,255,0)";
- ctx.fillRect(0, 0, width, height);
+ ctx.clearRect(0, 0, width, height);
ctx.restore();
theImage.render(ctx, 0, 0);
success([toplevelNode, reusableCanvasNode]);
diff --git a/docs/src/trove/ast.js.rkt b/docs/src/trove/ast.js.rkt
index 378598239..5ec054104 100644
--- a/docs/src/trove/ast.js.rkt
+++ b/docs/src/trove/ast.js.rkt
@@ -262,7 +262,7 @@
}
}
@constr-spec["s-global"]{
- @members{@member-spec["s"]}
+ @members{@member-spec["l"] @member-spec["s"]}
@with-members{
@method-spec[
"to-compiled-source"
@@ -297,7 +297,7 @@
}
}
@constr-spec["s-type-global"]{
- @members{@member-spec["s"]}
+ @members{@member-spec["l"] @member-spec["s"]}
@with-members{
@method-spec[
"to-compiled-source"
@@ -332,7 +332,7 @@
}
}
@constr-spec["s-atom"]{
- @members{@member-spec["base"] @member-spec["serial"]}
+ @members{@member-spec["l"] @member-spec["base"] @member-spec["serial"]}
@with-members{
@method-spec[
"to-compiled-source"
diff --git a/embed/package-lock.json b/embed/package-lock.json
index 5053675ca..e5bf9cc2d 100644
--- a/embed/package-lock.json
+++ b/embed/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pyret-embed",
- "version": "0.0.46",
+ "version": "0.0.57",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pyret-embed",
- "version": "0.0.46",
+ "version": "0.0.57",
"license": "ISC",
"dependencies": {
"@zenfs/core": "^2.2.3",
diff --git a/embed/package.json b/embed/package.json
index 07f9c850e..e74879823 100644
--- a/embed/package.json
+++ b/embed/package.json
@@ -1,6 +1,6 @@
{
"name": "pyret-embed",
- "version": "0.0.46",
+ "version": "0.0.57",
"description": "A library for embedding Pyret into webpages",
"main": "dist/pyret.js",
"files": [
diff --git a/lang/package-lock.json b/lang/package-lock.json
index 08bc17c0e..be610f654 100644
--- a/lang/package-lock.json
+++ b/lang/package-lock.json
@@ -45,6 +45,7 @@
"ws": "^8.18.0"
},
"devDependencies": {
+ "@types/ws": "^8.18.1",
"http-server": "^14.1.1",
"jest": "^30.0.4"
}
@@ -94,6 +95,7 @@
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -1388,6 +1390,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@@ -2342,6 +2354,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
diff --git a/lang/package.json b/lang/package.json
index b79e78ccf..561083ac6 100644
--- a/lang/package.json
+++ b/lang/package.json
@@ -65,6 +65,7 @@
},
"homepage": "https://github.com/brownplt/pyret-lang",
"devDependencies": {
+ "@types/ws": "^8.18.1",
"http-server": "^14.1.1",
"jest": "^30.0.4"
}
diff --git a/lang/src/arr/compiler/anf-loop-compiler.arr b/lang/src/arr/compiler/anf-loop-compiler.arr
index 24cc05cc8..fee2d61f3 100644
--- a/lang/src/arr/compiler/anf-loop-compiler.arr
+++ b/lang/src/arr/compiler/anf-loop-compiler.arr
@@ -133,7 +133,7 @@ effective-ids = D.make-mutable-string-dict()
fun fresh-id(id :: A.Name) -> A.Name:
base-name = if A.is-s-type-global(id): id.tosourcestring() else: id.toname() end
no-hyphens = string-replace(base-name, "-", "$")
- n = js-names.make-atom(no-hyphens)
+ n = js-names.make-atom(id.l, no-hyphens)
if effective-ids.has-key-now(n.tosourcestring()) block: #awkward name collision!
fresh-id(id)
else:
@@ -2226,11 +2226,11 @@ fun compile-module(self, l, prog-provides, imports-in, prog, freevars, provides,
global-binds = for CL.map_list(n from module-and-global-binds.is-false):
{ maybe-origin; which } =
cases(A.Name) n:
- | s-module-global(s) =>
+ | s-module-global(_, s) =>
{ env.origin-by-module-name(n.toname()); "modules"}
- | s-global(s) =>
+ | s-global(_, s) =>
{ env.origin-by-value-name(n.toname()); "values"}
- | s-type-global(s) =>
+ | s-type-global(_, s) =>
{ env.origin-by-type-name(n.toname()); "types"}
end
@@ -2291,7 +2291,7 @@ fun compile-module(self, l, prog-provides, imports-in, prog, freevars, provides,
module-id = fresh-id(compiler-name(l.source)).tosourcestring()
module-ref = lam(name): j-bracket(rt-field("modules"), j-str(name)) end
input-ids = CL.map_list(lam(i):
- if A.is-s-atom(i) and (i.base == "$import"): js-names.make-atom("$$import")
+ if A.is-s-atom(i) and (i.base == "$import"): js-names.make-atom(i.l, "$$import")
else: js-id-of(compiler-name(i.toname()))
end
end, mod-ids)
diff --git a/lang/src/arr/compiler/anf.arr b/lang/src/arr/compiler/anf.arr
index a05807000..f5e0a6348 100644
--- a/lang/src/arr/compiler/anf.arr
+++ b/lang/src/arr/compiler/anf.arr
@@ -18,7 +18,7 @@ names = A.global-names
flat-prim-app = A.prim-app-info-c(false)
fun mk-id(loc, base):
- t = names.make-atom(base)
+ t = names.make-atom(loc, base)
{ id: t, id-b: bind(loc, t), id-e: N.a-id(loc, t) }
end
@@ -325,7 +325,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr:
| s-tuple-get(l, tup, index, index-loc) =>
anf-name(tup, "anf_tuple_get", lam(v): k(N.a-tuple-get(l, v, index)) end)
| s-array(l, values) =>
- array-id = names.make-atom("anf_array")
+ array-id = names.make-atom(l, "anf_array")
N.a-let(
l,
bind(l, array-id),
@@ -354,7 +354,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr:
| a-blank => A.s-let-expr(l, let-binds, body, blocky)
| a-any(_) => A.s-let-expr(l, let-binds, body, blocky)
| else =>
- a = A.global-names.make-atom("inline_body")
+ a = A.global-names.make-atom(l, "inline_body")
A.s-let-expr(l,
let-binds
+ [list: A.s-let-bind(body.l, A.s-bind(l, false, a, ann), body)],
diff --git a/lang/src/arr/compiler/ast-anf.arr b/lang/src/arr/compiler/ast-anf.arr
index 140b0f614..dd6e82981 100644
--- a/lang/src/arr/compiler/ast-anf.arr
+++ b/lang/src/arr/compiler/ast-anf.arr
@@ -781,8 +781,8 @@ fun freevars-e(expr :: AExpr) -> FrozenNameDict:
where:
d = dummy-loc
n = A.global-names.make-atom
- x = n("x")
- y = n("y")
+ x = n(d, "x")
+ y = n(d, "y")
freevars-e(
a-let(d, a-bind(d, x, A.a-blank), a-val(d, a-num(d, 4)),
a-lettable(d, a-val(d, a-id(d, y))))).keys-list() is [list: y.key()]
diff --git a/lang/src/arr/compiler/ast-util.arr b/lang/src/arr/compiler/ast-util.arr
index 4e63d40d9..dbb6b9ad0 100644
--- a/lang/src/arr/compiler/ast-util.arr
+++ b/lang/src/arr/compiler/ast-util.arr
@@ -150,12 +150,12 @@ end
fun binding-type-env-from-env(env):
for SD.fold-keys(acc from SD.make-string-dict(), name from env.globals.types):
- acc.set(A.s-type-global(name).key(), e-bind(A.dummy-loc, false, b-typ))
+ acc.set(A.s-type-global(A.dummy-loc, name).key(), e-bind(A.dummy-loc, false, b-typ))
end
end
fun binding-env-from-env(env):
for SD.fold-keys(acc from SD.make-string-dict(), name from env.globals.values):
- acc.set(A.s-global(name).key(), e-bind(A.dummy-loc, false, b-prim(name)))
+ acc.set(A.s-global(A.dummy-loc, name).key(), e-bind(A.dummy-loc, false, b-prim(name)))
end
end
@@ -526,7 +526,7 @@ inline-lams = A.default-map-visitor.{
cases(A.Expr) f:
| s-lam(l, _, _, args, ann, _, body, _, _, _) =>
if (args.length() == exps.length()):
- a = A.global-names.make-atom("inline_body")
+ a = A.global-names.make-atom(l, "inline_body")
let-binds = for lists.map2(arg from args, exp from exps):
A.s-let-bind(arg.l, arg, exp.visit(self))
end
@@ -897,8 +897,8 @@ strip-annotations-visitor = A.default-map-visitor.{
fun make-renamer(replacements :: SD.StringDict):
A.default-map-visitor.{
- method s-atom(self, base, serial):
- a = A.s-atom(base, serial)
+ method s-atom(self, l, base, serial):
+ a = A.s-atom(l, base, serial)
k = a.key()
if replacements.has-key(k):
replacements.get-value(k)
@@ -937,7 +937,7 @@ fun wrap-extra-imports(p :: A.Program, env :: CS.ExtraImports) -> A.Program:
|#
l = A.dummy-loc
for fold(lst from empty, i from imports):
- name-to-use = if i.as-name == "_": A.global-names.make-atom("$extra-import") else: A.s-name(l, i.as-name) end
+ name-to-use = if i.as-name == "_": A.global-names.make-atom(l, "$extra-import") else: A.s-name(l, i.as-name) end
ast-dep = cases(CS.Dependency) i.dependency:
| builtin(name) => A.s-const-import(p.l, name)
| dependency(protocol, args) => A.s-special-import(p.l, protocol, args)
@@ -990,7 +990,7 @@ fun ann-to-typ(a :: A.Ann, uri, compile-env) -> T.Type:
| a-any(l) => T.t-top(l, false)
| a-name(l, id) =>
cases(A.Name) id:
- | s-type-global(name) =>
+ | s-type-global(_, name) =>
cases(Option) compile-env.globals.types.get(name):
| none =>
raise("Name not found in globals.types: " + name)
@@ -998,7 +998,7 @@ fun ann-to-typ(a :: A.Ann, uri, compile-env) -> T.Type:
# ```include from string-dict: type StringDict as SD end```
T.t-name(T.module-uri(origin.uri-of-definition), origin.original-name, l, false)
end
- | s-atom(_, _) => T.t-name(T.module-uri(uri), id, l, false)
+ | s-atom(_, _, _) => T.t-name(T.module-uri(uri), id, l, false)
| else => raise("Bad name found in ann-to-typ: " + id.key())
end
| a-type-var(l, id) =>
@@ -1458,7 +1458,7 @@ fun get-typed-provides(resolved, typed :: TCS.Typed, uri :: URI, compile-env ::
transformer = lam(t):
cases(T.Type) t:
| t-name(origin, name, l, inferred) =>
- T.t-name(origin, A.s-type-global(name.toname()), l, inferred)
+ T.t-name(origin, A.s-type-global(l, name.toname()), l, inferred)
| else => t
end
end
@@ -1546,3 +1546,80 @@ fun get-typed-provides(resolved, typed :: TCS.Typed, uri :: URI, compile-env ::
end
end
end
+
+
+fun find-name-key-by-srcloc(resolved :: A.Program, srcloc :: Loc) -> Option block:
+ var result-mangled-name = none
+ visitor = A.default-iter-visitor.{
+ # Use sites: s-id(use-l, atom/global) — match on the outer l, return inner key
+ method s-id(self, l, id):
+ if l == srcloc block:
+ result-mangled-name := some(id.key())
+ false
+ else:
+ true
+ end
+ end,
+ method s-id-var(self, l, id):
+ if l == srcloc block:
+ result-mangled-name := some(id.key())
+ false
+ else:
+ true
+ end
+ end,
+ method s-id-letrec(self, l, id, safe):
+ if l == srcloc block:
+ result-mangled-name := some(id.key())
+ false
+ else:
+ true
+ end
+ end,
+ # Binding sites: s-atom/s-global appear directly with bind-l — only match
+ # if the user clicked exactly on a binding site (rare but possible)
+ method s-atom(self, l, base, serial):
+ if l == srcloc block:
+ result-mangled-name := some(A.s-atom(l, base, serial).key())
+ false
+ else:
+ true
+ end
+ end,
+ method s-global(self, l, s):
+ if l == srcloc block:
+ result-mangled-name := some(A.s-global(l, s).key())
+ false
+ else:
+ true
+ end
+ end
+ }
+
+ resolved.visit(visitor)
+ result-mangled-name
+end
+
+is-s-name = A.is-s-name
+fun find-name-at(prog :: A.Program, line :: Number, col :: Number) -> Option block:
+ var result-name = none
+ visitor = A.default-iter-visitor.{
+ method s-name(self, l, s):
+ cases (Loc) l:
+ | builtin(_) => true
+ | srcloc(_, sl, sc, _, el, ec, _) =>
+ if (sl <= line) and (line <= el) and (sc <= col) and (col <= ec) block:
+ result-name := some(A.s-name(l, s))
+ false
+ else:
+ true
+ end
+ end
+ end
+ }
+
+ prog.visit(visitor)
+ result-name
+end
+
+
diff --git a/lang/src/arr/compiler/cli-module-loader.arr b/lang/src/arr/compiler/cli-module-loader.arr
index 4b9dbf414..cdcf64044 100644
--- a/lang/src/arr/compiler/cli-module-loader.arr
+++ b/lang/src/arr/compiler/cli-module-loader.arr
@@ -25,64 +25,39 @@ import file("locators/jsfile.arr") as JSF
import file("locators/npm.arr") as NPM
import file("js-of-pyret.arr") as JSP
-j-fun = J.j-fun
-j-var = J.j-var
-j-id = J.j-id
-j-method = J.j-method
-j-block = J.j-block
-j-true = J.j-true
-j-false = J.j-false
-j-num = J.j-num
-j-str = J.j-str
-j-return = J.j-return
-j-assign = J.j-assign
-j-if = J.j-if
-j-if1 = J.j-if1
-j-new = J.j-new
-j-app = J.j-app
-j-list = J.j-list
-j-obj = J.j-obj
-j-dot = J.j-dot
-j-bracket = J.j-bracket
-j-field = J.j-field
-j-dot-assign = J.j-dot-assign
-j-bracket-assign = J.j-bracket-assign
-j-try-catch = J.j-try-catch
-j-throw = J.j-throw
-j-expr = J.j-expr
-j-binop = J.j-binop
-j-and = J.j-and
-j-lt = J.j-lt
-j-eq = J.j-eq
-j-neq = J.j-neq
-j-geq = J.j-geq
-j-unop = J.j-unop
-j-decr = J.j-decr
-j-incr = J.j-incr
-j-not = J.j-not
-j-instanceof = J.j-instanceof
-j-ternary = J.j-ternary
-j-null = J.j-null
-j-parens = J.j-parens
-j-switch = J.j-switch
-j-case = J.j-case
-j-default = J.j-default
-j-label = J.j-label
-j-break = J.j-break
-j-while = J.j-while
-j-for = J.j-for
-
-clist = C.clist
+include from J:
+ data JStmt,
+ data JExpr,
+ data JBlock,
+end
-type Loadable = CS.Loadable
+include from E:
+ data Either
+end
+include from CS:
+ type Loadable
+end
-type Either = E.Either
+clist = C.clist
fun uri-to-path(uri, name):
name + "-" + crypto.sha256(uri)
end
+type CacheManager = {
+ cached-available :: (String, String, String, Number -> Option),
+ get-cached :: (String, String, String, Any -> Any),
+ get-cached-if-available :: (String, Any -> Any),
+ get-loadable :: (String, List, Any, Any -> Option),
+ set-loadable :: (String, Any, Any -> String),
+ get-builtin-locator :: (String, List, String -> Any),
+ set-surface-ast :: (String, Any -> Nothing),
+ get-surface-ast :: (String -> Option),
+ set-named-result :: (String, Any -> Nothing),
+ get-named-result :: (String -> Option)
+}
+
# NOTE(joe): This is just a little one-off type to represent a simple
# situation: Builtin pure-JS files are stored in single files with a hash
# followed by .js, while builtin Pyret files are stored in two files – one with
@@ -100,7 +75,7 @@ end
# it's fine to pass a modified time of 0 to indicate that we're always happy
# with the compiled version of the file.
-fun cached-available(basedir, uri, name, modified-time) -> Option:
+fun file-cached-available(basedir, uri, name, modified-time) -> Option:
saved-path = Filesystem.join(basedir, uri-to-path(uri, name))
if (Filesystem.exists(saved-path + "-static.js") and
@@ -114,7 +89,17 @@ fun cached-available(basedir, uri, name, modified-time) -> Option:
end
end
-fun get-cached(basedir, uri, name, cache-type):
+fun mem-cached-available(store, basedir, uri, name, _) -> Option:
+ key = basedir + uri + name
+ if store.has-key-now(key):
+ some(nothing)
+ else:
+ none
+ end
+end
+
+
+fun file-get-cached(basedir, uri, name, cache-type):
saved-path = Filesystem.join(basedir, uri-to-path(uri, name))
{static-path; module-path} = cases(CachedType) cache-type:
# NOTE(joe): leaving off .js because builtin-raw-locator below
@@ -172,16 +157,70 @@ fun get-cached(basedir, uri, name, cache-type):
}
end
-fun get-cached-if-available(basedir, loc) block:
- get-cached-if-available-known-mtimes(basedir, loc, [SD.string-dict:])
+fun mem-get-cached(store, _, uri, name, _):
+ {
+ method get-uncached(_): none end,
+ method needs-compile(_, _): false end,
+ method get-modified-time(_): 0 end,
+ method get-options(_, options): options.{ checks: "none" } end,
+ method get-module(_):
+ cases(Option) store.get-now(uri):
+ | some(entry) =>
+ cases(Option) entry.surface-ast:
+ | some(ast) => CL.pyret-ast(ast)
+ | none => raise("No cached source for module " + uri)
+ end
+ | none => raise("No cached source for module " + uri)
+ end
+ end,
+ method get-extra-imports(_):
+ if CL.is-builtin-module(uri): CS.minimal-imports
+ else: CS.standard-imports
+ end
+ end,
+ method get-dependencies(_):
+ cases(Option) store.get-now(uri):
+ | some(entry) =>
+ cases(Option) entry.surface-ast:
+ | some(ast) =>
+ if CL.is-builtin-module(uri):
+ CL.get-dependencies(CL.pyret-ast(ast), uri)
+ else:
+ CL.get-standard-dependencies(CL.pyret-ast(ast), uri)
+ end
+ | none => empty
+ end
+ | none => empty
+ end
+ end,
+ method get-native-modules(_): [list:] end,
+ method get-globals(_): CS.standard-globals end,
+ method uri(_): uri end,
+ method name(_): name end,
+ method set-compiled(_, _, _): nothing end,
+ method get-compiled(_):
+ cases(Option) store.get-now(uri):
+ | some(entry) => entry.loadable
+ | none => none
+ end
+ end,
+ method _equals(self, other, req-eq):
+ req-eq(self.uri(), other.uri())
+ end
+ }
+end
+
+
+fun get-cached-if-available(cache-manager, basedir, loc) block:
+ get-cached-if-available-known-mtimes(cache-manager, basedir, loc, [SD.string-dict:])
end
-fun get-cached-if-available-known-mtimes(basedir, loc, max-dep-times) block:
+fun get-cached-if-available-known-mtimes(cache-manager, basedir, loc, max-dep-times) block:
saved-path = Filesystem.join(basedir, uri-to-path(loc.uri(), loc.name()))
dependency-based-mtime =
if max-dep-times.has-key(loc.uri()): max-dep-times.get-value(loc.uri())
else: loc.get-modified-time()
end
- cached-type = cached-available(basedir, loc.uri(), loc.name(), dependency-based-mtime)
+ cached-type = cache-manager.cached-available(basedir, loc.uri(), loc.name(), dependency-based-mtime)
cases(Option) cached-type:
| none =>
cases(Option) loc.get-uncached():
@@ -189,54 +228,53 @@ fun get-cached-if-available-known-mtimes(basedir, loc, max-dep-times) block:
| none => loc
end
- | some(ct) => get-cached(basedir, loc.uri(), loc.name(), ct).{
+ | some(ct) => cache-manager.get-cached(basedir, loc.uri(), loc.name(), ct).{
method get-uncached(self): some(loc) end
}
end
end
-fun get-file-locator(basedir, real-path):
+fun get-file-locator(cache-manager, basedir, real-path):
loc = FL.file-locator(real-path, CS.standard-globals)
- get-cached-if-available(basedir, loc)
+ get-cached-if-available(cache-manager, basedir, loc)
end
-fun get-builtin-locator(basedir, read-only-basedirs, modname):
+fun file-get-builtin-locator(cache-manager, basedir, read-only-basedirs, modname):
all-dirs = read-only-basedirs
first-available = for find(rob from all-dirs):
- is-some(cached-available(rob, "builtin://" + modname, modname, 0))
+ is-some(cache-manager.cached-available(rob, "builtin://" + modname, modname, 0))
end
cases(Option) first-available:
| none =>
cases(Option) BL.maybe-make-builtin-locator(modname) block:
| some(loc) =>
- get-cached-if-available(basedir, loc)
+ get-cached-if-available(cache-manager, basedir, loc)
| none =>
raise("Could not find builtin module " + modname + " in any of " + all-dirs.join-str(", "))
end
| some(ro-basedir) =>
- ca = cached-available(ro-basedir, "builtin://" + modname, modname, 0).or-else(split)
- get-cached(ro-basedir, "builtin://" + modname, modname, ca)
+ ca = cache-manager.cached-available(ro-basedir, "builtin://" + modname, modname, 0).or-else(split)
+ cache-manager.get-cached(ro-basedir, "builtin://" + modname, modname, ca)
end
end
-fun get-builtin-test-locator(basedir, modname):
+fun get-builtin-test-locator(cache-manager, basedir, modname):
loc = BL.make-builtin-locator(modname).{
method uri(_): "builtin-test://" + modname end
}
- get-cached-if-available(basedir, loc)
+ cache-manager.get-cached-if-available(basedir, loc)
end
-fun get-loadable(basedir, read-only-basedirs, l, max-dep-times) -> Option:
+fun get-loadable-impl(cache-manager, basedir, read-only-basedirs, l, max-dep-times) -> Option:
locuri = l.locator.uri()
-# cached = cached-available(basedir, l.locator.uri(), l.locator.name(), l.locator.get-modified-time())
first-available = for find(rob from link(basedir, read-only-basedirs)):
- is-some(cached-available(rob, l.locator.uri(), l.locator.name(), max-dep-times.get-value(locuri)))
+ is-some(cache-manager.cached-available(rob, l.locator.uri(), l.locator.name(), max-dep-times.get-value(locuri)))
end
cases(Option) first-available block:
| none => none
- | some(found-basedir) =>
- c = cached-available(found-basedir, l.locator.uri(), l.locator.name(), max-dep-times.get-value(locuri))
+ | some(found-basedir) =>
+ c = cache-manager.cached-available(found-basedir, l.locator.uri(), l.locator.name(), max-dep-times.get-value(locuri))
saved-path = Filesystem.join(found-basedir, uri-to-path(locuri, l.locator.name()))
{static-path; module-path} = cases(CachedType) c.or-else(single-file):
| split =>
@@ -298,6 +336,7 @@ end
type CLIContext = {
current-load-path :: String,
cache-base-dir :: String,
+ compiled-read-only-dirs :: List,
url-file-mode :: CS.UrlFileMode
}
@@ -316,17 +355,18 @@ fun maybe-add-slash(s):
end
end
-fun locate-file(ctxt :: CLIContext, rel-path :: String):
+fun locate-file(cache-manager :: CacheManager, ctxt :: CLIContext, rel-path :: String):
clp = ctxt.current-load-path
real-path = get-real-path(clp, rel-path)
new-context = ctxt.{current-load-path: Filesystem.dirname(real-path)}
if Filesystem.exists(real-path):
- some(CL.located(get-file-locator(ctxt.cache-base-dir, real-path), new-context))
+ some(CL.located(get-file-locator(cache-manager, ctxt.cache-base-dir, real-path), new-context))
else:
none
end
end
-fun module-finder(ctxt :: CLIContext, dep :: CS.Dependency):
+fun module-finder-with(cache-manager :: CacheManager, ctxt :: CLIContext, dep :: CS.Dependency):
+ shadow locate-file = locate-file(cache-manager, _, _)
cases(CS.Dependency) dep:
| dependency(protocol, args) =>
if protocol == "file":
@@ -367,7 +407,7 @@ fun module-finder(ctxt :: CLIContext, dep :: CS.Dependency):
new-context = ctxt.{current-load-path: Filesystem.dirname(real-path)}
CL.located(locator, new-context)
else if protocol == "builtin-test":
- l = get-builtin-test-locator(ctxt.cache-base-dir, args.first)
+ l = get-builtin-test-locator(cache-manager, ctxt.cache-base-dir, args.first)
force-check-mode = l.{
method get-options(self, options):
options.{ checks: "all", type-check: false }
@@ -393,10 +433,11 @@ fun module-finder(ctxt :: CLIContext, dep :: CS.Dependency):
raise("Unknown import type: " + protocol)
end
| builtin(modname) =>
- CL.located(get-builtin-locator(ctxt.cache-base-dir, ctxt.compiled-read-only-dirs, modname), ctxt)
+ CL.located(file-get-builtin-locator(cache-manager, ctxt.cache-base-dir, ctxt.compiled-read-only-dirs, modname), ctxt)
end
end
+
default-start-context = {
current-load-path: Filesystem.resolve("./"),
cache-base-dir: Filesystem.resolve("./compiled"),
@@ -413,6 +454,7 @@ default-test-context = {
fun compile(path, options):
base-module = CS.dependency("file", [list: path])
+ shadow module-finder = module-finder-with(options.cache-manager, _, _)
base = module-finder({
current-load-path: Filesystem.resolve(options.base-dir),
cache-base-dir: options.compiled-cache,
@@ -443,6 +485,120 @@ fun propagate-exit(result) block:
end
end
+fun make-file-cache() -> CacheManager:
+ {
+ cached-available: file-cached-available,
+ get-cached: file-get-cached,
+ method get-cached-if-available(self, basedir, loc):
+ get-cached-if-available(self, basedir, loc)
+ end,
+ method get-loadable(self, basedir, read-only-basedirs, l, max-dep-times):
+ get-loadable-impl(self, basedir, read-only-basedirs, l, max-dep-times)
+ end,
+ method set-loadable(self, basedir, locator, loadable):
+ set-loadable(basedir, locator, loadable)
+ end,
+ method get-builtin-locator(self, basedir, read-only-basedirs, modname):
+ file-get-builtin-locator(self, basedir, read-only-basedirs, modname)
+ end,
+ method set-surface-ast(self, _, _): nothing end,
+ method get-surface-ast(self, _): none end,
+ method set-named-result(self, _, _): nothing end,
+ method get-named-result(self, _): none end,
+ }
+end
+
+fun make-in-memory-cache() -> CacheManager:
+ store = [SD.mutable-string-dict:]
+
+ fun get-entry(uri):
+ store.get-now(uri)
+ end
+
+ fun update-entry(uri, updater):
+ existing = cases(Option) store.get-now(uri):
+ | some(v) => v
+ | none => { surface-ast: none, named-result: none, loadable: none }
+ end
+ store.set-now(uri, updater(existing))
+ end
+
+ {
+ # TODO: falling back to file-based lookup for builtins is a workaround;
+ # ideally builtins would be loaded into the in-memory store directly
+ cached-available: lam(basedir, uri, name, mtime):
+ if store.has-key-now(uri): some(nothing)
+ else if string-index-of(uri, "builtin://") == 0:
+ file-cached-available(basedir, uri, name, mtime)
+ else: none
+ end
+ end,
+ get-cached: lam(basedir, uri, name, cache-type):
+ if store.has-key-now(uri):
+ mem-get-cached(store, basedir, uri, name, cache-type)
+ else if string-index-of(uri, "builtin://") == 0:
+ file-get-cached(basedir, uri, name, cache-type)
+ else:
+ raise("No in-memory cache entry for non-builtin module " + uri)
+ end
+ end,
+ method get-cached-if-available(self, _, loc):
+ if store.has-key-now(loc.uri()):
+ self.get-cached("", loc.uri(), loc.name(), nothing).{
+ method get-uncached(_): some(loc) end
+ }
+ else:
+ cases(Option) loc.get-uncached():
+ | some(shadow loc) => loc
+ | none => loc
+ end
+ end
+ end,
+ method get-loadable(self, basedir, read-only-basedirs, l, max-dep-times):
+ cases(Option) get-entry(l.locator.uri()):
+ | some(e) => e.loadable
+ | none =>
+ # Fall back to file-based lookup for builtins so they land in
+ # starter-modules and skip compile-module entirely.
+ if string-index-of(l.locator.uri(), "builtin://") == 0:
+ get-loadable-impl(self, basedir, read-only-basedirs, l, max-dep-times)
+ else:
+ none
+ end
+ end
+ end,
+ method set-loadable(self, _, locator, loadable) block:
+ update-entry(locator.uri(), lam(e): e.{loadable: some(loadable)} end)
+ locator.uri()
+ end,
+ # TODO: builtins should be loaded into the in-memory store rather than
+ # going through file-based lookup. Blocked on having a serialization
+ # format for Loadable that doesn't depend on the JS file infrastructure
+ # (builtin-raw-locator / -static.js / -module.js).
+ method get-builtin-locator(self, basedir, read-only-basedirs, modname):
+ file-get-builtin-locator(self, basedir, read-only-basedirs, modname)
+ end,
+ method set-surface-ast(self, uri, ast):
+ update-entry(uri, lam(e): e.{surface-ast: some(ast)} end)
+ end,
+ method get-surface-ast(self, uri):
+ cases(Option) get-entry(uri):
+ | some(e) => e.surface-ast
+ | none => none
+ end
+ end,
+ method set-named-result(self, uri, named-result):
+ update-entry(uri, lam(e): e.{named-result: some(named-result)} end)
+ end,
+ method get-named-result(self, uri):
+ cases(Option) get-entry(uri):
+ | some(e) => e.named-result
+ | none => none
+ end
+ end,
+ }
+end
+
fun run(path, options, subsequent-command-line-arguments):
stats = SD.make-mutable-string-dict()
maybe-program = build-program(path, options, stats)
@@ -461,10 +617,33 @@ fun run(path, options, subsequent-command-line-arguments):
end
end
+# TODO: this shares a lot of commonality with `build-program`.
+fun compile-for-query(options, program) block:
+ base-module = CS.dependency("file", [list: program])
+ shadow module-finder = module-finder-with(options.cache-manager, _, _)
+ base = module-finder({
+ current-load-path: Filesystem.resolve(options.base-dir),
+ cache-base-dir: options.compiled-cache,
+ compiled-read-only-dirs: options.compiled-read-only.map(Filesystem.resolve),
+ url-file-mode: options.url-file-mode
+ }, base-module)
+ wl = CL.compile-worklist(module-finder, base.locator, base.context)
+ # starter-modules = CL.modules-from-worklist(wl,
+ # lam(l, _): cache-manager.get-loadable("", empty, l, [SD.string-dict:]) end)
+ starter-modules = CL.modules-from-worklist(wl, options.cache-manager.get-loadable(options.compiled-cache, options.compiled-read-only.map(Filesystem.resolve), _, _))
+ CL.compile-program-with(wl, starter-modules, options)
+ base.locator.uri()
+end
+
fun build-program(path, options, stats) block:
doc: ```Returns the program as a JavaScript AST of module list and dependency map,
and its native dependencies as a list of strings```
+ # TODO: this should probably refactored into default opts
+ shadow options = options.{
+ cache-manager: if options.query: make-in-memory-cache() else: make-file-cache() end
+ }
+
print-progress-clearing = lam(s, to-clear):
when options.display-progress:
options.log(s, to-clear)
@@ -478,6 +657,7 @@ fun build-program(path, options, stats) block:
end
print-progress(str)
base-module = CS.dependency("file", [list: path])
+ shadow module-finder = module-finder-with(options.cache-manager, _, _)
base = module-finder({
current-load-path: Filesystem.resolve(options.base-dir),
cache-base-dir: options.compiled-cache,
@@ -490,12 +670,13 @@ fun build-program(path, options, stats) block:
max-dep-times = CL.dep-times-from-worklist(wl)
shadow wl = for map(located from wl):
- located.{ locator: get-cached-if-available-known-mtimes(options.compiled-cache, located.locator, max-dep-times) }
+ located.{ locator: get-cached-if-available-known-mtimes(options.cache-manager, options.compiled-cache, located.locator, max-dep-times) }
end
clear-and-print("Loading existing compiled modules...")
- starter-modules = CL.modules-from-worklist(wl, get-loadable(options.compiled-cache, options.compiled-read-only.map(Filesystem.resolve), _, _))
+ starter-modules = CL.modules-from-worklist(wl,
+ options.cache-manager.get-loadable(options.compiled-cache, options.compiled-read-only.map(Filesystem.resolve), _, _))
cached-modules = starter-modules.count-now()
total-modules = wl.length() - cached-modules
@@ -550,7 +731,7 @@ fun build-runnable-standalone(path, require-config-path, outfile, options) block
| none => nothing
| some(tb) =>
cases(JSON.JSON) tb:
- | j-arr(l) =>
+ | j-arr(l) =>
BL.set-typable-builtins(l.map(_.s))
| else => raise("Expected a list for typable-builtins, but got: " + to-repr(tb))
end
@@ -578,7 +759,7 @@ fun build-runnable-standalone(path, require-config-path, outfile, options) block
end
ans = make-standalone-res and html-res
-
+
when options.collect-times block:
standalone-end = time-now() - stats.get-value-now("standalone")
stats.set-now("standalone", [list: "Outputing JS: " + tostring(standalone-end) + "ms"])
@@ -608,3 +789,7 @@ fun build-require-standalone(path, options):
print(prog.to-ugly-source())
end
+
+
+# backwards compatibility
+module-finder = module-finder-with(make-file-cache(), _, _)
diff --git a/lang/src/arr/compiler/compile-lib.arr b/lang/src/arr/compiler/compile-lib.arr
index bcaea4b08..7b013c77c 100644
--- a/lang/src/arr/compiler/compile-lib.arr
+++ b/lang/src/arr/compiler/compile-lib.arr
@@ -24,6 +24,18 @@ import file("type-check.arr") as T
import file("desugar-check.arr") as CH
import file("resolve-scope.arr") as RS
+include from J:
+ data JExpr,
+ data JStmt,
+ data JCase,
+ data JBinop,
+ data JField,
+end
+
+include from E:
+ data Either
+end
+
data CompilationPhase:
| start(time :: Number)
| phase(name :: String, result :: Any, time :: Number, prev :: CompilationPhase)
@@ -42,57 +54,6 @@ sharing:
end
end
-j-fun = J.j-fun
-j-var = J.j-var
-j-id = J.j-id
-j-method = J.j-method
-j-block = J.j-block
-j-true = J.j-true
-j-false = J.j-false
-j-num = J.j-num
-j-str = J.j-str
-j-return = J.j-return
-j-assign = J.j-assign
-j-if = J.j-if
-j-if1 = J.j-if1
-j-new = J.j-new
-j-app = J.j-app
-j-list = J.j-list
-j-obj = J.j-obj
-j-dot = J.j-dot
-j-bracket = J.j-bracket
-j-field = J.j-field
-j-dot-assign = J.j-dot-assign
-j-bracket-assign = J.j-bracket-assign
-j-try-catch = J.j-try-catch
-j-throw = J.j-throw
-j-expr = J.j-expr
-j-binop = J.j-binop
-j-and = J.j-and
-j-lt = J.j-lt
-j-eq = J.j-eq
-j-neq = J.j-neq
-j-geq = J.j-geq
-j-unop = J.j-unop
-j-decr = J.j-decr
-j-incr = J.j-incr
-j-not = J.j-not
-j-instanceof = J.j-instanceof
-j-ternary = J.j-ternary
-j-null = J.j-null
-j-parens = J.j-parens
-j-switch = J.j-switch
-j-case = J.j-case
-j-default = J.j-default
-j-label = J.j-label
-j-break = J.j-break
-j-while = J.j-while
-j-for = J.j-for
-
-
-left = E.left
-right = E.right
-type Either = E.Either
mtd = [SD.string-dict:]
@@ -296,6 +257,7 @@ end
type CompiledProgram = {loadables :: List, modules :: SD.MutableStringDict}
+# TODO: use cache-manager here?
fun compile-program-with(worklist :: List, modules, options) -> CompiledProgram block:
cache = modules
loadables = for map(w from worklist):
@@ -363,6 +325,7 @@ fun compile-module(locator :: Locator, provide-map :: SD.StringDict, module
| pyret-ast(module-ast) =>
module-ast
end
+ options.cache-manager.set-surface-ast(locator.uri(), ast)
var ret = start(time-now())
fun add-phase(name, value) block:
if options.collect-all:
@@ -427,6 +390,7 @@ fun compile-module(locator :: Locator, provide-map :: SD.StringDict, module
var desugared = D.desugar(spied)
spied := nothing
named-result.env.bindings.merge-now(desugared.new-binds)
+ options.cache-manager.set-named-result(locator.uri(), named-result)
# ...in order to be checked for bad assignments here
any-errors := RS.check-unbound-ids-bad-assignments(desugared.ast, named-result, env)
add-phase("Fully desugared", desugared.ast)
@@ -464,17 +428,24 @@ fun compile-module(locator :: Locator, provide-map :: SD.StringDict, module
when not(options.type-check) block:
provides := AU.get-named-provides(named-result, locator.uri(), env)
end
- {final-provides; cr} = JSP.trace-make-compiled-pyret(add-phase, cleaned, env, named-result.env, provides, options)
- cleaned := nothing
- canonical-provides = AU.canonicalize-provides(final-provides, env)
- #|
- spy "compile-lib:canonicalize-provides":
- final-provides,
- canonical-provides
+ if options.query block:
+ {
+ module-as-string(provides, env, named-result.env, CS.ok(cleaned));
+ if options.collect-all or options.collect-times: ret.tolist() else: empty end
+ }
+ else:
+ {final-provides; cr} = JSP.trace-make-compiled-pyret(add-phase, cleaned, env, named-result.env, provides, options)
+ cleaned := nothing
+ canonical-provides = AU.canonicalize-provides(final-provides, env)
+ #|
+ spy "compile-lib:canonicalize-provides":
+ final-provides,
+ canonical-provides
+ end
+ |#
+ mod-result = module-as-string(canonical-provides, env, named-result.env, cr)
+ {mod-result; if options.collect-all or options.collect-times: ret.tolist() else: empty end}
end
- |#
- mod-result = module-as-string(canonical-provides, env, named-result.env, cr)
- {mod-result; if options.collect-all or options.collect-times: ret.tolist() else: empty end}
| err(_) =>
{ module-as-string(provides, env, CS.computed-none, type-checked);
if options.collect-all or options.collect-times:
@@ -569,11 +540,11 @@ fun make-standalone(wl, compiled, options):
check-str = if not(options.check-mode): "none" else: options.checks end
- runtime-options = J.j-obj(
+ runtime-options = j-obj(
[C.clist:
- J.j-field("checksFormat", j-str(options.checks-format)),
- J.j-field("checks", j-str(check-str)),
- J.j-field("disableAnnotationChecks",
+ j-field("checksFormat", j-str(options.checks-format)),
+ j-field("checks", j-str(check-str)),
+ j-field("disableAnnotationChecks",
if options.runtime-annotations:
j-false
else:
diff --git a/lang/src/arr/compiler/compile-structs.arr b/lang/src/arr/compiler/compile-structs.arr
index 563a1e010..84c024fc0 100644
--- a/lang/src/arr/compiler/compile-structs.arr
+++ b/lang/src/arr/compiler/compile-structs.arr
@@ -255,7 +255,7 @@ sharing:
end
| d-type(_, _) => remote-datatype
end
- some(T.t-name(T.module-uri(de.origin.uri-of-definition), A.s-type-global(de.typ.name), de.origin.local-bind-site, false))
+ some(T.t-name(T.module-uri(de.origin.uri-of-definition), A.s-type-global(A.dummy-loc, de.typ.name), de.origin.local-bind-site, false))
| none =>
cases(Option) provides-of-aliased.aliases.get(name):
| some(typ) =>
@@ -461,11 +461,11 @@ fun type-from-raw(uri, typ, tyvar-env :: SD.StringDict) block:
T.t-tuple(for map(e from typ.elts): tfr(e) end, l, false)
| t == "name" then:
if typ.origin.import-type == "$ELF":
- T.t-name(T.module-uri(uri), A.s-type-global(typ.name), l, false)
+ T.t-name(T.module-uri(uri), A.s-type-global(l, typ.name), l, false)
else if typ.origin.import-type == "uri":
- T.t-name(T.module-uri(typ.origin.uri), A.s-type-global(typ.name), l, false)
+ T.t-name(T.module-uri(typ.origin.uri), A.s-type-global(l, typ.name), l, false)
else:
- T.t-name(T.dependency(make-dep(typ.origin)), A.s-type-global(typ.name), l, false)
+ T.t-name(T.dependency(make-dep(typ.origin)), A.s-type-global(l, typ.name), l, false)
end
| t == "tyvar" then:
cases(Option) tyvar-env.get(typ.name):
@@ -474,7 +474,7 @@ fun type-from-raw(uri, typ, tyvar-env :: SD.StringDict) block:
end
| t == "forall" then:
new-env = for fold(new-env from tyvar-env, a from typ.args):
- tvn = A.global-names.make-atom(a)
+ tvn = A.global-names.make-atom(l, a)
new-env.set(a, tvn)
end
params = for SD.map-keys(k from new-env):
@@ -518,7 +518,7 @@ fun datatype-from-raw(uri, datatyp):
d-alias(origin, datatyp.name)
else if datatyp.tag == "data":
pdict = for fold(pdict from SD.make-string-dict(), a from datatyp.params):
- tvn = A.global-names.make-atom(a)
+ tvn = A.global-names.make-atom(l, a)
pdict.set(a, tvn)
end
params = for SD.map-keys(k from pdict):
@@ -2981,7 +2981,14 @@ default-compile-options = {
html-file: none,
deps-file: "build/bundled-node-deps.js",
standalone-file: "src/js/base/handalone.js",
- url-file-mode: all-remote
+ url-file-mode: all-remote,
+ query: false,
+ cache-manager: {
+ method set-surface-ast(self, _, _): nothing end,
+ method get-surface-ast(self, _): none end,
+ method set-named-result(self, _, _): nothing end,
+ method get-named-result(self, _): none end,
+ }
}
fun make-default-compile-options(this-pyret-dir):
@@ -3003,7 +3010,7 @@ t-within-num = t-arrow([list: t-number], t-arrow([list: t-number, t-number], t-b
t-within-any = t-arrow([list: t-number], t-arrow([list: t-top, t-top], t-boolean))
fun t-forall1(f):
- n = A.global-names.make-atom("a")
+ n = A.global-names.make-atom(A.dummy-loc, "a")
t-forall([list: t-var(n)], f(t-var(n)))
end
@@ -3315,16 +3322,16 @@ no-globals = globals([string-dict:], [string-dict:], [string-dict:])
reactor-optional-fields = [SD.string-dict:
- "last-image", {(l): A.a-name(l, A.s-type-global("Function"))},
- "on-tick", {(l): A.a-name(l, A.s-type-global("Function"))},
- "to-draw", {(l): A.a-name(l, A.s-type-global("Function"))},
- "on-key", {(l): A.a-name(l, A.s-type-global("Function"))},
- "on-raw-key", {(l): A.a-name(l, A.s-type-global("Function"))},
- "on-mouse", {(l): A.a-name(l, A.s-type-global("Function"))},
- "stop-when", {(l): A.a-name(l, A.s-type-global("Function"))},
- "seconds-per-tick", {(l): A.a-name(l, A.s-type-global("NumPositive"))},
- "title", {(l): A.a-name(l, A.s-type-global("String"))},
- "close-when-stop", {(l): A.a-name(l, A.s-type-global("Boolean"))}
+ "last-image", {(l): A.a-name(l, A.s-type-global(l, "Function"))},
+ "on-tick", {(l): A.a-name(l, A.s-type-global(l, "Function"))},
+ "to-draw", {(l): A.a-name(l, A.s-type-global(l, "Function"))},
+ "on-key", {(l): A.a-name(l, A.s-type-global(l, "Function"))},
+ "on-raw-key", {(l): A.a-name(l, A.s-type-global(l, "Function"))},
+ "on-mouse", {(l): A.a-name(l, A.s-type-global(l, "Function"))},
+ "stop-when", {(l): A.a-name(l, A.s-type-global(l, "Function"))},
+ "seconds-per-tick", {(l): A.a-name(l, A.s-type-global(l, "NumPositive"))},
+ "title", {(l): A.a-name(l, A.s-type-global(l, "String"))},
+ "close-when-stop", {(l): A.a-name(l, A.s-type-global(l, "Boolean"))}
]
reactor-fields = reactor-optional-fields.set("init", {(l): A.a-any(l)})
diff --git a/lang/src/arr/compiler/desugar-post-tc.arr b/lang/src/arr/compiler/desugar-post-tc.arr
index 071e19918..431e7f467 100644
--- a/lang/src/arr/compiler/desugar-post-tc.arr
+++ b/lang/src/arr/compiler/desugar-post-tc.arr
@@ -19,7 +19,7 @@ desugar-visitor = A.default-map-visitor.{
A.s-prim-app(l, "throwUnfinishedTemplate", [list: A.s-srcloc(l, l)], flat-prim-app)
end,
method s-cases-else(self, l, typ, val, branches, els, blocky):
- name = A.global-names.make-atom("cases")
+ name = A.global-names.make-atom(l, "cases")
typ-compiled = typ.visit(self)
val-exp = val.visit(self)
val-id = A.s-id(l, name)
@@ -28,7 +28,7 @@ desugar-visitor = A.default-map-visitor.{
els.visit(self), true), false)
end,
method s-cases(self, l, typ, val, branches, blocky):
- name = A.global-names.make-atom("cases")
+ name = A.global-names.make-atom(l, "cases")
typ-compiled = typ.visit(self)
val-exp = val.visit(self)
val-id = A.s-id(l, name)
@@ -37,7 +37,7 @@ desugar-visitor = A.default-map-visitor.{
A.s-block(l, [list: no-cases-exn(l, val-id)]), true), false)
end,
method s-check(self, l, name, body, keyword-check):
- A.s-id(l, A.s-global("nothing"))
+ A.s-id(l, A.s-global(l, "nothing"))
end
}
diff --git a/lang/src/arr/compiler/desugar.arr b/lang/src/arr/compiler/desugar.arr
index 19e088049..ac785f280 100644
--- a/lang/src/arr/compiler/desugar.arr
+++ b/lang/src/arr/compiler/desugar.arr
@@ -24,8 +24,8 @@ end
mt-d-env = d-env([tree-set: ], [tree-set: ], [tree-set: ])
var generated-binds = SD.make-mutable-string-dict()
-fun g(id): A.s-global(id) end
-fun gid(l, id): A.s-id(l, g(id)) end
+fun g(l, id): A.s-global(l, id) end
+fun gid(l, id): A.s-id(l, g(l, id)) end
fun bid(l, name): A.s-dot(l, A.s-prim-val(l, "builtins"), name) end
flat-prim-app = A.prim-app-info-c(false)
@@ -138,13 +138,13 @@ fun desugar(program :: A.Program):
end
fun mk-id-ann(loc, base, ann) block:
- a = names.make-atom(base)
+ a = names.make-atom(loc, base)
generated-binds.set-now(a.key(), C.value-bind(C.bo-local(loc, a), C.vb-let, a, ann))
{ id: a, id-b: A.s-bind(loc, false, a, ann), id-e: A.s-id(loc, a) }
end
fun mk-id-var-ann(loc, base, ann) block:
- a = names.make-atom(base)
+ a = names.make-atom(loc, base)
generated-binds.set-now(a.key(), C.value-bind(C.bo-local(loc, a), C.vb-var, a, ann))
{ id: a, id-b: A.s-bind(loc, false, a, ann), id-e: A.s-id-var(loc, a) }
end
@@ -278,7 +278,7 @@ fun ds-curry(l, f, args):
where:
d = A.dummy-loc
n = A.s-global
- id = lam(s): A.s-id(d, A.s-global(s)) end
+ id = lam(s): A.s-id(d, A.s-global(d, s)) end
under = A.s-id(d, A.s-underscore(d))
ds-ed = ds-curry(
d,
@@ -699,7 +699,7 @@ fun desugar-expr(expr :: A.Expr):
| s-table-extend-reducer(shadow l, name, reducer-expr, _, _) =>
reducer = reducers.get-value(name)
acc = accs.get-value(name)
- nothing-expr = A.s-id(l, A.s-global("nothing"))
+ nothing-expr = A.s-id(l, A.s-global(A.dummy-loc, "nothing"))
link(A.s-let-bind(l, reducer.id-b, desugar-expr(reducer-expr)),
link(A.s-var-bind(l, acc.id-b, nothing-expr),
reducers-acc))
@@ -784,7 +784,7 @@ fun desugar-expr(expr :: A.Expr):
flat-prim-app),
# Data
with-initialized-reducers(
- A.s-app(l, A.s-id(l, A.s-global("raw-array-map-1")), [list:
+ A.s-app(l, A.s-id(l, A.s-global(A.dummy-loc, "raw-array-map-1")), [list:
data-pop-mapfun(true),
data-pop-mapfun(false),
A.s-dot(A.dummy-loc, tbl.id-e, "_rows-raw-array")]))], flat-prim-app)]), true)
@@ -823,7 +823,7 @@ fun desugar-expr(expr :: A.Expr):
# Header
A.s-dot(A.dummy-loc, tbl.id-e, "_header-raw-array"),
# Data
- A.s-app(l, A.s-id(A.dummy-loc, g("raw-array-map")), [list:
+ A.s-app(l, A.s-id(A.dummy-loc, g(A.dummy-loc, "raw-array-map")), [list:
A.s-lam(A.dummy-loc, "", empty, [list: row.id-b], A.a-blank, "",
A.s-let-expr(A.dummy-loc,
link(
@@ -862,7 +862,7 @@ fun desugar-expr(expr :: A.Expr):
# Header
A.s-array(A.dummy-loc, columns.map(_.name)),
# Data
- A.s-app(l, A.s-id(A.dummy-loc, g("raw-array-map")), [list:
+ A.s-app(l, A.s-id(A.dummy-loc, g(A.dummy-loc, "raw-array-map")), [list:
A.s-lam(A.dummy-loc, "", empty, [list: row.id-b], A.a-blank, "",
A.s-array(A.dummy-loc,
columns.map(lam(c):
@@ -880,7 +880,7 @@ fun desugar-expr(expr :: A.Expr):
get-table-column(l, table.l, tbl.id-e, {l: column.l, name: A.s-str(A.dummy-loc,column.s)}))],
# Table Construction
A.s-prim-app(A.dummy-loc, "raw_array_to_list", [list:
- A.s-app(l, A.s-id(A.dummy-loc, g("raw-array-map")), [list:
+ A.s-app(l, A.s-id(A.dummy-loc, g(A.dummy-loc, "raw-array-map")), [list:
A.s-lam(A.dummy-loc, "", empty, [list: row.id-b], A.a-blank, "",
A.s-prim-app(A.dummy-loc, "raw_array_get", [list: row.id-e, col.id-e], flat-prim-app), none, none, true),
A.s-dot(A.dummy-loc, tbl.id-e, "_rows-raw-array")])], flat-prim-app), true)
@@ -894,7 +894,7 @@ fun desugar-expr(expr :: A.Expr):
| s-table-filter(l, column-binds, predicate) =>
row = mk-id(A.dummy-loc, "row")
tbl = mk-id(l, "table")
- pred-res = mk-id-ann(predicate.l, "pred", A.a-name(predicate.l, A.s-type-global("Boolean")))
+ pred-res = mk-id-ann(predicate.l, "pred", A.a-name(predicate.l, A.s-type-global(l, "Boolean")))
columns =
column-binds.binds.map(lam(c):
@@ -916,7 +916,7 @@ fun desugar-expr(expr :: A.Expr):
# Header
A.s-dot(A.dummy-loc, tbl.id-e, "_header-raw-array"),
# Data
- A.s-app(l, A.s-id(A.dummy-loc, g("raw-array-filter")), [list:
+ A.s-app(l, A.s-id(A.dummy-loc, g(A.dummy-loc, "raw-array-filter")), [list:
A.s-lam(A.dummy-loc, "", empty, [list: row.id-b], A.a-blank, "",
A.s-let-expr(A.dummy-loc,
columns.map(lam(column):
@@ -956,8 +956,8 @@ fun desugar-expr(expr :: A.Expr):
where:
d = A.dummy-loc
unglobal = A.default-map-visitor.{
- method s-global(self, s): A.s-name(d, s) end,
- method s-atom(self, base, serial): A.s-name(d, base) end
+ method s-global(self, l, s): A.s-name(l, s) end,
+ method s-atom(self, l, base, serial): A.s-name(l, base) end
}
p = lam(str): PP.surface-parse(str, "test").block.visit(A.dummy-loc-visitor) end
ds = lam(prog): desugar-expr(prog).visit(unglobal).visit(A.dummy-loc-visitor) end
diff --git a/lang/src/arr/compiler/query.arr b/lang/src/arr/compiler/query.arr
new file mode 100644
index 000000000..32f1082f2
--- /dev/null
+++ b/lang/src/arr/compiler/query.arr
@@ -0,0 +1,34 @@
+provide *
+
+# NOTE: New LSP/queries go here as functions that take the cache-manager and
+# query parameters, then return results. The cache-manager has surface-ast,
+# named-result, and loadable for every compiled module.
+
+import either as E
+import srcloc as S
+import file("ast-util.arr") as AU
+import file("compile-structs.arr") as CS
+
+fun jump-to-def(cache-manager, uri :: String, line :: Number, col :: Number) -> E.Either, {String; S.Srcloc}>:
+ cases(Option) cache-manager.get-surface-ast(uri):
+ | none => E.left([list:])
+ | some(ast) =>
+ cases(Option) AU.find-name-at(ast, line, col):
+ | none => E.left([list:])
+ | some(name) =>
+ cases(Option) cache-manager.get-named-result(uri):
+ | none => E.left([list:])
+ | some(named-result) =>
+ cases(Option) AU.find-name-key-by-srcloc(named-result.ast, name.l):
+ | none => E.left([list:])
+ | some(key) =>
+ cases(Option) named-result.env.bindings.get-now(key):
+ | none => E.left([list:])
+ | some(vb) =>
+ E.right({vb.origin.uri-of-definition; vb.origin.definition-bind-site})
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lang/src/arr/compiler/resolve-scope.arr b/lang/src/arr/compiler/resolve-scope.arr
index 77da061d2..5e076d493 100644
--- a/lang/src/arr/compiler/resolve-scope.arr
+++ b/lang/src/arr/compiler/resolve-scope.arr
@@ -45,7 +45,7 @@ fun desugar-toplevel-types(stmts :: List) -> List block:
| s-newtype(l, name, namet) =>
rev-type-binds := link(A.s-newtype-bind(l, name, namet), rev-type-binds)
| s-data(l, name, params, mixins, variants, shared, _check-loc, _check) =>
- namet = names.make-atom(name)
+ namet = names.make-atom(l, name)
rev-type-binds := link(A.s-newtype-bind(l, A.s-name(l, name), namet), rev-type-binds)
rev-stmts := link(A.s-data-expr(l, name, namet, params, mixins, variants, shared, _check-loc, _check), rev-stmts)
| else =>
@@ -263,7 +263,7 @@ fun simplify-let-bind(rebuild, l, bind, expr, lbs :: List) -> List
{bound-expr; binding} = cases(Option) as-name:
| none =>
- name = names.make-atom("tup")
+ name = names.make-atom(lb, "tup")
ann = A.a-tuple(lb, for map(f from fields):
cases(A.Bind) f:
| s-bind(_, _, _, a) => a
@@ -383,7 +383,7 @@ fun desugar-scope-block(stmts :: List, binding-group :: BindingGroup) ->
A.s-letrec-bind(v.l, b(v.l, checker-name), get-part(checker-name))
]
end
- blob-id = names.make-atom(name)
+ blob-id = names.make-atom(l, name)
data-expr = A.s-data-expr(l, name, namet, params, mixins, variants, shared, _check-loc, _check)
bind-data = A.s-letrec-bind(l, bn(l, blob-id), data-expr)
bind-data-pred = A.s-letrec-bind(l, b(l, A.make-checker-name(name)), A.s-dot(l, A.s-id-letrec(l, blob-id, true), name))
@@ -689,7 +689,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
datatypes = SD.make-mutable-string-dict()
fun make-anon-import-for(l, s, env, shadow bindings, b) block:
- atom = names.make-atom(s)
+ atom = names.make-atom(l, s)
bindings.set-now(atom.key(), b(atom))
{ atom: atom, env: env }
end
@@ -723,17 +723,17 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
# origin: env.get-value(s)
# end
# end
- atom = names.make-atom(s)
+ atom = names.make-atom(l, s)
binding = make-binding(atom)
bindings.set-now(atom.key(), binding)
{ atom: atom, env: env.set(s, binding) }
| s-underscore(l) =>
- atom = names.make-atom("$underscore")
+ atom = names.make-atom(l, "$underscore")
bindings.set-now(atom.key(), make-binding(atom))
{ atom: atom, env: env }
# NOTE(joe): an s-atom is pre-resolved to all its uses, so no need to add
# it or do any more work.
- | s-atom(_, _) =>
+ | s-atom(_, _, _) =>
binding = make-binding(name)
bindings.set-now(name.key(), binding)
{ atom: name, env: env }
@@ -769,13 +769,13 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
| some(shadow val-info) =>
cases(C.ValueExport) val-info block:
| v-var(_, t) =>
- b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-var, names.s-global(name), A.a-blank)
- bindings.set-now(names.s-global(name).key(), b)
+ b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-var, names.s-global(A.dummy-loc, name), A.a-blank)
+ bindings.set-now(names.s-global(A.dummy-loc, name).key(), b)
acc.set-now(name, b)
| else =>
# TODO(joe): Good place to add _location_ to valueexport to report errs better
- b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-let, names.s-global(name), A.a-blank)
- bindings.set-now(names.s-global(name).key(), b)
+ b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-let, names.s-global(A.dummy-loc, name), A.a-blank)
+ bindings.set-now(names.s-global(A.dummy-loc, name).key(), b)
acc.set-now(name, b)
end
end
@@ -788,8 +788,8 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
for SD.each-key(name from initial.globals.types) block:
origin = initial.globals.types.get-value(name)
type-info = initial.type-by-origin-value(origin)
- b = C.type-bind(C.bo-global(some(origin), origin.uri-of-definition, origin.original-name), C.tb-type-let, names.s-type-global(name), C.tb-typ(type-info))
- type-bindings.set-now(names.s-type-global(name).key(), b)
+ b = C.type-bind(C.bo-global(some(origin), origin.uri-of-definition, origin.original-name), C.tb-type-let, names.s-type-global(A.dummy-loc, name), C.tb-typ(type-info))
+ type-bindings.set-now(names.s-type-global(A.dummy-loc, name).key(), b)
acc.set-now(name, b)
end
acc.freeze()
@@ -800,8 +800,8 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
for SD.each-key(name from initial.globals.modules) block:
origin = initial.globals.modules.get-value(name)
mod-info = initial.provides-by-origin-value(origin)
- b = C.module-bind(C.bo-global(some(origin), origin.uri-of-definition, origin.original-name), names.s-module-global(name), mod-info.modules.get-value(name))
- module-bindings.set-now(names.s-module-global(name).key(), b)
+ b = C.module-bind(C.bo-global(some(origin), origin.uri-of-definition, origin.original-name), names.s-module-global(A.dummy-loc, name), mod-info.modules.get-value(name))
+ module-bindings.set-now(names.s-module-global(A.dummy-loc, name).key(), b)
acc.set-now(name, b)
end
acc.freeze()
@@ -837,9 +837,9 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
if env.has-key(s):
env.get-value(s).atom
else:
- names.s-global(s)
+ names.s-global(l2, s)
end
- | s-atom(_, _) => id
+ | s-atom(_, _, _) => id
| s-underscore(_) => id
| else => raise("Wasn't expecting a non-s-name in resolve-names id: " + torepr(id))
end
@@ -855,7 +855,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
| tb-type-var => A.a-type-var(l, name)
end
else:
- A.a-name(l, names.s-type-global(s))
+ A.a-name(l, names.s-type-global(l, s))
end
| else => A.a-name(l, id)
end
@@ -1054,14 +1054,14 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
new-header = A.s-import(l, file, atom-env-m.atom)
{ imp-e; imp-te; atom-env-m.env; link(new-header, imp-imps) }
| s-import-fields(l, fields, file) =>
- synth-include-name = names.make-atom(include-name())
+ synth-include-name = names.make-atom(l, include-name())
updated = add-import(acc, A.s-import(l, file, synth-include-name))
add-import(updated, A.s-include-from(l, [list: synth-include-name],
fields.map(lam(f):
A.s-include-name(l, A.s-module-ref(l, [list: f], none))
end)))
| s-include(l, file) =>
- synth-include-name = names.make-atom(include-name())
+ synth-include-name = names.make-atom(l, include-name())
updated = add-import(acc, A.s-import(l, file, synth-include-name))
add-import(updated, A.s-include-from(l, [list: synth-include-name],
[list:
@@ -1702,7 +1702,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
when self.type-env.has-key(s) block:
name-errors := link(C.type-id-used-as-value(id, self.type-env.get-value(s).origin), name-errors)
end
- A.s-id(l2, names.s-global(s))
+ A.s-id(l2, names.s-global(l2, s))
| some(vb) =>
cases (C.ValueBinder) vb.binder:
| vb-let => A.s-id(l2, vb.atom)
@@ -1710,7 +1710,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com
| vb-var => A.s-id-var(l2, vb.atom)
end
end
- | s-atom(_, _) => A.s-id(l, id)
+ | s-atom(_, _, _) => A.s-id(l, id)
| s-underscore(_) => A.s-id(l, id)
| else => raise("Wasn't expecting a non-s-name in resolve-names id: " + torepr(id))
end
diff --git a/lang/src/arr/compiler/server.arr b/lang/src/arr/compiler/server.arr
index ca3e15ed8..44bf031a1 100644
--- a/lang/src/arr/compiler/server.arr
+++ b/lang/src/arr/compiler/server.arr
@@ -1,4 +1,7 @@
-provide *
+provide:
+ serve
+end
+
import either as E
import json as J
@@ -8,100 +11,180 @@ import render-error-display as RED
import js-file("server") as S
import file("./cli-module-loader.arr") as CLI
import file("./compile-structs.arr") as CS
+import file("./compile-lib.arr") as CL
+import file("./query.arr") as Q
import file("locators/builtin.arr") as B
+fun get-compile-opts(options):
+ opts = CS.make-default-compile-options(options.get-value("this-pyret-dir"))
+ opts.{
+ base-dir: options.get("base-dir").or-else(opts.base-dir),
+ this-pyret-dir : options.get-value("this-pyret-dir"),
+ check-mode : not(options.get("no-check-mode").or-else(not(opts.check-mode))),
+ type-check : options.get("type-check").or-else(opts.type-check),
+ allow-shadowed : options.get("allow-shadowed").or-else(opts.allow-shadowed),
+ collect-all: options.get("collect-all").or-else(opts.collect-all),
+ ignore-unbound: options.get("ignore-unbound").or-else(opts.ignore-unbound),
+ proper-tail-calls: not(options.get("improper-tail-calls").or-else(not(opts.proper-tail-calls))),
+ compiled-cache: options.get("compiled-dir").or-else("./compiled"),
+ compiled-read-only: options.get("compiled-read-only").or-else(opts.compiled-read-only),
+ standalone-file: options.get("standalone-file").or-else(opts.standalone-file),
+ checks: options.get("checks").or-else(opts.checks),
+ checks-format: options.get("checks-format").or-else(opts.checks-format),
+ display-progress: options.get("display-progress").or-else(opts.display-progress),
+ log: options.get("log").or-else(opts.log),
+ log-error: options.get("log-error").or-else(opts.log-error),
+ deps-file: options.get("deps-file").or-else(opts.deps-file),
+ user-annotations: options.get("user-annotations").or-else(opts.user-annotations)
+ }
+end
+
+fun handle-compile-opts(pyret-dir, msg, send-message) block:
+ # print("Got message in pyret-land: " + msg)
+ opts = J.read-json(msg).native()
+ # print(torepr(opts))
+ # print("\n")
+ when opts.has-key("builtin-js-dir"):
+ if is-List(opts.get-value("builtin-js-dir")):
+ B.set-builtin-js-dirs(opts.get-value("builtin-js-dir"))
+ else:
+ B.set-builtin-js-dirs([list: opts.get-value("builtin-js-dir")])
+ end
+ end
+ when opts.has-key("builtin-arr-dir"):
+ if is-List(opts.get-value("builtin-arr-dir")):
+ B.set-builtin-arr-dirs(opts.get-value("builtin-arr-dir"))
+ else:
+ B.set-builtin-arr-dirs([list: opts.get-value("builtin-arr-dir")])
+ end
+ end
+ when opts.has-key("allow-builtin-overrides"):
+ B.set-allow-builtin-overrides(opts.get-value("allow-builtin-overrides"))
+ end
+ fun log(s, to-clear):
+ d = [SD.string-dict: "type", J.j-str("echo-log"), "contents", J.j-str(s)]
+ with-clear = cases(Option) to-clear:
+ | none => d.set("clear-first", J.j-bool(false))
+ | some(n) => d.set("clear-first", J.j-num(n))
+ end
+ send-message(J.j-obj(with-clear).serialize())
+ end
+ fun err(s):
+ d = [SD.string-dict: "type", J.j-str("echo-err"), "contents", J.j-str(s)]
+ send-message(J.j-obj(d).serialize())
+ end
+ opts-prime = opts
+ .set("log", log)
+ .set("log-error", err)
+ .set("this-pyret-dir", pyret-dir)
+ .set("compiled-read-only", link(P.resolve(P.join(pyret-dir, "lib-compiled")), empty))
+ opts-prim2 = if opts.has-key("perilous") and opts.get-value("perilous"):
+ opts-prime.set("user-annotations", false)
+ else:
+ opts-prime
+ end
+ opts-prim2
+ .set("require-config", opts.get("require-config").or-else(P.resolve(P.join(pyret-dir, "config.json"))))
+end
+
fun compile(options):
outfile = cases(Option) options.get("outfile"):
| some(v) => v
| none => options.get-value("program") + ".jarr"
end
- compile-opts = CS.make-default-compile-options(options.get-value("this-pyret-dir"))
+ opts = get-compile-opts(options).{
+ cache-manager: CLI.make-file-cache()
+ }
CLI.build-runnable-standalone(
options.get-value("program"),
options.get-value("require-config"),
outfile,
- compile-opts.{
- base-dir: options.get-value("base-dir"),
- this-pyret-dir : options.get-value("this-pyret-dir"),
- check-mode : not(options.get("no-check-mode").or-else(false)),
- type-check : options.get("type-check").or-else(false),
- allow-shadowed : options.get("allow-shadowed").or-else(false),
- collect-all: options.get("collect-all").or-else(false),
- ignore-unbound: options.get("ignore-unbound").or-else(false),
- proper-tail-calls: options.get("improper-tail-calls").or-else(true),
- compiled-cache: options.get("compiled-dir").or-else("./compiled"),
- compiled-read-only: options.get("compiled-read-only").or-else(empty),
- standalone-file: options.get("standalone-file").or-else(compile-opts.standalone-file),
- checks: options.get-value("checks"),
- checks-format: options.get-value("checks-format"),
- display-progress: options.get("display-progress").or-else(true),
- log: options.get("log").or-else(compile-opts.log),
- log-error: options.get("log-error").or-else(compile-opts.log-error),
- deps-file: options.get("deps-file").or-else(compile-opts.deps-file),
- user-annotations: options.get("user-annotations").or-else(compile-opts.user-annotations)
- })
+ opts
+ )
end
-fun serve(port, pyret-dir):
- S.make-server(port, lam(msg, send-message) block:
- # print("Got message in pyret-land: " + msg)
- opts = J.read-json(msg).native()
- # print(torepr(opts))
- # print("\n")
- when opts.has-key("builtin-js-dir"):
- if is-List(opts.get-value("builtin-js-dir")):
- B.set-builtin-js-dirs(opts.get-value("builtin-js-dir"))
- else:
- B.set-builtin-js-dirs([list: opts.get-value("builtin-js-dir")])
- end
- end
- when opts.has-key("builtin-arr-dir"):
- if is-List(opts.get-value("builtin-arr-dir")):
- B.set-builtin-arr-dirs(opts.get-value("builtin-arr-dir"))
- else:
- B.set-builtin-arr-dirs([list: opts.get-value("builtin-arr-dir")])
- end
- end
- when opts.has-key("allow-builtin-overrides"):
- B.set-allow-builtin-overrides(opts.get-value("allow-builtin-overrides"))
- end
- fun log(s, to-clear):
- d = [SD.string-dict: "type", J.j-str("echo-log"), "contents", J.j-str(s)]
- with-clear = cases(Option) to-clear:
- | none => d.set("clear-first", J.j-bool(false))
- | some(n) => d.set("clear-first", J.j-num(n))
- end
- send-message(J.j-obj(with-clear).serialize())
- end
- fun err(s):
- d = [SD.string-dict: "type", J.j-str("echo-err"), "contents", J.j-str(s)]
+fun query-compile(options, cache-manager):
+ program = options.get-value("program")
+ pyret-dir = options.get-value("this-pyret-dir")
+ compile-opts = get-compile-opts(options).{
+ query: true,
+ cache-manager: cache-manager
+ }
+ CLI.compile-for-query(compile-opts, program)
+end
+
+fun on-compile(pyret-dir, msg, send-message):
+ opts = handle-compile-opts(pyret-dir, msg, send-message)
+ result = run-task(lam(): compile(opts) end)
+ cases(E.Either) result block:
+ | right(exn) =>
+ err-str = RED.display-to-string(exn-unwrap(exn).render-reason(), tostring, empty)
+ opts.get-value("log-error")(err-str + "\n")
+ d = [SD.string-dict: "type", J.j-str("compile-failure")]
+ send-message(J.j-obj(d).serialize())
+ | left(val) =>
+ d = [SD.string-dict: "type", J.j-str("compile-success")]
send-message(J.j-obj(d).serialize())
+ nothing
+ end
+end
+
+fun on-query(pyret-dir, cache-manager, query, compile-opts, query-opts, send-message):
+ shadow compile-opts = handle-compile-opts(pyret-dir, compile-opts, send-message)
+ shadow query-opts = J.read-json(query-opts).native()
+ result = run-task(lam():
+ base-uri = query-compile(compile-opts, cache-manager)
+ # NOTE: To add a new query feature, add a case here and # a function in
+ # query.arr. The cache-manager has surface-ast, named-result, and loadable
+ # for every compiled module.
+ ask:
+ | query == "jump-to-def" then:
+ line = query-opts.get-value("line")
+ col = query-opts.get-value("col")
+ Q.jump-to-def(cache-manager, base-uri, line, col)
end
- with-logger = opts.set("log", log)
- with-error = with-logger.set("log-error", err)
- with-pyret-dir = with-error.set("this-pyret-dir", pyret-dir)
- with-compiled-read-only-dirs = with-pyret-dir.set("compiled-read-only",
- link(P.resolve(P.join(pyret-dir, "lib-compiled")), empty))
- with-perilous = if opts.has-key("perilous") and opts.get-value("perilous"):
- with-compiled-read-only-dirs.set("user-annotations", false)
- else:
- with-compiled-read-only-dirs
+ end)
+
+ err = compile-opts.get-value("log-error")
+
+ cases(E.Either) result block:
+ | right(exn) =>
+ err-str = RED.display-to-string(exn-unwrap(exn).render-reason(), tostring, empty)
+ err(err-str + "\n")
+ d = [SD.string-dict: "type", J.j-str(query + "-failure")]
+ send-message(J.j-obj(d).serialize())
+ | left(info-result) =>
+ # NOTE: Each query is responsible for its own response serialization
+ # here, since response shapes differ per feature.
+ # TODO: should probably refactor this
+ ask:
+ | query == "jump-to-def" then:
+ cases(E.Either) info-result block:
+ | left(errors) =>
+ err("jump-to-def: no result (errors: " + torepr(errors) + ")\n")
+ d = [SD.string-dict: "type", J.j-str("jump-to-def-failure")]
+ send-message(J.j-obj(d).serialize())
+ | right(loc-info) =>
+ srcloc = loc-info.{1}
+ d = [SD.string-dict:
+ "type", J.j-str("jump-to-def-success"),
+ "uri", J.j-str(loc-info.{0}),
+ "start-line", J.j-num(srcloc.start-line),
+ "start-column", J.j-num(srcloc.start-column),
+ "end-line", J.j-num(srcloc.end-line),
+ "end-column", J.j-num(srcloc.end-column)
+ ]
+ send-message(J.j-obj(d).serialize())
+ end
end
- with-require-config = with-perilous.set("require-config",
- opts.get("require-config").or-else(P.resolve(P.join(pyret-dir, "config.json"))))
- result = run-task(lam():
- compile(with-require-config)
- end)
- cases(E.Either) result block:
- | right(exn) =>
- err-str = RED.display-to-string(exn-unwrap(exn).render-reason(), tostring, empty)
- err(err-str + "\n")
- d = [SD.string-dict: "type", J.j-str("compile-failure")]
- send-message(J.j-obj(d).serialize())
- | left(val) =>
- d = [SD.string-dict: "type", J.j-str("compile-success")]
- send-message(J.j-obj(d).serialize())
- nothing
end
- end)
+end
+
+fun serve(port, pyret-dir):
+ cache-manager = CLI.make-in-memory-cache()
+ S.make-server(
+ port,
+ on-compile(pyret-dir, _, _),
+ on-query(pyret-dir, cache-manager, _, _, _, _)
+ )
end
diff --git a/lang/src/arr/compiler/server.js b/lang/src/arr/compiler/server.js
index 6845baa7b..eb2d58ec9 100644
--- a/lang/src/arr/compiler/server.js
+++ b/lang/src/arr/compiler/server.js
@@ -1,22 +1,31 @@
-{
+/** @satisfies {PyretModule} */
+({
provides: {
values: {
"make-server": "tany"
}
},
requires: [],
- nativeRequires: ['http', 'ws'],
- theModule: function(runtime, _, uri, http, ws) {
+ nativeRequires: ['http', 'ws', 'fs'],
+ theModule: function(
+ runtime,
+ _,
+ uri,
+ /** @type {import('node:http')} */ http,
+ ws,
+ /** @type {import('node:fs')} */ fs,
+ ) {
+ /** @import ws from "ws" */
const INFO = 4;
const LOG = 3;
const WARN = 2;
const ERROR = 1;
const SILENT = 0;
- var LOG_LEVEL = ERROR;
+ let LOG_LEVEL = ERROR;
- function makeLogger(level) {
- return function(...args) {
+ function makeLogger(/** @type {number} */ level) {
+ return function(/** @type {...any} */...args) {
if(LOG_LEVEL >= level) {
console.log.apply(console, ["[server] ", new Date()].concat(args));
}
@@ -30,13 +39,57 @@
// Port is a string for a file path, like /tmp/some-sock,
- const makeServer = function(port, onmessage) {
-
- var runQueue = [];
+ const makeServer = function(
+ /** @type {string} */ port,
+ /** @type {PyretFunction} */ onCompile,
+ /** @type {PyretFunction} */ onQuery,
+ ) {
+ /**
+ * @typedef {{respondForPy: PyretFunction, respondJSON: function, closeConn: function}} QueueCommon
+ * @typedef {{command: 'compile', options: unknown}} CompileQueueItem
+ * @typedef {{command: 'query', query: string, compileOptions: unknown, queryOptions: unknown}} QueryQueueItem
+ * @typedef {QueueCommon & (CompileQueueItem | QueryQueueItem)} QueueItem
+ */
+
+ /** @type {QueueItem[]} */
+ let runQueue = [];
+ let running = false;
+
+ function tryQueue() {
+ if (running || runQueue.length === 0) { return; }
+ running = true;
+ const current = /** @type {QueueItem} */ (runQueue.shift());
+ info(`Running queued command: ${current.command}, queue length now ${runQueue.length}`);
+ runtime.runThunk(function() {
+ const { respondForPy: respond } = current;
+ switch (current.command) {
+ case 'compile':
+ return onCompile.app(current.options, respond);
+ case 'query':
+ return onQuery.app(current.query, current.compileOptions, current.queryOptions, current.respondForPy);
+ }
+ }, function(result) {
+ if (runtime.isFailureResult(result)) {
+ const exn = result.exn;
+ const inner = exn && exn.exn !== undefined ? exn.exn : exn;
+ error("Failed (raw exn):", inner);
+ error("Failed (stack):", exn && exn.stack);
+ error("Failed (pyretStack):", exn && exn.pyretStack);
+ const exnStr = inner !== undefined
+ ? (typeof inner === 'object' ? JSON.stringify(inner) : String(inner))
+ : String(exn);
+ current.respondJSON({type: "echo-err", contents: "Internal error: " + exnStr});
+ if (exn && exn.stack) { current.respondJSON({type: "echo-err", contents: exn.stack}); }
+ }
+ current.closeConn();
+ running = false;
+ tryQueue();
+ });
+ }
//info("Starting up server");
return runtime.pauseStack(function(restarter) {
- var server = http.createServer(function(request, response) {
+ const server = http.createServer(function(request, response) {
response.writeHead(404);
response.end();
});
@@ -58,76 +111,80 @@
}
});
- var wsServer = new ws.Server({
+ /** @type {ws.Server} */
+ const wsServer = new ws.Server({
server: server
});
wsServer.on('connection', function(connection) {
-
- function respond(jsonData) {
+ function respond(/** @type {string} */ jsonData) {
info("Sending: ", jsonData);
connection.send(jsonData);
return runtime.nothing;
}
- function respondJSON(json) { return respond(JSON.stringify(json)); }
+ function respondJSON(/** @type {*} */ json) { return respond(JSON.stringify(json)); }
const respondForPy = runtime.makeFunction(respond, "respond");
+ function closeConn() { connection.close(); }
- function tryQueue() {
- info("Trying run queue, length is ", runQueue.length);
- if(runQueue.length > 0) {
- var current = runQueue.pop();
- runtime.runThunk(function() {
- return onmessage.app(current, respondForPy);
- }, function(result) {
- if(runtime.isFailureResult(result)) {
- error("Failed: ", result.exn.exn, result.exn.stack, result.exn.pyretStack);
- respondJSON({type: "echo-err", contents: "There was an internal error, please report this as a bug"});
- respondJSON({type: "echo-err", contents: String(result.exn.exn) });
- connection.close();
- // restarter.error(result.exn);
- }
- else {
- connection.close();
- // info("Success: ", result);
- }
- tryQueue();
- });
- }
- }
-
- info((new Date()) + ' Connection accepted.');
+ info(`${new Date()} Connection accepted.`);
- connection.on('message', function(message) {
- info('Received Message: ' + message);
+ /**
+ * @typedef {{command: 'stop'} |
+ * {command: 'shutdown'} |
+ * {command: 'compile', compileOptions: unknown} |
+ * {command: 'query', query: string, compileOptions: unknown, queryOptions: unknown}}
+ * ServerMessage
+ */
- var parsed = JSON.parse(message);
+ connection.on('message', function(/** @type {string} */ message) {
+ info(`Received Message: ${message}`);
- if(parsed.command === "stop") {
- runtime.schedulePause(function(restarter) {
- restarter.break();
- });
- tryQueue();
- }
-
- if(parsed.command === "shutdown") {
- runtime.breakAll();
- info("Exiting due to shutdown request");
- process.exit(0);
- }
+ /** @type {ServerMessage} */
+ const parsed = JSON.parse(message);
- if(parsed.command === "compile") {
- runQueue.push(parsed.compileOptions);
- tryQueue();
+ switch (parsed.command) {
+ case "stop": {
+ runtime.schedulePause(function(restarter) {
+ restarter.break();
+ });
+ tryQueue();
+ break;
+ }
+ case "shutdown": {
+ runtime.breakAll();
+ info("Exiting due to shutdown request");
+ process.exit(0);
+ break;
+ }
+ case "compile": {
+ runQueue.push({
+ command: parsed.command,
+ options: parsed.compileOptions,
+ respondForPy, respondJSON, closeConn
+ });
+ tryQueue();
+ break;
+ };
+ case "query": {
+ runQueue.push({
+ command: parsed.command,
+ query: parsed.query,
+ compileOptions: parsed.compileOptions,
+ queryOptions: parsed.queryOptions,
+ respondForPy, respondJSON, closeConn
+ });
+ tryQueue();
+ break;
+ }
}
-
});
connection.on('close', function(reasonCode, description) {
// info((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
});
});
-
+
info("Server startup successful");
if(process.send) {
process.send({type: 'success'});
@@ -144,4 +201,4 @@
"make-server": runtime.makeFunction(makeServer, "make-server")
}, {});
}
-}
+})
diff --git a/lang/src/arr/compiler/type-check.arr b/lang/src/arr/compiler/type-check.arr
index b1274bee2..bcf5f1f02 100644
--- a/lang/src/arr/compiler/type-check.arr
+++ b/lang/src/arr/compiler/type-check.arr
@@ -169,7 +169,7 @@ fun type-check(program :: A.Program, compile-env :: C.CompileEnvironment, post-c
globvs = compile-env.globals.values
globts = compile-env.globals.types
shadow context = globvs.fold-keys(lam(g, shadow context):
- if context.global-types.has-key(A.s-global(g).key()):
+ if context.global-types.has-key(A.s-global(A.dummy-loc, g).key()):
context
else:
# TODO(joe): type-check vars by making them refs
@@ -177,12 +177,12 @@ fun type-check(program :: A.Program, compile-env :: C.CompileEnvironment, post-c
if (g == "_"):
context
else:
- context.set-global-types(context.global-types.set(A.s-global(g).key(), compile-env.global-value-value(g).t))
+ context.set-global-types(context.global-types.set(A.s-global(A.dummy-loc, g).key(), compile-env.global-value-value(g).t))
end
end
end, context)
shadow context = globts.fold-keys(lam(g, shadow context):
- if context.aliases.has-key(A.s-type-global(g).key()):
+ if context.aliases.has-key(A.s-type-global(A.dummy-loc, g).key()):
context
else:
origin = globts.get-value(g)
@@ -195,11 +195,11 @@ fun type-check(program :: A.Program, compile-env :: C.CompileEnvironment, post-c
| none =>
cases(Option) provs.data-definitions.get(g):
| none => raise("Key " + g + " not found in " + torepr(provs))
- | some(v) => t-name(TS.builtin-uri, A.s-type-global(g), SL.builtin("global"), false)
+ | some(v) => t-name(TS.builtin-uri, A.s-type-global(A.dummy-loc, g), SL.builtin("global"), false)
end
| some(v) => v
end
- context.set-aliases(context.aliases.set(A.s-type-global(g).key(), t))
+ context.set-aliases(context.aliases.set(A.s-type-global(A.dummy-loc, g).key(), t))
| none =>
raise("Could not find module " + torepr(origin.uri-of-definition) + " in " + torepr(compile-env.all-modules) + " in " + torepr(program.l))
end
@@ -1570,15 +1570,16 @@ fun synthesis-app-fun(app-loc :: Loc, _fun :: Expr, args :: List, context
end
cases(Expr) _fun:
| s-id(fun-loc, id) =>
+ d = A.dummy-loc
ask:
- | id == A.s-global("_plus") then: choose-type("_plus")
- | id == A.s-global("_times") then: choose-type("_times")
- | id == A.s-global("_divide") then: choose-type("_divide")
- | id == A.s-global("_minus") then: choose-type("_minus")
- | id == A.s-global("_lessthan") then: choose-type("_lessthan")
- | id == A.s-global("_lessequal") then: choose-type("_lessequal")
- | id == A.s-global("_greaterthan") then: choose-type("_greaterthan")
- | id == A.s-global("_greaterequal") then: choose-type("_greaterequal")
+ | id == A.s-global(d, "_plus") then: choose-type("_plus")
+ | id == A.s-global(d, "_times") then: choose-type("_times")
+ | id == A.s-global(d, "_divide") then: choose-type("_divide")
+ | id == A.s-global(d, "_minus") then: choose-type("_minus")
+ | id == A.s-global(d, "_lessthan") then: choose-type("_lessthan")
+ | id == A.s-global(d, "_lessequal") then: choose-type("_lessequal")
+ | id == A.s-global(d, "_greaterthan") then: choose-type("_greaterthan")
+ | id == A.s-global(d, "_greaterequal") then: choose-type("_greaterequal")
| otherwise:
synthesis(_fun, false, context).fold-bind(lam(_, new-type, shadow context):
fold-result(new-type, context)
@@ -1815,7 +1816,7 @@ fun lam-to-type(coll :: SD.StringDict, l :: Loc, params :: List, a
shadow context = context.add-variable(ret-type)
fold-arg-types = map-fold-result(lam(arg, shadow context):
arg-is-underscore = cases(A.Name) arg.id:
- | s-atom(base, _) => base == "$underscore"
+ | s-atom(_, base, _) => base == "$underscore"
| else => false
end
arg-type = coll.get-value(arg.id.key())
@@ -2400,7 +2401,7 @@ fun ignore-checker(l :: Loc, binds :: List, body :: Expr, blocky, con
if binds.length() == 1:
binding = binds.get(0)
cases(A.Name) binding.b.id:
- | s-atom(base, _) =>
+ | s-atom(_, base, _) =>
if string-length(base) >= 19:
name = string-substring(base, 0, 19)
if name == "result-after-checks":
diff --git a/lang/src/arr/compiler/type-defaults.arr b/lang/src/arr/compiler/type-defaults.arr
index 81f880635..c5aae759b 100644
--- a/lang/src/arr/compiler/type-defaults.arr
+++ b/lang/src/arr/compiler/type-defaults.arr
@@ -44,31 +44,31 @@ string-dict = SD.string-dict
s-atom = A.s-atom
-tva = t-var(A.global-names.make-atom("A"))
-tvb = t-var(A.global-names.make-atom("B"))
-tvc = t-var(A.global-names.make-atom("C"))
-tvd = t-var(A.global-names.make-atom("D"))
-tve = t-var(A.global-names.make-atom("E"))
-tvf = t-var(A.global-names.make-atom("F"))
-tvg = t-var(A.global-names.make-atom("G"))
-tvh = t-var(A.global-names.make-atom("H"))
-
-t-image = t-name(module-uri("builtin://image-lib"), A.s-type-global("Image"))
-t-option = t-name(module-uri("builtin://option"), A.s-type-global("Option"))
-t-reactor = t-name(module-uri("builtin://reactors"), A.s-type-global("Reactor"))
-t-equality-result = t-name(module-uri("builtin://equality"), A.s-type-global("EqualityResult"))
-t-value-skeleton = t-name(module-uri("builtin://valueskeleton"), A.s-type-global("ValueSkeleton"))
-t-list = t-name(module-uri("builtin://lists"), A.s-type-global("List"))
-t-big-array = t-name(module-uri("builtin://arrays"), A.s-type-global("Array"))
-t-set = t-name(module-uri("builtin://sets"), A.s-type-global("Set"))
-t-avl = t-name(module-uri("builtin://sets"), A.s-type-global("AVLTree"))
-t-runtime-error = t-name(module-uri("builtin://error"), A.s-type-global("RuntimeError"))
-t-parse-error = t-name(module-uri("builtin://error"), A.s-type-global("ParseError"))
-t-either = t-name(module-uri("builtin://either"), A.s-type-global("Either"))
-t-s-exp = t-name(module-uri("builtin://s-exp-structs"), A.s-type-global("S-Exp"))
-t-pick = t-name(module-uri("builtin://pick"), A.s-type-global("Pick"))
-t-json = t-name(module-uri("builtin://json-structs"), A.s-type-global("JSON"))
-t-string-dict = t-name(module-uri("builtin://string-dict"), A.s-type-global("StringDict"))
+tva = t-var(A.global-names.make-atom(A.dummy-loc, "A"))
+tvb = t-var(A.global-names.make-atom(A.dummy-loc, "B"))
+tvc = t-var(A.global-names.make-atom(A.dummy-loc, "C"))
+tvd = t-var(A.global-names.make-atom(A.dummy-loc, "D"))
+tve = t-var(A.global-names.make-atom(A.dummy-loc, "E"))
+tvf = t-var(A.global-names.make-atom(A.dummy-loc, "F"))
+tvg = t-var(A.global-names.make-atom(A.dummy-loc, "G"))
+tvh = t-var(A.global-names.make-atom(A.dummy-loc, "H"))
+
+t-image = t-name(module-uri("builtin://image-lib"), A.s-type-global(A.dummy-loc, "Image"))
+t-option = t-name(module-uri("builtin://option"), A.s-type-global(A.dummy-loc, "Option"))
+t-reactor = t-name(module-uri("builtin://reactors"), A.s-type-global(A.dummy-loc, "Reactor"))
+t-equality-result = t-name(module-uri("builtin://equality"), A.s-type-global(A.dummy-loc, "EqualityResult"))
+t-value-skeleton = t-name(module-uri("builtin://valueskeleton"), A.s-type-global(A.dummy-loc, "ValueSkeleton"))
+t-list = t-name(module-uri("builtin://lists"), A.s-type-global(A.dummy-loc, "List"))
+t-big-array = t-name(module-uri("builtin://arrays"), A.s-type-global(A.dummy-loc, "Array"))
+t-set = t-name(module-uri("builtin://sets"), A.s-type-global(A.dummy-loc, "Set"))
+t-avl = t-name(module-uri("builtin://sets"), A.s-type-global(A.dummy-loc, "AVLTree"))
+t-runtime-error = t-name(module-uri("builtin://error"), A.s-type-global(A.dummy-loc, "RuntimeError"))
+t-parse-error = t-name(module-uri("builtin://error"), A.s-type-global(A.dummy-loc, "ParseError"))
+t-either = t-name(module-uri("builtin://either"), A.s-type-global(A.dummy-loc, "Either"))
+t-s-exp = t-name(module-uri("builtin://s-exp-structs"), A.s-type-global(A.dummy-loc, "S-Exp"))
+t-pick = t-name(module-uri("builtin://pick"), A.s-type-global(A.dummy-loc, "Pick"))
+t-json = t-name(module-uri("builtin://json-structs"), A.s-type-global(A.dummy-loc, "JSON"))
+t-string-dict = t-name(module-uri("builtin://string-dict"), A.s-type-global(A.dummy-loc, "StringDict"))
fun t-either-app(typ-1 :: Type, typ-2 :: Type):
t-app(t-either, [list: typ-1, typ-2])
@@ -265,7 +265,7 @@ module-const-pick = t-module("builtin://pick",
"_match", t-top
])),
SD.make-string-dict()
- .set("Pick", t-name(module-uri("builtin://pick"), A.s-type-global("Pick")))
+ .set("Pick", t-name(module-uri("builtin://pick"), A.s-type-global(A.dummy-loc, "Pick")))
)
set-constructor =
diff --git a/lang/src/arr/compiler/type-structs.arr b/lang/src/arr/compiler/type-structs.arr
index 2216b3a07..1591e7213 100644
--- a/lang/src/arr/compiler/type-structs.arr
+++ b/lang/src/arr/compiler/type-structs.arr
@@ -548,7 +548,7 @@ sharing:
+ ")"
| t-var(id, _, _) =>
cases(Name) id:
- | s-atom(base, _) =>
+ | s-atom(_, base, _) =>
if base == "%tyvar":
cases(Option) tyvar-mapping.get-now(typ.key()) block:
| some(name) => name
@@ -592,29 +592,29 @@ check:
t-forall([list: a-name, b-name], b-name, A.dummy-loc, false).to-string() is "forall a, b . b"
t-ref(a-name, A.dummy-loc, false).to-string() is "ref a"
t-data-refinement(a-name, "a", A.dummy-loc, false).to-string() is "(a % is-a)"
- t-var(A.s-atom("%tyvar", 0), A.dummy-loc, false).to-string() is "A"
+ t-var(A.s-atom(A.dummy-loc, "%tyvar", 0), A.dummy-loc, false).to-string() is "A"
t-var(A.s-name(A.dummy-loc, "a"), A.dummy-loc, false).to-string() is "a"
t-existential(A.s-name(A.dummy-loc, "a"), A.dummy-loc, false).to-string() is "?-1"
end
fun new-existential(l :: Loc, inferred :: Boolean):
- t-existential(A.global-names.make-atom("%exists"), l, inferred)
+ t-existential(A.global-names.make-atom(l, "%exists"), l, inferred)
end
fun new-type-var(l :: Loc):
- t-var(A.global-names.make-atom("%tyvar"), l, false)
+ t-var(A.global-names.make-atom(l, "%tyvar"), l, false)
end
# TODO(MATT): which of these should be kept
builtin-uri = module-uri("builtin://global")
-t-array-name = t-name(builtin-uri, A.s-type-global("RawArray"), A.dummy-loc, false)
+t-array-name = t-name(builtin-uri, A.s-type-global(A.dummy-loc, "RawArray"), A.dummy-loc, false)
-t-number = lam(l): t-name(builtin-uri, A.s-type-global("Number"), l, false) end
-t-string = lam(l): t-name(builtin-uri, A.s-type-global("String"), l, false) end
-t-boolean = lam(l): t-name(builtin-uri, A.s-type-global("Boolean"), l, false) end
-t-nothing = lam(l): t-name(builtin-uri, A.s-type-global("Nothing"), l, false) end
-t-srcloc = lam(l): t-name(builtin-uri, A.s-type-global("Loc"), l, false) end
+t-number = lam(l): t-name(builtin-uri, A.s-type-global(l, "Number"), l, false) end
+t-string = lam(l): t-name(builtin-uri, A.s-type-global(l, "String"), l, false) end
+t-boolean = lam(l): t-name(builtin-uri, A.s-type-global(l, "Boolean"), l, false) end
+t-nothing = lam(l): t-name(builtin-uri, A.s-type-global(l, "Nothing"), l, false) end
+t-srcloc = lam(l): t-name(builtin-uri, A.s-type-global(l, "Loc"), l, false) end
t-array = lam(v, l): t-app(t-array-name.set-loc(l), [list: v], l, false) end
-t-option = lam(v, l): t-app(t-name(module-uri("builtin://option"), A.s-type-global("Option"), l, false), [list: v], l, false) end
-t-table = lam(l): t-name(builtin-uri, A.s-type-global("Table"), l, false) end
+t-option = lam(v, l): t-app(t-name(module-uri("builtin://option"), A.s-type-global(l, "Option"), l, false), [list: v], l, false) end
+t-table = lam(l): t-name(builtin-uri, A.s-type-global(l, "Table"), l, false) end
diff --git a/lang/src/arr/trove/ast.arr b/lang/src/arr/trove/ast.arr
index 5910bd7bc..7a34038ed 100644
--- a/lang/src/arr/trove/ast.arr
+++ b/lang/src/arr/trove/ast.arr
@@ -125,7 +125,7 @@ data Name:
method toname(self): self.s end,
method key(self): "name#" + self.s end
- | s-global(s :: String) with:
+ | s-global(l :: Loc, s :: String) with:
method to-compiled-source(self): PP.str(self.to-compiled()) end,
method to-compiled(self): self.s end,
method tosource(self): PP.str(self.s) end,
@@ -133,7 +133,7 @@ data Name:
method toname(self): self.s end,
method key(self): "global#" + self.s end
- | s-module-global(s :: String) with:
+ | s-module-global(l :: Loc, s :: String) with:
method to-compiled-source(self): PP.str(self.to-compiled()) end,
method to-compiled(self): "$module$" + self.s end,
method tosource(self): PP.str(self.s) end,
@@ -141,7 +141,7 @@ data Name:
method toname(self): self.s end,
method key(self): "mglobal#" + self.s end
- | s-type-global(s :: String) with:
+ | s-type-global(l :: Loc, s :: String) with:
method to-compiled-source(self): PP.str(self.to-compiled()) end,
method to-compiled(self): "$type$" + self.s end,
method tosource(self): PP.str(self.s) end,
@@ -149,7 +149,7 @@ data Name:
method toname(self): self.s end,
method key(self): "tglobal#" + self.s end
- | s-atom(base :: String, serial :: Number) with:
+ | s-atom(l :: Loc, base :: String, serial :: Number) with:
method to-compiled-source(self): PP.str(self.to-compiled()) end,
method to-compiled(self): self.base + tostring(self.serial) end,
method tosource(self): PP.str(self.toname()) end,
@@ -169,9 +169,9 @@ end
fun MakeName(start):
var count = start
- fun atom(base :: String) block:
+ fun atom(l :: Loc, base :: String) block:
count := 1 + count
- s-atom(base, count)
+ s-atom(l, base, count)
end
{
reset: lam(): count := start end,
@@ -578,6 +578,7 @@ sharing:
end
end
+# ZACK: no loc
data DefinedModule:
| s-defined-module(name :: String, value :: Name, uri :: String) with:
method label(self): "s-defined-module" end,
@@ -590,6 +591,7 @@ sharing:
end
end
+# ZACK: no loc
data DefinedValue:
| s-defined-value(name :: String, value :: Expr) with:
method label(self): "s-defined-value" end,
@@ -1371,6 +1373,7 @@ sharing:
end
end
+# ZACK: doesn't have loc
data ConstructModifier:
| s-construct-normal with:
method label(self): "s-construct-normal" end,
@@ -1490,6 +1493,7 @@ sharing:
end
end
+# ZACK: no loc
data ColumnSortOrder:
| ASCENDING with:
method tosource(self):
@@ -1570,6 +1574,7 @@ sharing:
end
end
+# ZACK: no loc
data VariantMemberType:
| s-normal with:
method label(self): "s-normal" end,
@@ -1669,6 +1674,7 @@ sharing:
end
end
+# ZACK: NO LOC
data CasesBindType:
| s-cases-bind-ref with:
method label(self): "s-cases-bind-ref" end,
@@ -1777,6 +1783,7 @@ sharing:
end
end
+# a-blank HAS NO LOC, nor a-checked
data Ann:
| a-blank with:
method label(self): "a-blank" end,
@@ -1898,20 +1905,20 @@ default-map-visitor = {
s-name(l, s)
end,
- method s-type-global(self, s):
- s-type-global(s)
+ method s-type-global(self, l, s):
+ s-type-global(l, s)
end,
- method s-module-global(self, s):
- s-module-global(s)
+ method s-module-global(self, l, s):
+ s-module-global(l, s)
end,
- method s-global(self, s):
- s-global(s)
+ method s-global(self, l, s):
+ s-global(l, s)
end,
- method s-atom(self, base, serial):
- s-atom(base, serial)
+ method s-atom(self, l, base, serial):
+ s-atom(l, base, serial)
end,
method s-star(self, l, hidden):
@@ -2524,16 +2531,16 @@ default-iter-visitor = {
method s-name(self, l, s):
true
end,
- method s-global(self, s):
+ method s-global(self, l, s):
true
end,
- method s-type-global(self, s):
+ method s-type-global(self, l, s):
true
end,
- method s-module-global(self, s):
+ method s-module-global(self, l, s):
true
end,
- method s-atom(self, base, serial):
+ method s-atom(self, l, base, serial):
true
end,
@@ -3142,17 +3149,17 @@ dummy-loc-visitor = {
method s-name(self, l, s):
s-name(dummy-loc, s)
end,
- method s-global(self, s):
- s-global(s)
+ method s-global(self, l, s):
+ s-global(dummy-loc, s)
end,
- method s-type-global(self, s):
- s-type-global(s)
+ method s-type-global(self, l, s):
+ s-type-global(dummy-loc, s)
end,
- method s-module-global(self, s):
- s-module-global(s)
+ method s-module-global(self, l, s):
+ s-module-global(dummy-loc, s)
end,
- method s-atom(self, base, serial):
- s-atom(base, serial)
+ method s-atom(self, l, base, serial):
+ s-atom(dummy-loc, base, serial)
end,
method s-star(self, _, hidden):
diff --git a/lang/src/arr/trove/charts.arr b/lang/src/arr/trove/charts.arr
index ae49042b5..ce6b7c028 100644
--- a/lang/src/arr/trove/charts.arr
+++ b/lang/src/arr/trove/charts.arr
@@ -369,10 +369,10 @@ explode-method = method(self, offsets :: CL.LoN) block:
end
histogram-label-method = method(self, labels :: CL.LoS) block:
- when raw-array-length(self.obj!tab) <> labels.length():
+ when self.obj.vals.length() <> labels.length():
raise(ERR.message-exception('histogram: xs and labels should have the same length'))
end
- self.constr()(self.obj.{tab: raw-array-from-list(map2({(arr, label): raw-array-set(arr, 0, label)}, raw-array-to-list(self.obj!tab), labels))})
+ self.constr()(self.obj.{vals: map2({(v, l): v.{label: l} }, self.obj.vals, labels)})
end
box-labels-method = method(self, labels :: CL.LoS) block:
@@ -1111,9 +1111,15 @@ default-multi-bar-chart-series = {
bandwidth: 0.8,
default-interval-color: none
}
+
+type HistogramValue = {
+ value :: Number,
+ label :: String,
+ image :: Option
+}
type HistogramSeries = {
- tab :: TableIntern,
+ vals :: List,
bin-width :: Option,
max-num-bins :: Option,
min-num-bins :: Option,
@@ -2380,13 +2386,17 @@ fun freq-bar-chart-from-list(label :: CL.LoS) -> DataSeries:
bar-chart-from-list(ls.reverse(), vs.reverse())
end
+fun get-histogram-value(value :: Number, label :: String, optimg :: Option) -> HistogramValue:
+ { value: value, label: label, image: optimg }
+end
+
fun histogram-from-list(values :: CL.LoN) -> DataSeries block:
doc: ```
Consume a list of numbers and construct a histogram
```
values.each(check-num)
default-histogram-series.{
- tab: to-table2(values.map({(_): ''}), values),
+ vals: map3(get-histogram-value, values, values.map({(_): ''}), values.map({(_): none})),
} ^ histogram-series
end
@@ -2402,7 +2412,7 @@ fun labeled-histogram-from-list(labels :: CL.LoS, values :: CL.LoN) -> DataSerie
values.each(check-num)
labels.each(check-string)
default-histogram-series.{
- tab: to-table2(labels, values),
+ vals: map3(get-histogram-value, values, labels, values.map({(_): none})),
} ^ histogram-series
end
@@ -2416,7 +2426,7 @@ fun image-histogram-from-list(images :: CL.LoI, values :: CL.LoN) -> DataSeries
values.each(check-num)
default-histogram-series.{
- tab: to-table3(values.map({(_): ''}), values, images),
+ vals: map3(get-histogram-value, values, values.map({(_): ''}), images.map(some))
} ^ histogram-series
end
@@ -2517,6 +2527,7 @@ fun render-chart(s :: DataSeries) -> ChartWindow:
shadow self = self.{y-min: none}
_ = check-render-x-axis(self)
_ = check-render-y-axis(self)
+ _ = check-data-range(self.x-min, self.x-max, obj.vals)
CL.histogram(self, obj)
end
} ^ histogram-chart-window
diff --git a/lang/src/js/trove/charts-lib.js b/lang/src/js/trove/charts-lib.js
index 18fdf9289..2fa7d5fb0 100644
--- a/lang/src/js/trove/charts-lib.js
+++ b/lang/src/js/trove/charts-lib.js
@@ -211,7 +211,8 @@
const chartFontConfig = {
signals: [
- { name: 'fontSizeScale', value: 14 }
+ { name: 'fontSizeScale', value: 14 },
+ { name: 'titleText', value: '' },
],
mark: {
fontSize: { signal: 'fontSizeScale' }
@@ -469,7 +470,7 @@
return {
"$schema": "https://vega.github.io/schema/vega/v6.json",
description: title,
- title: title ? { text: title } : '',
+ title: title && { text: { signal: 'titleText' } },
width,
height,
padding: 0,
@@ -1032,7 +1033,7 @@
return {
"$schema": "https://vega.github.io/schema/vega/v6.json",
description: title,
- title: title ? { text: title } : '',
+ title: title && { text: { signal: 'titleText' } },
width,
height,
padding: 0,
@@ -1320,7 +1321,7 @@
return {
"$schema": "https://vega.github.io/schema/vega/v6.json",
description: title,
- title: title ? { text: title } : '',
+ title: title && { text: { signal: 'titleText' } },
width,
height,
padding: 0,
@@ -1596,7 +1597,7 @@
return {
"$schema": "https://vega.github.io/schema/vega/v6.json",
description: title,
- title: title ? { text: title } : '',
+ title: title && { text: { signal: 'titleText' } },
width,
height,
padding: 0,
@@ -1613,11 +1614,15 @@
}
function histogram(globalOptions, rawData) {
- const table = get(rawData, 'tab');
+ const values = RUNTIME.ffi.toArray(get(rawData, 'vals'));
const binWidth = getNumOrDefault(get(rawData, 'bin-width'), undefined);
const maxNumBins = getNumOrDefault(get(rawData, 'max-num-bins'), undefined);
+ const xMinValue = getNumOrDefault(globalOptions['x-min'], undefined);
+ const xMaxValue = getNumOrDefault(globalOptions['x-max'], undefined);
+ const yMinValue = getNumOrDefault(globalOptions['y-min'], undefined);
+ const yMaxValue = getNumOrDefault(globalOptions['y-max'], undefined);
const title = globalOptions['title'];
const width = globalOptions['width'];
const height = globalOptions['height'];
@@ -1627,10 +1632,13 @@
const data = [
{
name: 'rawTable',
- values: table.map((row, i) => ({
- label: row[0],
- value: toFixnum(row[1]),
- image: (row[2] && row[2].val && IMAGE.isImage(row[2].val)) ? imageToCanvas(row[2].val) : undefined,
+ values: values.map((v, i) => ({
+ label: get(v, 'label'),
+ value: toFixnum(get(v, 'value')),
+ image: cases(RUNTIME.ffi.isOption, 'Option', get(v, 'image'), {
+ none: () => undefined,
+ some: (opaqueImg) => imageToCanvas(opaqueImg.val)
+ }),
})),
transform: [
{
@@ -1684,7 +1692,11 @@
const signals = [
{ name: 'boxHeight', update: 'height / abs(domain("countScale")[0] - domain("countScale")[1])' },
{ name: 'showIndividualBoxes', update: 'boxHeight >= 5' },
- { name: 'binWidth', update: `${binWidth ?? 0}` }
+ { name: 'binWidth', update: `${binWidth ?? 0}` },
+ { name: 'xMinValue', value: xMinValue },
+ { name: 'xMaxValue', value: xMaxValue },
+ { name: 'yMinValue', value: yMinValue },
+ { name: 'yMaxValue', value: yMaxValue },
];
const rangeFormatStr = '"[" + trim(format(datum.bin0, "5~f")) + ", " + trim(format(datum.bin1, "5~f")) + "]"';
@@ -1753,7 +1765,8 @@
type: 'linear',
range: 'width',
zero: false,
- domain: { data: 'rawTable', fields: ['bin0', 'bin1'] }
+ domain: { data: 'rawTable', fields: ['bin0', 'bin1'] },
+ domainMin: { signal: 'xMinValue' }, domainMax: { signal: 'xMaxValue' }
},
{
name: 'countScale',
@@ -1770,7 +1783,7 @@
{ orient: 'bottom', scale: 'binScale', zindex: 1, title: xAxisLabel,
format: '5~r',
values: (binWidth
- ? { signal: `sequence(range('binScale')[0], range('binScale')[1] + binWidth, binWidth)` }
+ ? { signal: `sequence(floor(domain('binScale')[0] / binWidth) * binWidth, domain('binScale')[1] + binWidth, binWidth)` }
: undefined) },
{ orient: 'left', scale: 'countScale', grid: true, title: yAxisLabel }
];
@@ -1779,7 +1792,7 @@
return {
"$schema": "https://vega.github.io/schema/vega/v6.json",
description: title,
- title: title ? { text: title } : '',
+ title: title && { text: { signal: 'titleText' } },
width,
height,
padding: 0,
@@ -1860,7 +1873,7 @@
];
const signals = [
{ name: 'dotSize', value: pointSize },
- { name: 'binSize', update: 'invert("binScale", dotSize)' },
+ { name: 'binSize', update: 'abs(dotSize * (domain("binScale")[1] - domain("binScale")[0]) / (range("binScale")[1] - range("binScale")[0]))' },
{ name: 'actualDotSize', update: 'scale("dotScale", 0) - scale("dotScale", 1)' },
{ name: 'headspace', value: '0.25' },
{ name: 'wrapMaxY', update: 'floor(domain("dotScale")[1] * (1 - headspace))' },
@@ -1948,7 +1961,7 @@
return {
"$schema": "https://vega.github.io/schema/vega/v6.json",
description: title,
- title: title ? { text: title } : '',
+ title: title && { text: { signal: 'titleText' } },
width,
height,
padding: 0,
@@ -2067,7 +2080,7 @@
return {
"$schema": "https://vega.github.io/schema/vega/v6.json",
description: title,
- title: title ? { text: title } : '',
+ title: title && { text: { signal: 'titleText' } },
width,
height,
padding: 0,
@@ -3122,7 +3135,7 @@
return {
"$schema": "https://vega.github.io/schema/vega/v6.json",
description: title,
- title: title ? { text: title } : '',
+ title: title && { text: { signal: 'titleText' } },
width,
height,
padding: 0,
@@ -3251,9 +3264,9 @@
// NOTE(Ben): this externalContext *should* be unnecessary, but for some reason,
// vega-as-bundled-in-a-jarr doesn't seem to notice NodeCanvas correctly
const externalContext = canvas.getContext('2d');
- view.width(width).height(height).resize()
+ view.width(width).height(height).signal('titleText', 'spacer').resize()
return view.runAsync()
- .then(() => view.toCanvas(1, { externalContext }))
+ .then(() => view.signal('titleText', view.description()).toCanvas(1, { externalContext }))
.then(() => externalContext.getImageData(0, 0, width, height));
}
@@ -3329,7 +3342,7 @@
hover: true,
tooltip: vegaTooltipHandler.call
});
- view.width(width).height(height).resize();
+ view.width(width).height(height).signal('titleText', view.description()).resize();
var tmp = processed;
tmp.view = view;
diff --git a/lang/src/js/trove/source-map-lib.js b/lang/src/js/trove/source-map-lib.js
index 5b22e46d8..d36ffbd18 100644
--- a/lang/src/js/trove/source-map-lib.js
+++ b/lang/src/js/trove/source-map-lib.js
@@ -1,4 +1,4 @@
-{
+({
nativeRequires: ["source-map"],
requires: [],
provides: {
@@ -75,4 +75,4 @@
},
{});
}
-}
+})
diff --git a/lang/src/types.d.ts b/lang/src/types.d.ts
new file mode 100644
index 000000000..dfe98eac6
--- /dev/null
+++ b/lang/src/types.d.ts
@@ -0,0 +1,431 @@
+declare const __pyret_module_return: unique symbol;
+type ret = { [__pyret_module_return]: void };
+
+declare const __pyret_value: symbol;
+type val = { [__pyret_value]: void };
+
+declare const __pyret_type: unique symbol;
+type typ = { [__pyret_type]: void };
+
+type todo = unknown;
+type todo_func = (...args: todo[]) => todo;
+
+type pyret_pos = {
+ startRow: number;
+ startCol: number;
+ startChar: number;
+ endRow: number;
+ endChar: number;
+};
+
+interface PyretFFI {
+ makePyretPos(filename: string, p: pyret_pos): val;
+ combinePyretPos(filename: string, p1: pyret_pos, p2: pyret_pos): val;
+
+ // #region throw
+ throwUpdateNonObj(loc: val, objval: val, objloc: todo): never;
+ throwUpdateFrozenRef(
+ loc: val,
+ objloc: val,
+ fieldname: string,
+ fieldloc: val,
+ ): never;
+ throwUpdateNonRef: todo_func;
+ throwUpdateNonExistentField: todo_func;
+ throwNumStringBinopError: todo_func;
+ throwNumericBinopError: todo_func;
+ throwInternalError: todo_func;
+ throwSpinnakerError: todo_func;
+ throwFieldNotFound: todo_func;
+ throwConstructorSyntaxNonConstructor: todo_func;
+ throwLookupConstructorNotObject: todo_func;
+ throwLookupNonObject: todo_func;
+ throwLookupNonTuple: todo_func;
+ throwBadTupleBind: todo_func;
+ throwNonTupleBind: todo_func;
+ throwLookupLargeIndex: todo_func;
+ throwExtendNonObject: todo_func;
+ throwTypeMismatch: todo_func;
+ throwInvalidArrayIndex: todo_func;
+ throwMessageException(msg: string): never;
+ throwMultiErrorException: todo_func;
+ throwUserException: todo_func;
+ throwEqualityException: todo_func;
+ throwUninitializedId: todo_func;
+ throwUninitializedIdMkLoc: todo_func;
+ throwArityError: todo_func;
+ throwArityErrorC: todo_func;
+ throwHeaderRowMismatch: todo_func;
+ throwRowLengthMismatch: todo_func;
+ throwColLengthMismatch: todo_func;
+ throwConstructorArityErrorC: todo_func;
+ throwCasesArityError: todo_func;
+ throwCasesArityErrorC: todo_func;
+ throwCasesSingletonError: todo_func;
+ throwCasesSingletonErrorC: todo_func;
+ throwNonBooleanCondition: todo_func;
+ throwNonBooleanOp: todo_func;
+ throwNoBranchesMatched: todo_func;
+ throwNoCasesMatched: todo_func;
+ throwNonFunApp: todo_func;
+ throwColumnNotFound: todo_func;
+ throwDuplicateColumn: todo_func;
+ throwUnfinishedTemplate: todo_func;
+ throwModuleLoadFailureL: todo_func;
+ throwParseErrorBadApp: todo_func;
+ throwParseErrorBadFunHeader: todo_func;
+ throwParseErrorNextToken: todo_func;
+ throwParseErrorColonColon: todo_func;
+ throwParseErrorEOF: todo_func;
+ throwParseErrorUnterminatedString: todo_func;
+ throwParseErrorBadNumber: todo_func;
+ throwParseErrorBadOper: todo_func;
+ throwParseErrorBadCheckOper: todo_func;
+ // #endregion
+
+ makeBadBracketException: todo_func;
+ makeRecordFieldsFail: todo_func;
+ makeTupleAnnsFail: todo_func;
+ makeFieldFailure: todo_func;
+ makeAnnFailure: todo_func;
+ makeMissingField: todo_func;
+ makeTupleLengthMismatch: todo_func;
+ makeTypeMismatch: todo_func;
+ makeRefInitFail: todo_func;
+ makePredicateFailure: todo_func;
+ makeDotAnnNotPresent: todo_func;
+ makeFailureAtArg: todo_func;
+ contractOk: todo;
+ contractFail: todo;
+ contractFailArg: todo;
+ isOk: todo;
+ isFail: todo;
+ isFailArg: todo;
+
+ equal: val;
+ notEqual: val;
+ unknown: val;
+ isEqual(v: val): boolean;
+ isNotEqual(v: val): boolean;
+ isUnknown(v: val): boolean;
+ isEqualityResult(v: val): boolean;
+
+ makeInternalError(message: string, otherArgs: val): val;
+ makeMessageException(message: string): val;
+ makeUserException(val: val): val;
+ makeModuleLoadFailureL(names: string[]): val;
+
+ cases(
+ pred: (val: val) => boolean,
+ predName: string,
+ val: val,
+ casesObj: Record,
+ ): val;
+ checkArity: PyretRuntime["checkArity"];
+
+ makeList(items: val[]): val;
+ isList(val: val): boolean;
+ isLink(val: val): boolean;
+ isEmpty(val: val): boolean;
+ listLength(val: val): number;
+
+ toArray(val: val): val[];
+
+ isOption: (val: val) => boolean;
+ isSome: (val: val) => boolean;
+ isNone: (val: val) => boolean;
+ makeSome(val: val): val;
+ makeNone: () => val;
+
+ isEither: (val: val) => boolean;
+ isLeft: (val: val) => boolean;
+ isRight: (val: val) => boolean;
+ makeLeft(val: val): val;
+ makeRight(val: val): val;
+}
+
+declare const __type_brander__: unique symbol;
+
+type SrcLocJs =
+ | [builtInModule: string]
+ | [
+ source: string,
+ startLine: number,
+ startColumn: number,
+ startChar: number,
+ endLine: number,
+ endColumn: number,
+ endChar: number,
+ ];
+type func = (...args: any[]) => any;
+
+type restarter = {
+ resume: (val: val) => void;
+ // TODO:
+ break: func;
+ error: func;
+};
+
+type Resumer = (restarter: restarter) => void;
+
+declare namespace ABI {
+ class PBase {
+ protected constructor();
+ brands: Record;
+ dict: Record;
+
+ extendWith(fields: Record): PBase;
+ }
+
+ class POpaque extends PBase {}
+
+ class PNothing extends PBase {
+ brand(b: string): PNothing;
+ }
+
+ class PFunction extends PBase {
+ protected constructor(fun: func, arity: number, name: string);
+ app: func;
+ name: number;
+
+ brand(b: string): PFunction;
+ }
+
+ class PMethod extends PBase {
+ protected constructor(meth: func, full_meth: func, name: string);
+ meth: func;
+ full_meth: func;
+ name: string;
+ }
+
+ const enum RefState {
+ GRAPHABLE = 0,
+ UNGRAPHABLE = 1,
+ SET = 2,
+ FROZEN = 3,
+ }
+
+ class PRef {
+ state: RefState;
+ // TODO:
+ anns: unknown[];
+ value: unknown;
+ }
+
+ class PTuple extends PBase {
+ vals: val[];
+ }
+
+ class SuccessResult {
+ result: PBase;
+ stats: unknown | undefined;
+ }
+
+ type __ErrorExt = {
+ pyretStack?: string[];
+ exn?: val;
+ }
+
+ class FailureResult {
+ exn: Error & __ErrorExt;
+ stats: unknown | undefined;
+ }
+}
+
+type PyretObject = val & ABI.PBase;
+type PyretOpaque = val & ABI.POpaque;
+type PyretNothing = val & ABI.PNothing;
+type PyretString = val & string;
+type PyretBoolean = val & boolean;
+type PyretRoughNum = val & number;
+
+type PyretFunction = val & ABI.PFunction;
+type PyretMethod = val & ABI.PMethod;
+
+type PyretTuple = val & ABI.PTuple;
+
+// TODO:
+type RunOptions = {
+ sync: boolean;
+ initialGas: number;
+ initialRunGas: number;
+};
+
+
+// NOTE: this is far from complete
+interface PyretRuntime {
+ builtins: val;
+ run(
+ program: unknown,
+ namespace: unknown,
+ options: RunOptions,
+ onDone: unknown,
+ ): unknown;
+ runThunk(
+ f: () => T,
+ then: (result: T) => unknown,
+ options?: RunOptions,
+ ): void;
+ safeCall(fun: () => T, after: (result: T) => U, stackFrame: string): U;
+
+ ffi: PyretFFI;
+
+ pauseStack(resumer: Resumer): val;
+ schedulePause(resumer: Resumer): void;
+ breakAll(): void;
+ await(promise: Promise): T;
+
+ getField(obj: val, field: string): T;
+ getFieldLoc(obj: val, field: string, loc: SrcLocJs): T;
+ getFieldRef(obj: val, field: string, loc: SrcLocJs): T;
+ getFields(obj: val): string[];
+ getBracket(loc: SrcLocJs, obj: val, field: string): T;
+ getColonField(val: val, field: string): T;
+ getColonFieldLoc(val: val, field: string, loc: SrcLocJs): T;
+ getTuple(tup: val, index: number, loc: SrcLocJs): T;
+ checkTupleBind(tup: val, index: number, loc: SrcLocJs): boolean;
+ extendObj(loc: SrcLocJs, obj: val, extension: Record): val;
+
+ isBase(v: unknown): v is PyretObject;
+ isNothing(v: unknown): v is PyretNothing;
+ isNumber(v: unknown): v is val;
+ isRoughnum(v: unknown): v is PyretRoughNum;
+ isExactnum(v: unknown): v is val;
+ isString(v: unknown): v is PyretString;
+ isBoolean(v: unknown): v is PyretBoolean;
+ isFunction(v: unknown): v is func;
+ isMethod(v: unknown): v is func;
+ isTuple(v: unknown): v is PyretTuple;
+ isObject(v: unknown): v is val;
+ isDataValue(v: unknown): v is val;
+ isRef(v: unknown): v is val;
+ isOpaque(v: unknown): v is PyretOpaque;
+ isPyretVal(v: unknown): v is val;
+
+ isSuccessResult(v: unknown): v is ABI.SuccessResult;
+ makeSuccessResult(r: unknown): ABI.SuccessResult;
+ isFailureResult(v: unknown): v is ABI.FailureResult;
+ makeFailureResult(e: unknown): ABI.FailureResult;
+
+ makeNothing(): PyretNothing;
+ makeNumber(n: number): val;
+ makeNumberBig(n: unknown): val;
+ makeNumberFromString(s: string): val;
+ makeBoolean(b: boolean): PyretBoolean;
+ makeString(s: string): PyretString;
+ makeFunction(fun: func, name: string): PyretFunction;
+ makeMethod(meth: func, full_meth: func, name: string): PyretMethod;
+ // ...
+ makeTuple(tup: val[]): PyretTuple;
+ makeObject(obj: Record): PyretObject;
+ makeArray(arr: val[]): val;
+ makeArrayN(n: number): val;
+
+ // TODO:
+ checkArrayIndex: func;
+ makeBrandedObject: func;
+ makeGraphableRef: func;
+ makeRef: func;
+ makeUnsafeSetRef: func;
+ makeVariantConstructor: func;
+ makeDataValue: func;
+ makeDataTypeConstructor: func;
+ makeMatch: func;
+ makeOpqaue(val: any): PyretOpaque;
+
+ // ...
+
+ wrap(v: any): val;
+ unwrap(v: val): T;
+
+ checkArity: (
+ expected: number,
+ args: IArguments,
+ source: string,
+ isMethod: boolean,
+ ) => void;
+ checkString(v: string): void;
+ checkNumber(v: number): void;
+ checkExactnum(v: number): void;
+ checkRoughnum(v: number): void;
+ checkNumInteger(v: number): void;
+ checkNumRational(v: number): void;
+ checkNumNegative(v: number): void;
+ checkNumPositive(v: number): void;
+ checkNumNonPositive(v: number): void;
+ checkNumNonNegative(v: number): void;
+ checkTuple(v: val): void;
+ checkArray(v: val): void;
+ checkBoolean(v: boolean): void;
+ checkObject(v: val): void;
+ checkFunction(v: val): void;
+ checkMethod(v: val): void;
+ checkOpaque(v: val): void;
+ checkPyretVal(v: unknown): void;
+
+ nothing: val;
+ toRepr(v: val): val;
+
+ makeSrcloc(srcloc: SrcLocJs): val;
+
+ makeJSModuleReturn: (jsMod: any) => ret;
+ makeModuleReturn: (
+ values: Record,
+ types: Record,
+ internal?: Record,
+ ) => ret;
+
+ modules: Record;
+
+ stdout: typeof process.stdout;
+ stderr: typeof process.stderr;
+ stdin: typeof process.stdin;
+ console: typeof console;
+
+ makePrimAnn: unknown;
+}
+
+type PrimType =
+ | "Number"
+ | "String"
+ | "Boolean"
+ | "Nothing"
+ | "Any"
+ | "tany"
+ | "tbot";
+type TypeId = ["tid", string];
+type Arrow = ["arrow", InteropSignature[], InteropSignature];
+type ForAll = ["forall", string[], InteropSignature];
+type ListOf = ["List", InteropSignature];
+type ArrayOf = ["Array", InteropSignature];
+type RawArrayOf = ["RawArray", InteropSignature];
+type OptionOf = ["Option", InteropSignature];
+type Maker = ["Maker", InteropSignature];
+type InteropSignature =
+ | ForAll
+ | Arrow
+ | TypeId
+ | PrimType
+ | ListOf
+ | ArrayOf
+ | RawArrayOf
+ | OptionOf
+ | Maker;
+
+type RequireSpec =
+ | { "import-type": "builtin"; name: string }
+ | { "import-type": "dependency"; protocol: string; args: any[] };
+interface PyretModule {
+ requires: RequireSpec[];
+ nativeRequires: any[];
+ provides: {
+ values?: Record;
+ types?: Record;
+ };
+ theModule: (
+ runtime: PyretRuntime,
+ namespace: string,
+ uri: string,
+ ...imports: any[]
+ ) => ret;
+}
diff --git a/lang/tests/pyret/tests/test-compile-lib.arr b/lang/tests/pyret/tests/test-compile-lib.arr
index c00f127e4..3bc9bcb08 100644
--- a/lang/tests/pyret/tests/test-compile-lib.arr
+++ b/lang/tests/pyret/tests/test-compile-lib.arr
@@ -413,7 +413,7 @@ check "raw-provide-syntax":
l = SL.builtin("test-raw-provides")
bn = lam(modname, name):
- T.t-name(T.module-uri("builtin://" + modname), A.s-type-global(name), l, false)
+ T.t-name(T.module-uri("builtin://" + modname), A.s-type-global(l, name), l, false)
end
g = lam(name):
bn("global", name)
@@ -452,7 +452,7 @@ check:
# MARK(joe/ben): modules
mt,
[string-dict:
- "x", CM.v-just-type(o("x"), T.t-name(T.module-uri("builtin://global"), A.s-global("Number"), A.dummy-loc, false))
+ "x", CM.v-just-type(o("x"), T.t-name(T.module-uri("builtin://global"), A.s-global(A.dummy-loc, "Number"), A.dummy-loc, false))
],
mt,
mt)
@@ -478,7 +478,7 @@ check:
canon is CM.provides("test-provides1",
mt, #MARK(joe/ben): modules
[string-dict:
- "x", CM.v-just-type(o("x"), T.t-name(T.module-uri("builtin://global"), A.s-global("Number"), A.dummy-loc, false))
+ "x", CM.v-just-type(o("x"), T.t-name(T.module-uri("builtin://global"), A.s-global(A.dummy-loc, "Number"), A.dummy-loc, false))
],
mt,
mt)
diff --git a/lang/tests/pyret/tests/test-letrec.arr b/lang/tests/pyret/tests/test-letrec.arr
index e8cc0c320..5bfc721d7 100644
--- a/lang/tests/pyret/tests/test-letrec.arr
+++ b/lang/tests/pyret/tests/test-letrec.arr
@@ -1,3 +1,5 @@
+use context empty-context
+
import either as Eth
import error as E
diff --git a/lang/tests/type-check/main.arr b/lang/tests/type-check/main.arr
index e9397749e..ad33efcd5 100644
--- a/lang/tests/type-check/main.arr
+++ b/lang/tests/type-check/main.arr
@@ -146,7 +146,7 @@ end
check "All builtins should have a type":
covered = TD.make-default-types()
for each(builtin from CS.standard-globals.values.keys-list()):
- builtin-typ = covered.get-now(A.s-global(builtin).key())
+ builtin-typ = covered.get-now(A.s-global(A.dummy-loc, builtin).key())
builtin-typ satisfies is-some
when is-none(builtin-typ):
"Should have a type: " is builtin
diff --git a/lang/tsconfig.json b/lang/tsconfig.json
new file mode 100644
index 000000000..cbcbe30d0
--- /dev/null
+++ b/lang/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "include": ["src/**/*.js", "src/**/*.d.ts"],
+ "exclude": [".pyret/**/*", "node_modules"],
+ "compilerOptions": {
+ "strict": true,
+ "allowJs": true,
+ "checkJs": true,
+ "module": "nodenext",
+ "target": "esnext",
+ "noEmit": true
+ }
+}
diff --git a/lsp/.gitignore b/lsp/.gitignore
new file mode 100644
index 000000000..75dc6efa4
--- /dev/null
+++ b/lsp/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/out
diff --git a/lsp/README.md b/lsp/README.md
new file mode 100644
index 000000000..e4246104c
--- /dev/null
+++ b/lsp/README.md
@@ -0,0 +1,13 @@
+To hack on the LSP, you must jump through a few build hoops
+(at least until we get some better monorepo make rules):
+1. In `lang`, make sure to run `make libA`. This builds the libraries
+ (in `libs.arr`) in addition to the compiler itself, and is needed
+ by the LSP.
+2. Build CPO, following the instructions in the README
+ (it should reference `lang` via symlink)
+3. Build this server, using `npm run build`
+4. Build and launch the VSCode extension for debugging, following the
+ instructions in the README
+
+To see the server's logged messages, check VSCode's Output tab
+(`CTRL`+`SHIFT`+`U`) and select `Pyret Language Server`.
diff --git a/lsp/package-lock.json b/lsp/package-lock.json
new file mode 100644
index 000000000..2d4704b88
--- /dev/null
+++ b/lsp/package-lock.json
@@ -0,0 +1,125 @@
+{
+ "name": "pyret-lsp",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "pyret-lsp",
+ "version": "1.0.0",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "@types/ws": "^8.18.1",
+ "vscode-languageserver": "^9.0.1",
+ "vscode-languageserver-textdocument": "^1.0.12",
+ "ws": "^8.19.0"
+ },
+ "devDependencies": {
+ "@types/node": "^25.3.0",
+ "typescript": "^5.9.3"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "25.3.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz",
+ "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-jsonrpc": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
+ "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/vscode-languageserver": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
+ "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-languageserver-protocol": "3.17.5"
+ },
+ "bin": {
+ "installServerIntoExtension": "bin/installServerIntoExtension"
+ }
+ },
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
+ "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-jsonrpc": "8.2.0",
+ "vscode-languageserver-types": "3.17.5"
+ }
+ },
+ "node_modules/vscode-languageserver-textdocument": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
+ "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
+ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
+ "license": "MIT"
+ },
+ "node_modules/ws": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/lsp/package.json b/lsp/package.json
new file mode 100644
index 000000000..e633a5026
--- /dev/null
+++ b/lsp/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "pyret-lsp",
+ "version": "1.0.0",
+ "description": "",
+ "license": "UNLICENSED",
+ "author": "",
+ "type": "commonjs",
+ "main": "index.js",
+ "scripts": {
+ "build": "tsc -p tsconfig.json",
+ "watch": "tsc -p tsconfig.json --watch"
+ },
+ "dependencies": {
+ "@types/ws": "^8.18.1",
+ "vscode-languageserver": "^9.0.1",
+ "vscode-languageserver-textdocument": "^1.0.12",
+ "ws": "^8.19.0"
+ },
+ "devDependencies": {
+ "@types/node": "^25.3.0",
+ "typescript": "^5.9.3"
+ }
+}
diff --git a/lsp/src/server-browser.ts b/lsp/src/server-browser.ts
new file mode 100644
index 000000000..aae16f968
--- /dev/null
+++ b/lsp/src/server-browser.ts
@@ -0,0 +1,12 @@
+import {
+ BrowserMessageReader,
+ BrowserMessageWriter,
+ createConnection,
+} from 'vscode-languageserver/browser';
+import { setupServer } from './server-shared';
+
+const messageReader = new BrowserMessageReader(globalThis);
+const messageWriter = new BrowserMessageWriter(globalThis);
+
+const connection = createConnection(messageReader, messageWriter);
+setupServer(connection);
diff --git a/lsp/src/server-node-tmp.ts b/lsp/src/server-node-tmp.ts
new file mode 100644
index 000000000..2876f8e9b
--- /dev/null
+++ b/lsp/src/server-node-tmp.ts
@@ -0,0 +1,252 @@
+import { createConnection, ProposedFeatures } from "vscode-languageserver/node";
+
+import {
+ Connection,
+ ServerCapabilities,
+ TextDocuments,
+ TextDocumentSyncKind,
+} from "vscode-languageserver";
+import { TextDocument } from "vscode-languageserver-textdocument";
+import * as childProcess from "child_process";
+import * as path from "path";
+import * as fs from "fs";
+import * as os from "os";
+import WebSocket from "ws";
+
+const connection = createConnection(ProposedFeatures.all);
+
+// Pyret server management
+const compilerPath = path.join(
+ __dirname,
+ "..",
+ "..",
+ "lang",
+ "build",
+ "phaseA",
+ "pyret.jarr",
+);
+let pyretServerProcess: childProcess.ChildProcess | null = null;
+
+function getSocketPath(): string {
+ const name = "parley-lsp-" + os.userInfo().username;
+ const dir = path.join(os.tmpdir(), name);
+
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+ }
+
+ return path.join(dir, `comm-${process.pid}.sock`);
+}
+
+function startPyretServer(portFile: string): Promise {
+ return new Promise((resolve, reject) => {
+ connection.console.log(
+ `Starting Pyret server with compiler at: ${compilerPath}`,
+ );
+
+ if (!fs.existsSync(compilerPath)) {
+ reject(new Error(`Compiler not found at ${compilerPath}`));
+ return;
+ }
+
+ const child = childProcess.fork(
+ compilerPath,
+ ["-serve", "--port", portFile],
+ {
+ stdio: [0, 1, 2, "ipc"],
+ execArgv: ["-max-old-space-size=8192"],
+ },
+ );
+
+ pyretServerProcess = child;
+
+ child.on("message", (msg: any) => {
+ if (msg.type === "success") {
+ connection.console.log("Pyret server started successfully");
+ child.unref();
+ child.disconnect();
+ resolve();
+ } else {
+ reject(msg);
+ }
+ });
+
+ child.on("error", (err) => {
+ connection.console.error(`Pyret server error: ${err.message}`);
+ reject(err);
+ });
+
+ child.on("exit", (code, signal) => {
+ connection.console.log(
+ `Pyret server exited with code ${code}, signal ${signal}`,
+ );
+ pyretServerProcess = null;
+ });
+ });
+}
+
+function shutdownPyretServer(portFile: string): void {
+ if (fs.existsSync(portFile)) {
+ try {
+ fs.unlinkSync(portFile);
+ } catch (e) {
+ connection.console.warn(`Could not remove port file: ${e}`);
+ }
+ }
+
+ if (pyretServerProcess) {
+ pyretServerProcess.kill();
+ pyretServerProcess = null;
+ }
+}
+
+connection.onInitialize(async (_params) => {
+ // NOTE(lsp): Register new LSP capabilities here (e.g. hoverProvider: true)
+ const capabilities: ServerCapabilities = {
+ textDocumentSync: TextDocumentSyncKind.Incremental,
+ definitionProvider: true,
+ };
+
+ const portFile = getSocketPath();
+ try {
+ if (!fs.existsSync(portFile)) {
+ await startPyretServer(portFile);
+ } else {
+ connection.console.log("Pyret server already running at " + portFile);
+ }
+ } catch (err) {
+ connection.console.error(`Failed to start Pyret server: ${err}`);
+ }
+
+ return { capabilities };
+});
+
+connection.onShutdown((_params) => {
+ const portFile = getSocketPath();
+ shutdownPyretServer(portFile);
+});
+
+interface JumpToDefSuccess {
+ uri: string;
+ startLine: number;
+ startColumn: number;
+ endLine: number;
+ endColumn: number;
+}
+
+// NOTE(lsp): To add a new LSP feature:
+// 1. Add a send*Request function below (following this pattern)
+// 2. Add a connection.on* handler at the bottom that calls it
+// 3. Register the capability in onInitialize
+// 4. Add an info-type case in server.arr's info handler
+// 5. Add the Pyret-side logic in lsp.arr
+
+function sendJumpToDefRequest(
+ portFile: string,
+ filePath: string,
+ line: number,
+ col: number,
+): Promise {
+ return new Promise((resolve, reject) => {
+ const client = new WebSocket("ws+unix://" + portFile);
+ let settled = false;
+
+ client.on("error", (err) => {
+ if (!settled) {
+ settled = true;
+ reject(err);
+ }
+ });
+
+ client.on("open", () => {
+ client.send(
+ JSON.stringify({
+ command: "query",
+ query: "jump-to-def",
+ compileOptions: JSON.stringify({
+ program: filePath,
+ "base-dir": ".", // TODO
+ }), // TODO allow configuring default compileOptions
+ queryOptions: JSON.stringify({ line, col }),
+ }),
+ );
+ });
+
+ client.on("message", (data: WebSocket.RawData) => {
+ const msg = JSON.parse(data.toString());
+ if (msg.type === "jump-to-def-success") {
+ if (!settled) {
+ settled = true;
+ resolve({
+ uri: msg.uri,
+ startLine: msg["start-line"],
+ startColumn: msg["start-column"],
+ endLine: msg["end-line"],
+ endColumn: msg["end-column"],
+ });
+ }
+ } else if (msg.type === "jump-to-def-failure") {
+ if (!settled) {
+ settled = true;
+ resolve(null);
+ }
+ } else if (msg.type === "echo-err") {
+ connection.console.error("[pyret jump-to-def] " + msg.contents);
+ } else if (msg.type === "echo-log") {
+ connection.console.log("[pyret jump-to-def] " + msg.contents);
+ }
+ });
+
+ client.on("close", () => {
+ if (!settled) {
+ settled = true;
+ resolve(null);
+ }
+ });
+ });
+}
+
+connection.onDefinition(async (params) => {
+ const portFile = getSocketPath();
+ if (!fs.existsSync(portFile)) {
+ connection.console.error(
+ "Pyret server not running, cannot jump to definition",
+ );
+ return null;
+ }
+
+ // LSP positions are 0-indexed; Pyret srclocs are 1-indexed
+ const line = params.position.line + 1;
+ const col = params.position.character + 1;
+
+ // Strip file:// scheme to get a plain file path
+ const fileUri = params.textDocument.uri;
+ const filePath = fileUri.startsWith("file://")
+ ? decodeURIComponent(fileUri.slice(7))
+ : fileUri;
+
+ try {
+ const result = await sendJumpToDefRequest(portFile, filePath, line, col);
+ if (!result) {
+ return null;
+ }
+
+ return {
+ uri: result.uri,
+ range: {
+ start: {
+ line: result.startLine - 1,
+ character: result.startColumn,
+ },
+ end: { line: result.endLine - 1, character: result.endColumn },
+ },
+ };
+ } catch (err) {
+ connection.console.error(`jump-to-def error: ${err}`);
+ return null;
+ }
+});
+
+const documents = new TextDocuments(TextDocument);
+documents.listen(connection);
+connection.listen();
diff --git a/lsp/src/server-node.ts b/lsp/src/server-node.ts
new file mode 100644
index 000000000..5bb5217ae
--- /dev/null
+++ b/lsp/src/server-node.ts
@@ -0,0 +1,5 @@
+import { createConnection, ProposedFeatures } from 'vscode-languageserver/node';
+import { setupServer } from './server-shared';
+
+const connection = createConnection(ProposedFeatures.all);
+setupServer(connection);
diff --git a/lsp/src/server-shared.ts b/lsp/src/server-shared.ts
new file mode 100644
index 000000000..f615bc977
--- /dev/null
+++ b/lsp/src/server-shared.ts
@@ -0,0 +1,22 @@
+import {
+ Connection,
+ ServerCapabilities,
+ TextDocuments,
+ TextDocumentSyncKind,
+} from 'vscode-languageserver';
+import { TextDocument } from 'vscode-languageserver-textdocument';
+
+export function setupServer(connection: Connection) {
+ connection.onInitialize(_params => {
+ const capabilities: ServerCapabilities = {
+ textDocumentSync: TextDocumentSyncKind.Incremental,
+ };
+ return { capabilities };
+ });
+
+ connection.onShutdown(_params => {});
+
+ const documents = new TextDocuments(TextDocument);
+ documents.listen(connection);
+ connection.listen();
+}
diff --git a/lsp/tsconfig.json b/lsp/tsconfig.json
new file mode 100644
index 000000000..d4525ec76
--- /dev/null
+++ b/lsp/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "ES2020",
+ "outDir": "out",
+ "rootDir": "src",
+ "sourceMap": true,
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+}
diff --git a/new-docs/assets/pyret-logo.png b/new-docs/assets/pyret-logo.png
new file mode 100644
index 000000000..901c319a0
Binary files /dev/null and b/new-docs/assets/pyret-logo.png differ
diff --git a/new-docs/index.poly.pm b/new-docs/index.poly.pm
index 294b97b96..ec3edbe70 100644
--- a/new-docs/index.poly.pm
+++ b/new-docs/index.poly.pm
@@ -4,41 +4,23 @@
◊title[#:version (number->string ◊VERSION)]{Pyret}
-◊nested{This document has detailed information on the Pyret grammar and the
-behavior of its expression forms and built-in libraries, along with many
-examples and some longer descriptions of language design choices. If you want
-to do something in a program and you can't find how in this document, feel free
-to post a message on the
-◊link["https://groups.google.com/forum/#!forum/pyret-discuss" "Pyret discussion
-list"], and we'll be happy to help.}
-
-◊nested{If you want to learn about (or teach!) programming and computer science
-using Pyret, check out ◊link["https://dcic-world.org" "A Data Centric Introduction to Computing"], which is a textbook on programming starting with
-Pyret.}
-
-◊nested{Previous release notes documents have useful information on major
-updates over time.}
-
-◊ul[
- ◊li{◊hyperlink["https://www.pyret.org/release-notes/2025.html"]{Summer 2025}}
- ◊li{◊hyperlink["https://www.pyret.org/release-notes/summer-2021.html"]{Summer 2021}}
- ◊li{◊hyperlink["https://www.pyret.org/release-notes/summer-2020.html"]{Summer 2020}}
- ◊li{◊hyperlink["https://groups.google.com/g/pyret-discuss/c/kUr3iIYsheE/m/Z7FTW9ZcEwAJ"]{Fall 2017}}
- ◊li{◊hyperlink["https://groups.google.com/g/pyret-discuss/c/n4yAxubXHyY/m/EJr0yMlwAAAJ"]{Fall 2016}}
- ◊li{◊hyperlink["https://groups.google.com/g/pyret-discuss/c/i1qMU_YP9Tw/m/j67PlQx0CQAJ"]{Summer 2016}}
- ◊li{◊hyperlink["https://www.pyret.org/release-notes/v0.5.html"]{Summer 2014}}
-]
-
-◊include-section{getting-started.poly.pm}
-
-◊include-section{language-concepts.poly.pm}
-
-◊include-section{libraries.poly.pm}
-
-◊include-section{style-guide.poly.pm}
-
-◊include-section{internal.poly.pm}
-
-◊include-section{glossary.poly.pm}
+◊landing-hero{
+◊h1[#:class "landing-h1"]{Pyret Documentation}
+◊landing-description{This document has detailed information on the Pyret grammar and the behavior of its expression forms and built-in libraries, along with many examples and some longer descriptions of language design choices. If you want to do something in a program and you can't find how in this document, feel free to post a message on the ◊link["https://groups.google.com/forum/#!forum/pyret-discuss" "Pyret discussion list"], and we'll be happy to help.
+
+If you want to learn about (or teach!) programming and computer science using Pyret, check out ◊link["https://dcic-world.org" "A Data Centric Introduction to Computing"], which is a textbook on programming starting with Pyret.}
+◊landing-searchbar[]
+◊landing-btn["getting-started.html"]{Get Started}}
+
+◊landing-releases{
+◊h3[#:class "landing-releases-heading"]{Previous release notes documents:}
+◊landing-release-links{
+◊hyperlink["https://www.pyret.org/release-notes/2025.html"]{Summer 2025}
+◊hyperlink["https://www.pyret.org/release-notes/summer-2021.html"]{Summer 2021}
+◊hyperlink["https://www.pyret.org/release-notes/summer-2020.html"]{Summer 2020}
+◊hyperlink["https://groups.google.com/g/pyret-discuss/c/kUr3iIYsheE/m/Z7FTW9ZcEwAJ"]{Fall 2017}
+◊hyperlink["https://groups.google.com/g/pyret-discuss/c/n4yAxubXHyY/m/EJr0yMlwAAAJ"]{Fall 2016}
+◊hyperlink["https://groups.google.com/g/pyret-discuss/c/i1qMU_YP9Tw/m/j67PlQx0CQAJ"]{Summer 2016}
+◊hyperlink["https://www.pyret.org/release-notes/v0.5.html"]{Summer 2014}}}
◊pollen-postlude[]
diff --git a/new-docs/pyret-docs.rkt b/new-docs/pyret-docs.rkt
index 31df1dfff..02951d110 100644
--- a/new-docs/pyret-docs.rkt
+++ b/new-docs/pyret-docs.rkt
@@ -430,3 +430,33 @@
(define (rbrace)
`(span () "}"))
+
+;; Landing page components
+
+(define (landing-hero . elems)
+ `(div ([class "landing-hero"]) ,@elems))
+
+(define (landing-description . elems)
+ `(div ([class "landing-description"]) ,@elems))
+
+(define (landing-searchbar)
+ `(div ([class "landing-searchbar"])
+ (div ([class "landing-searchbar-inner"])
+ (span ([class "material-symbols-rounded search-icon"]) "search")
+ (input ([class "landing-searchbox"]
+ [id "landing-searchbox"]
+ [type "text"]
+ [tabindex "1"]
+ [placeholder "Search..."]
+ [title "Enter a search string to search the manuals"]
+ [onkeypress "return DoSearchKey(event, this, '9.1', '');"])))))
+
+(define (landing-btn url . elems)
+ `(div ([class "landing-btn-wrapper"])
+ (a ([class "landing-btn"] [href ,url]) ,@elems)))
+
+(define (landing-releases . elems)
+ `(div ([class "landing-releases"]) ,@elems))
+
+(define (landing-release-links . elems)
+ `(div ([class "landing-release-links"]) ,@elems))
diff --git a/new-docs/styles.css b/new-docs/styles.css
index 8c41f2f56..b12399ad9 100644
--- a/new-docs/styles.css
+++ b/new-docs/styles.css
@@ -350,7 +350,7 @@ ul.glossary {
.container {
width: 100%;
min-width: 0;
- padding: 24px 0 0 24px;
+ padding: 24px 24px 0 24px;
box-sizing: border-box;
}
@@ -486,3 +486,327 @@ h4 {
font-weight: 400;
line-height: 150%;
}
+
+/* Landing page layout */
+
+.landing-page {
+ background-color: #F8F9FA;
+}
+
+.landing-page .sidebar {
+ display: none;
+}
+
+.landing-page .searchform {
+ display: none;
+}
+
+.landing-page .container {
+ max-width: 962px;
+ margin: 0 auto;
+ padding: 24px 48px 24px;
+}
+
+.landing-page .container > hr,
+.landing-page .container > .floatleft,
+.landing-page .container > .floatright,
+.landing-page .container h1[id="Pyret"] {
+ display: none;
+}
+
+/* Landing page components */
+
+.landing-hero {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ align-self: stretch;
+}
+
+.landing-h1 {
+ font-family: Roboto, sans-serif;
+ font-weight: 400;
+ font-size: 40px;
+ line-height: 1.2;
+ color: #000;
+ margin: 0;
+ margin-left: 0;
+}
+
+.landing-h1::before {
+ content: none !important;
+}
+
+.landing-description {
+ font-family: Roboto, sans-serif;
+ font-weight: 400;
+ font-size: 16px;
+ line-height: 1.5;
+ color: #000;
+ max-width: 834px;
+}
+
+.landing-description p {
+ margin: 0 0 1em 0;
+}
+
+.landing-description p:last-child {
+ margin-bottom: 0;
+}
+
+.landing-searchbar {
+ align-self: stretch;
+}
+
+.landing-searchbar-inner {
+ display: flex;
+ align-items: center;
+ padding: 12px;
+ gap: 10px;
+ background: #fff;
+ border: 1px solid #D8D8D8;
+ border-radius: 4px;
+}
+
+.landing-searchbar-inner:focus-within {
+ border-color: #A1CDFF;
+ box-shadow: 0px 0px 0px 4px rgba(13, 110, 253, 0.25);
+}
+
+.landing-searchbox {
+ flex: 1;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-family: Roboto, sans-serif;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #333;
+ padding: 0;
+}
+
+.landing-searchbox::placeholder {
+ color: #999;
+}
+
+.landing-btn {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 24px;
+ background-color: var(--primary-50);
+ color: #fff;
+ border-radius: 4px;
+ text-decoration: none;
+ font-family: Roboto, sans-serif;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 1.5;
+ align-self: flex-start;
+}
+
+.landing-btn:hover {
+ background-color: #2567ad;
+ color: #fff;
+}
+
+.landing-releases {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ margin-top: 32px;
+}
+
+.landing-releases-heading {
+ font-family: Roboto, sans-serif;
+ font-weight: 400;
+ font-size: 28px;
+ line-height: 1.2;
+ color: #000;
+ margin: 0;
+}
+
+.landing-releases-heading::before {
+ content: none !important;
+}
+
+.landing-release-links {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.landing-release-links a {
+ font-family: Inter, sans-serif;
+ font-weight: 400;
+ font-size: 18px;
+ line-height: 1.2;
+ color: var(--primary-50);
+ text-decoration: none;
+}
+
+.landing-release-links a:hover {
+ text-decoration: underline;
+}
+
+/* Mobile topbar & hamburger (hidden on desktop) */
+
+.mobile-topbar {
+ display: none;
+}
+
+.sidebar-overlay {
+ display: none;
+}
+
+.sidebar-close-btn {
+ display: none;
+}
+
+/* Mobile layout */
+
+@media (max-width: 768px) {
+ /* Topbar: hide logo & links, show hamburger + title */
+ .topbar {
+ height: auto;
+ padding: 12px 24px;
+ }
+
+ .topbar-logo {
+ display: none;
+ }
+
+ .topbar-links {
+ display: none;
+ }
+
+ .searchform {
+ display: none;
+ }
+
+ .mobile-topbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex: 1;
+ }
+
+ .hamburger-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: none;
+ padding: 8px;
+ cursor: pointer;
+ width: 40px;
+ height: 40px;
+ color: #232323;
+ }
+
+ .hamburger-btn .material-symbols-rounded {
+ font-size: 24px;
+ }
+
+ .mobile-logo {
+ height: 32px;
+ width: auto;
+ }
+
+ /* Sidebar: off-screen drawer */
+ .sidebar {
+ position: fixed;
+ top: 0;
+ left: -300px;
+ z-index: 200;
+ height: 100vh;
+ transition: left 0.25s ease;
+ }
+
+ .sidebar.sidebar-open {
+ left: 0;
+ }
+
+ .sidebar-close-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: none;
+ padding: 8px;
+ cursor: pointer;
+ width: 40px;
+ height: 40px;
+ color: #666;
+ align-self: flex-end;
+ margin-bottom: 8px;
+ }
+
+ .sidebar-overlay {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.4);
+ z-index: 150;
+ }
+
+ .sidebar-overlay.active {
+ display: block;
+ }
+
+ body.sidebar-drawer-open {
+ overflow: hidden;
+ }
+
+ /* Landing page mobile */
+ .landing-page .container {
+ padding: 32px 24px 16px;
+ }
+
+ .landing-hero {
+ gap: 16px;
+ }
+
+ .landing-h1 {
+ font-size: 20px;
+ }
+
+ .landing-description {
+ font-size: 14px;
+ }
+
+ .landing-searchbar-inner {
+ padding: 8px;
+ }
+
+ .landing-searchbox {
+ font-size: 14px;
+ }
+
+ .landing-btn {
+ font-size: 14px;
+ padding: 8px 20px;
+ }
+
+ .landing-releases {
+ margin-top: 16px;
+ gap: 16px;
+ }
+
+ .landing-releases-heading {
+ font-size: 20px;
+ }
+
+ .landing-release-links a {
+ font-size: 14px;
+ }
+
+ .landing-release-links {
+ gap: 8px;
+ }
+}
diff --git a/new-docs/template.html.p b/new-docs/template.html.p
index 40be0c9de..e01e9f203 100644
--- a/new-docs/template.html.p
+++ b/new-docs/template.html.p
@@ -4,6 +4,7 @@
+
@@ -26,9 +27,15 @@
◊(doc-title doc)
-
+
+
+
+ menu
+
+
+
◊(define top-dir (point-to-project-root here))
+
@@ -102,6 +113,27 @@
setTimeout(() => link.classList.remove('clicking'), 300);
});
});
+
+ // Mobile hamburger menu
+ const hamburgerBtn = document.getElementById('hamburger-btn');
+ const sidebarOverlay = document.getElementById('sidebar-overlay');
+ const sidebarCloseBtn = document.getElementById('sidebar-close-btn');
+ const sidebar = document.querySelector('.sidebar');
+
+ function openSidebar() {
+ sidebar.classList.add('sidebar-open');
+ sidebarOverlay.classList.add('active');
+ document.body.classList.add('sidebar-drawer-open');
+ }
+ function closeSidebar() {
+ sidebar.classList.remove('sidebar-open');
+ sidebarOverlay.classList.remove('active');
+ document.body.classList.remove('sidebar-drawer-open');
+ }
+
+ if (hamburgerBtn) hamburgerBtn.addEventListener('click', openSidebar);
+ if (sidebarCloseBtn) sidebarCloseBtn.addEventListener('click', closeSidebar);
+ if (sidebarOverlay) sidebarOverlay.addEventListener('click', closeSidebar);
diff --git a/npm/package-lock.json b/npm/package-lock.json
index be35b6b7f..4feed0f0c 100644
--- a/npm/package-lock.json
+++ b/npm/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pyret-npm",
- "version": "0.0.77",
+ "version": "0.0.90",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pyret-npm",
- "version": "0.0.77",
+ "version": "0.0.90",
"license": "Apache-2.0",
"dependencies": {
"ascii-table": "^0.0.9",
diff --git a/npm/package.json b/npm/package.json
index b5c3e4cce..c5e1352c6 100644
--- a/npm/package.json
+++ b/npm/package.json
@@ -1,6 +1,6 @@
{
"name": "pyret-npm",
- "version": "0.0.77",
+ "version": "0.0.90",
"description": "The CLI for the Pyret programming language",
"files": [
"pyret.js",
diff --git a/util/update-repos.sh b/util/update-repos.sh
new file mode 100755
index 000000000..4f5fc6e5f
--- /dev/null
+++ b/util/update-repos.sh
@@ -0,0 +1,59 @@
+#!/bin/zsh
+
+# For each subrepo branch, pulls in any new commits from the upstream remote.
+# Then merges any updated branches back into the current branch.
+
+typeset -A repos=(
+ ["embed"]="git@github.com:jpolitz/pyret-embed.git"
+ ["vscode"]="git@github.com:jpolitz/pyret-parley-vscode.git"
+ ["npm"]="git@github.com:brownplt/pyret-npm.git"
+ ["pyret.org"]="git@github.com:brownplt/pyret.org.git"
+ ["docs"]="git@github.com:brownplt/pyret-docs.git"
+ ["codemirror-mode"]="git@github.com:brownplt/pyret-codemirror-mode.git"
+ ["code.pyret.org"]="git@github.com:brownplt/code.pyret.org.git"
+ ["lang"]="git@github.com:brownplt/pyret-lang.git"
+)
+
+set -e
+
+start_branch=$(git rev-parse --abbrev-ref HEAD)
+updated=()
+
+# Return to the starting branch on exit (covers both success and error paths).
+# If a merge conflict left us in MERGING state, abort it first so checkout works.
+cleanup() {
+ local git_dir
+ git_dir=$(git rev-parse --git-dir 2>/dev/null) || return
+ if [[ -f "$git_dir/MERGE_HEAD" ]]; then
+ echo "Aborting in-progress merge..."
+ git merge --abort
+ fi
+ git checkout "$start_branch"
+}
+trap cleanup EXIT
+
+for branch in "${(@k)repos}"; do
+ upstream="${repos[$branch]}"
+ git checkout "$branch"
+ before=$(git rev-parse HEAD)
+
+ echo "==> Checking $branch ($upstream)"
+ git pull --no-ff "$upstream"
+
+ after=$(git rev-parse HEAD)
+ updated+=("$branch")
+done
+
+# Back to start before merging (trap will fire on EXIT but we do it explicitly
+# here so the merges happen on the right branch)
+trap - EXIT
+git checkout "$start_branch"
+
+echo ""
+echo "==> Merging updated branches: ${updated[*]}"
+for branch in "${updated[@]}"; do
+ git merge --no-ff "$branch" -m "Merge branch '$branch'"
+done
+
+echo ""
+echo "Done."
diff --git a/vscode/.vscode/launch.json b/vscode/.vscode/launch.json
new file mode 100644
index 000000000..bb95fd1d8
--- /dev/null
+++ b/vscode/.vscode/launch.json
@@ -0,0 +1,14 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Run Extension",
+ "type": "extensionHost",
+ "request": "launch",
+ "runtimeExecutable": "${execPath}",
+ "args": ["--extensionDevelopmentPath=${workspaceRoot}"],
+ "outFiles": ["${workspaceFolder}/out/**/*.js"],
+ "preLaunchTask": "npm: compile"
+ }
+ ]
+}
diff --git a/vscode/README.dev.md b/vscode/README.dev.md
index eb52c6f99..579f18e34 100644
--- a/vscode/README.dev.md
+++ b/vscode/README.dev.md
@@ -1,10 +1,10 @@
-Borrows heavily from https://github.com/microsoft/vscode-extension-samples/tree/main/custom-editor-sample
+Borrows heavily from
To run, first you must symlink `build` to a the `build/` directory of
`code.pyret.org`. You can get one by cloning `code.pyret.org` elsewhere and
symlinking to it.
-Then:
+Then, to test the web extension (in a new `github.dev`-like browser environment):
```
npm i
@@ -12,6 +12,11 @@ npm run compile
npx vscode-test-web --browserType=chromium --extensionDevelopmentPath . ./sampleFiles/
```
+To test locally without the web extension (say, to test or debug a feature that
+is not yet properly supported on the web), the easiest way to do so is to
+1. Open up `src/extension.ts`, then
+2. Press F5 (or run `Debug: Start Debugging` from the Command Palette)
+
User settings for avoiding diff views using the fancy editor; put in
`.vscode/settings.json` (or set via the menu):
@@ -23,6 +28,6 @@ User settings for avoiding diff views using the fancy editor; put in
}
```
-(Courtesy of https://github.com/microsoft/vscode-discussions/discussions/799)
+(Courtesy of )
Grammar and language-configuration contributed by Seth Poulsen.
diff --git a/vscode/package-lock.json b/vscode/package-lock.json
index 641457fb2..fd5a8b47e 100644
--- a/vscode/package-lock.json
+++ b/vscode/package-lock.json
@@ -1,15 +1,16 @@
{
"name": "pyret-parley",
- "version": "0.42.40",
+ "version": "0.42.43",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pyret-parley",
- "version": "0.42.40",
+ "version": "0.42.43",
"license": "Apache-2.0",
"dependencies": {
- "mustache": "^4.2.0"
+ "mustache": "^4.2.0",
+ "vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/mustache": "^4.2.5",
@@ -3455,16 +3456,23 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
"license": "MIT"
},
"node_modules/bare-events": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz",
- "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==",
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
+ "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"dev": true,
"license": "Apache-2.0",
- "optional": true
+ "optional": true,
+ "peerDependencies": {
+ "bare-abort-controller": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ }
+ }
},
"node_modules/bare-fs": {
"version": "4.0.1",
@@ -3940,7 +3948,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -12812,7 +12819,6 @@
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
- "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -15264,6 +15270,57 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/vscode-jsonrpc": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
+ "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/vscode-languageclient": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz",
+ "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==",
+ "license": "MIT",
+ "dependencies": {
+ "minimatch": "^5.1.0",
+ "semver": "^7.3.7",
+ "vscode-languageserver-protocol": "3.17.5"
+ },
+ "engines": {
+ "vscode": "^1.82.0"
+ }
+ },
+ "node_modules/vscode-languageclient/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
+ "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-jsonrpc": "8.2.0",
+ "vscode-languageserver-types": "3.17.5"
+ }
+ },
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
+ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
+ "license": "MIT"
+ },
"node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
diff --git a/vscode/package.json b/vscode/package.json
index e4def3a66..4402cb3be 100644
--- a/vscode/package.json
+++ b/vscode/package.json
@@ -3,7 +3,7 @@
"displayName": "Pyret Interactive Editor for VSCode",
"description": "An interactive Pyret mode",
"icon": "media/pyret-logo-128.png",
- "version": "0.42.40",
+ "version": "0.42.43",
"publisher": "PyretProgrammingLanguage",
"private": true,
"license": "Apache-2.0",
@@ -22,6 +22,7 @@
"syntaxes/pyret.tmLanguage.json"
],
"browser": "./dist/web/extension.js",
+ "main": "./dist/extension.js",
"contributes": {
"languages": [
{
@@ -118,7 +119,14 @@
"Load `url-file()` imports from the local filesystem if they exist, otherwise from the URL"
],
"description": "Controls how `url-file()` imports are resolved"
- }
+ },
+ "pyret.trace.server": {
+ "type": "string",
+ "enum": ["off", "messages", "verbose"],
+ "default": "verbose",
+ "scope": "resource",
+ "description": "Traces logs of the communication between VS Code and the language server."
+ }
}
}
},
@@ -147,6 +155,7 @@
"yo": "^5.1.0"
},
"dependencies": {
- "mustache": "^4.2.0"
+ "mustache": "^4.2.0",
+ "vscode-languageclient": "^9.0.1"
}
}
diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts
new file mode 100644
index 000000000..438e89272
--- /dev/null
+++ b/vscode/src/extension.ts
@@ -0,0 +1,67 @@
+import * as vscode from "vscode";
+import * as path from "path";
+import { PyretCPOWebProvider, makeCommandHandler } from "./pyretCPOWebEditor";
+import {
+ LanguageClient,
+ LanguageClientOptions,
+ ServerOptions,
+ TransportKind,
+} from "vscode-languageclient/node";
+
+let client: LanguageClient;
+
+export function activate(context: vscode.ExtensionContext) {
+ context.subscriptions.push(PyretCPOWebProvider.register(context));
+ context.subscriptions.push(
+ vscode.commands.registerCommand(
+ "pyret-parley.run-file",
+ makeCommandHandler(context),
+ ),
+ );
+
+ const serverModule = context.asAbsolutePath(
+ path.join("..", "lsp", "out", "server-node-tmp.js"),
+ );
+ const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
+ const outputChannel = vscode.window.createOutputChannel(
+ "Pyret Server",
+ );
+ const traceOutputChannel = vscode.window.createOutputChannel(
+ "Pyret Language Server",
+ );
+
+ const serverOptions: ServerOptions = {
+ run: { module: serverModule, transport: TransportKind.ipc },
+ debug: {
+ module: serverModule,
+ transport: TransportKind.ipc,
+ options: debugOptions,
+ },
+ };
+
+ const clientOptions: LanguageClientOptions = {
+ documentSelector: [{ scheme: "file", language: "pyret" }],
+ synchronize: {
+ fileEvents: vscode.workspace.createFileSystemWatcher("**/*.arr"),
+ },
+ outputChannel: outputChannel,
+ traceOutputChannel: traceOutputChannel,
+ };
+
+ client = new LanguageClient(
+ "pyret",
+ "Pyret Language Server",
+ serverOptions,
+ clientOptions,
+ );
+
+ client.start();
+ outputChannel.appendLine("Pyret Language Server started");
+}
+
+export function deactivate(): Thenable | undefined {
+ if (!client) {
+ return undefined;
+ }
+ return client.stop();
+}
diff --git a/vscode/src/webExtension.ts b/vscode/src/webExtension.ts
index 34dd42554..bd73e9cdb 100644
--- a/vscode/src/webExtension.ts
+++ b/vscode/src/webExtension.ts
@@ -5,4 +5,4 @@ export function activate(context: vscode.ExtensionContext) {
// Register our custom editor providers
context.subscriptions.push(PyretCPOWebProvider.register(context));
context.subscriptions.push(vscode.commands.registerCommand("pyret-parley.run-file", makeCommandHandler(context)));
-}
\ No newline at end of file
+}
diff --git a/vscode/webpack.config.js b/vscode/webpack.config.js
index 9893296ff..dd706f861 100644
--- a/vscode/webpack.config.js
+++ b/vscode/webpack.config.js
@@ -87,4 +87,47 @@ const webExtensionConfig = {
},
};
-module.exports = [ webExtensionConfig ];
\ No newline at end of file
+/** @type WebpackConfig */
+const nodeExtensionConfig = {
+ mode: 'none',
+ target: 'node', // extensions run in a Node.js context
+ entry: {
+ 'extension': './src/extension.ts'
+ },
+ output: {
+ filename: '[name].js',
+ path: path.join(__dirname, './dist'),
+ libraryTarget: 'commonjs2',
+ devtoolModuleFilenameTemplate: '../[resource-path]'
+ },
+ resolve: {
+ mainFields: ['main', 'module'],
+ extensions: ['.ts', '.js'],
+ },
+ module: {
+ rules: [{
+ test: /\.ts$/,
+ exclude: /node_modules/,
+ use: [{
+ loader: 'ts-loader'
+ }]
+ },
+ {
+ test: /\.html/,
+ type: 'asset/source'
+ }
+ ]
+ },
+ externals: {
+ 'vscode': 'commonjs vscode',
+ },
+ performance: {
+ hints: false
+ },
+ devtool: 'nosources-source-map',
+ infrastructureLogging: {
+ level: "log",
+ },
+};
+
+module.exports = [ webExtensionConfig, nodeExtensionConfig ];
\ No newline at end of file