├── .circleci └── config.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── TODO.md ├── build.bat ├── check.bat ├── docs ├── CNAME ├── _config.yml ├── index.md └── jessie-frame │ ├── index.html │ ├── jessica.36eee4dc.js │ └── jessica.36eee4dc.js.map ├── lang ├── browser │ ├── .gitignore │ ├── build.bat │ ├── index.html │ ├── jessica.js │ ├── package-lock.json │ ├── package.json │ ├── supported.bat │ └── whitelist.js ├── c │ ├── .gitignore │ ├── .suffix │ ├── _jsestate.h │ ├── config.h │ ├── jessica.c │ ├── jessica.h │ ├── jesspipe.bat │ ├── jesspipe.c │ ├── jsenan.h │ ├── jseptr.h │ └── supported.bat └── nodejs │ ├── .gitignore │ ├── .suffix │ ├── __tests__ │ ├── insulate.ts │ ├── interp.ts │ ├── jessie-expr.ts │ ├── jessie.ts │ ├── json.ts │ ├── justin.ts │ ├── parser-utils.ts │ ├── peg.ts │ └── translate.ts │ ├── babel-tessie.js │ ├── babel.config.js │ ├── build.bat │ ├── check.bat │ ├── cmparse.bat │ ├── cmparse.js │ ├── cmparse.ts │ ├── jessieDefaults.js │ ├── jessieDefaults.js.ts │ ├── jessparse.bat │ ├── jessparse.js │ ├── jessparse.ts │ ├── jesspipe.bat │ ├── jesspipe.js │ ├── jesspipe.ts │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── supported.bat │ ├── tessc.bat │ ├── tessc.js │ ├── tsconfig.app.json │ └── tsconfig.test.json ├── lib ├── boot-jessica.js ├── boot-jessica.js.ts ├── boot-peg.js ├── boot-peg.js.ts ├── boot-pegast.js ├── boot-pegast.js.ts ├── emit-c.js ├── emit-c.js.ts ├── importer.js ├── importer.js.ts ├── indent.js ├── indent.js.ts ├── interp-jessie.js ├── interp-jessie.js.ts ├── interp-json.js ├── interp-json.js.ts ├── interp-justin.js ├── interp-justin.js.ts ├── interp-utils.js ├── interp-utils.js.ts ├── main-jesspipe.js ├── main-jesspipe.js.ts ├── peg.d.ts ├── quasi-chainmail.js ├── quasi-chainmail.js.ts ├── quasi-insulate.js ├── quasi-insulate.js.ts ├── quasi-jessie-module.js ├── quasi-jessie-module.js.ts ├── quasi-jessie.js ├── quasi-jessie.js.ts ├── quasi-json.js ├── quasi-json.js.ts ├── quasi-justin.js ├── quasi-justin.js.ts ├── quasi-peg.js ├── quasi-peg.js.ts ├── quasi-utils.js ├── quasi-utils.js.ts ├── readInput.js ├── readInput.js.ts ├── repl.js ├── repl.js.ts ├── rewrite-define.js ├── rewrite-define.js.ts ├── tag-string.js ├── tag-string.js.ts ├── translate.js └── translate.js.ts ├── makedocs.bat ├── package-lock.json ├── package.json ├── tsconfig.json ├── tslint.json ├── typings ├── jessie-proposed.d.ts ├── lib.jessie.d.ts ├── ses-proposed.d.ts └── ses.d.ts └── unpublished ├── agoric-jessie-0.1.0.tgz ├── agoric-jessie-0.2.0.tgz ├── types-agoric__harden-1.0.0.tgz ├── types-agoric__jessie-1.0.0.tgz └── types-michaelfig__slog-1.0.0.tgz /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build-buildpack: 4 | docker: 5 | - image: circleci/buildpack-deps:stretch 6 | steps: 7 | - checkout 8 | - run: 9 | name: Check that all the languages interlock 10 | command: ./check.bat 11 | build-node: 12 | docker: 13 | - image: circleci/node 14 | steps: 15 | - checkout 16 | - run: 17 | name: Check that all the languages interlock 18 | command: ./check.bat 19 | workflows: 20 | version: 2 21 | build: 22 | jobs: 23 | - build-buildpack 24 | - build-node 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | checkout/ 2 | node_modules/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.suggest.names": false, 3 | "javascript.suggest.autoImports": false, 4 | "tslint.jsEnable": true, 5 | "typescript.suggest.autoImports": false, 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jessica - Jessie (secure distributed Javascript) Compiler Architecture 2 | 3 | [![CircleCI](https://circleci.com/gh/michaelfig/jessica.svg?style=svg)](https://circleci.com/gh/michaelfig/jessica) 4 | 5 | **NOTE: Not yet production ready. Jessica is still being bootstrapped now, but you are welcome to contribute and discuss!** 6 | 7 | This directory contains Jessica, a compiler architecture implementing [Jessie](https://github.com/Agoric/Jessie). In short, Jessie is a secure subset of Javascript that enables interconnected distributed applications running on different targets (such as other threads, OS processes, device drivers, networked hosts). It does this without granting excess authority to any Jessie submodule. 8 | 9 | Jessica is a metacircular Jessie: it is a library designed to compile or interpret itself. Jessica consists of its own Jessie submodules in `lib`, and language platform-specific sources in `lang/*`. For each target, Jessica is an extension language library, as well as `jesspipe`, an executable (based on the Jessica library) for running Jessie modules. 10 | 11 | The goal of Jessica is to be broad: providing the minimal Jessie environment for as many different language platforms as possible. 12 | 13 | ## Grammar 14 | 15 | For those coming to Jessica in search of the specific Jessie grammar, the sources can be found in (from bottom layer to top layer): 16 | 17 | * [JSON](lib/quasi-json.js.ts) the base object grammar. 18 | * [Justin](lib/quasi-justin.js.ts) the pure expression grammar (no loops). 19 | * [Jessie](lib/quasi-jessie.js.ts) the top-level module grammar. 20 | 21 | All of these grammars are written in a flavour of PEG (Parsing Expression Grammar), which is [specified in its own syntax](lib/quasi-peg.js.ts). 22 | 23 | ## Implementations 24 | 25 | It is intended for you to use Jessica's extension language library in your favourite language to add Jessie scripting capabilities in an idiomatic way. 26 | 27 | Try running `check.bat` to verify all the combinations of Jessica that are supported by your operating system and build tools. 28 | 29 | Here are notes for how to use the Jessica API for a given language platform, including how to develop the Jessica library using its source code and the `jesspipe` interpreter. 30 | 31 | ### Node.js 8.5+ 32 | 33 | Node.js 8.5 and above can use Jessica directly as ES Modules (since Jessie is a subset of Javascript, with some additional endowed modules). 34 | 35 | Until Node.js API documents are completed, you can read `lang/nodejs/jesspipe.js` for an example of how to use the Jessie API. 36 | 37 | To run a `jesspipe` module, do: 38 | 39 | ```sh 40 | $ ./lang/nodejs/jesspipe.bat MODULE -- INFILES... 41 | ``` 42 | 43 | or on Windows: 44 | 45 | ```sh 46 | C:\> lang\nodejs\jesspipe.bat MODULE -- INFILES... 47 | ``` 48 | 49 | ### C99 50 | 51 | FIXME: not yet 52 | 53 | The C programming language, (ISO standard C 9899:1999) is a target language for the Jessica library. 54 | 55 | Read `lang/c/jessica.h` for information on the C programming API provided by `lang/c/jessica.c`. 56 | 57 | To run a `jesspipe` module, do: 58 | 59 | ```sh 60 | $ ./lang/c/jesspipe.bat MODULE -- INFILES... 61 | ``` 62 | 63 | ### Another language not listed 64 | 65 | See the next sections for instructions on how to bootstrap Jessica for a new language. 66 | 67 | ## Bootstrapping Jessica for Interpreted Languages 68 | 69 | This is how to bootstrap Jessica for an interpreted language: 70 | 71 | 1. Create a new `./lang/NEWLANG` directory, and adapt all the files in a similar `./lang/OLDLANG` directory. 72 | 73 | 2. Edit the main entry point, `./lang/NEWLANG/jesspipe.bat`. 74 | 75 | 3. Run the combinatorial `check.bat` to verify that your new implementation works with all the Jessica features: 76 | 77 | ```sh 78 | $ ./check.bat NEWLANG 79 | ``` 80 | 81 | 4. Commit all the files in `./lang/NEWLANG` to version control. 82 | 83 | ## Bootstrapping Jessica for Compiled Languages 84 | 85 | This is how to bootstrap Jessica for a compiled language 86 | implementation from a related language (or itself): 87 | 88 | 1. Create a new `./lib/emit-NEWLANG.js.ts` file, based off of an existing similar `./lib/emit-OLDLANG.js.ts`. If you really don't want to use Typescript, you can just create `./lib/emit-NEWLANG.js` directly. 89 | 90 | 2. Run your existing `jesspipe` to execute your emitter to translate the Jessica `./lib` master sources into a library in your new language's syntax, where `SRC` is the source file suffix for your language: 91 | 92 | ```sh 93 | $ mkdir ./lang/NEWLANG 94 | $ ./lang/OLDLANG/jesspipe.bat ./lib/emit-NEWLANG.js -- ./lib/*.js > ./lang/NEWLANG/jessica.SRC 95 | ``` 96 | 97 | 3. Write a `./lang/NEWLANG/jesspipe.SRC` main program, along with any support files in `./lang/NEWLANG` designed for linking against `./lang/NEWLANG/jessica.SRC`. 98 | 99 | 4. Create an executable `./lang/NEWLANG/jesspipe.bat` script to compile and link the`./lang/NEWLANG/jesspipe.exe` main program from `./lang/NEWLANG/*.SRC` using your new language's default compiler if the executable it is out-of-date, and then have `jesspipe.bat` run it. 100 | 101 | 5. Run the combinatorial `check.bat` to verify that your new implementation works with all the Jessica features: 102 | 103 | ```sh 104 | $ ./check.bat 105 | ``` 106 | 107 | 6. Be sure to commit all the `./lang/NEWLANG/*.SRC` source files into version control (including `jessica.SRC` but not compiler output files), so that you can provide these bootstrap files to people who only have your language's compiler to bootstrap Jessica. 108 | 109 | 110 | # Acknowledgements 111 | 112 | Much appreciation to Mark S. Miller (@erights) and the [Agoric team](https://agoric.com/) for spearheading the work on secure Javascript standards and Jessie in particular. 113 | 114 | Inspiration and a lot of implementation ideas taken from Ian Piumarta's [Maru](http://piumarta.com/software/maru/) project, as well as other work from the [Viewpoints Research Institute team](http://vpri.org/). 115 | 116 | Have fun! 117 | [Michael FIG](mailto:michael+jessica@fig.org), 2019-01-03 118 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * Remove insulate in favour of: 2 | 3 | import { makeModuleMembrane } from '@agoric/jessie'; 4 | const { imported, exportable } = makeModuleMembrane(); 5 | export const foo = exportable({}); 6 | const bar = imported($i_bar); 7 | export default exportable(baz); 8 | 9 | * General format of the interpreter/compilers in lang. 10 | 11 | Convert lib to bundled ASTs. 12 | Interpret bundled ASTs. 13 | Futamura Projection 14 | 15 | * '\xFF' syntax for Justin? 16 | 17 | * Update `writeOutput` and `readInput` to be incremental (i.e. it can be called again to append to output or read next line from stdin). Use them to implement a true repl. 18 | 19 | * Snarf the whitelist.js libraries for nodejs. 20 | 21 | * Use full SES! 22 | 23 | * boot-peg doesn't identify syntax errors correctly. Problem with tokenTypeList, which needs to match literals. 24 | 25 | * Better document quasi-peg.js, especially the < foo > syntax and SKIP. 26 | 27 | * Refactor and clean up boot-peg.js. peg-utils.js is a possibility. 28 | 29 | * Consider allowing `writeOutput` to create files if they are whitelisted in the `jesspipe` command line options. 30 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | :; cd `dirname "$0"` || exit $? 2 | :; exec ./lang/nodejs/tessc.bat lib/*.js.ts 3 | :; exit $? 4 | -------------------------------------------------------------------------------- /check.bat: -------------------------------------------------------------------------------- 1 | :; thisdir=`dirname "$0"`; status=0; # set -x 2 | :; langs=${1+"$@"} 3 | :; test -n "$langs" || langs=`cd "$thisdir"/lang && echo *` 4 | :; mkdir -p "$thisdir/checkout" 5 | :; for lang in $langs; do 6 | :; echo "==== Checking for $lang support" 7 | :; "$thisdir/lang/$lang/supported.bat" || continue 8 | :; build="$thisdir/lang/$lang/build.bat" 9 | :; test ! -f "$build" || "$build" || { status=$?; break; } 10 | :; check="$thisdir/lang/$lang/check.bat" 11 | :; test ! -f "$check" || "$check" || { status=$?; break; } 12 | :; test -f "$thisdir/lang/$lang/jesspipe.bat" || continue 13 | :; for emitter in "$thisdir"/lib/emit-*.js; do 14 | :; target=`echo "$emitter" | sed -e 's/.*\/emit-//; s/\.js$//'` 15 | :; sfx=`cat "$thisdir/lang/$target/.suffix"` 16 | :; out="$thisdir/checkout/${lang}2$target.$sfx" 17 | :; echo "==== Run $lang's jesspipe $emitter to generate $out"; 18 | :; "$thisdir/lang/$lang/jesspipe.bat" "$emitter" -- "$thisdir"/lib/*.js > "$out" || { status=$?; break; } 19 | :; diff -u "$thisdir/lang/$target/jessica.$sfx" "$out" || { status=$?; break; } 20 | :; out="$thisdir/checkout/m${lang}2$target.$sfx" 21 | :; echo "==== Run $lang's meta jesspipe $emitter to generate ${out}" 22 | :; "$thisdir/lang/$lang/jesspipe.bat" "$thisdir/lib/main-jesspipe.js" -- "$emitter" -- "$thisdir"/lib/*.js > "$out" || { status=$?; break; } 23 | :; diff -u "$thisdir/lang/$target/jessica.$sfx" "$out" || { status=$?; break; } 24 | :; done 25 | :; done 26 | :; if test $status -eq 0; then 27 | :; echo 1>&2 '=== ALL TESTS PASSED ===' 28 | :; else 29 | :; echo 1>&2 '=== TESTS FAILED ===' 30 | :; fi 31 | :; exit $status 32 | @echo off 33 | set thisdir=%~dp0 34 | set arguments=%* 35 | echo Not implemented for Windows yet. 36 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | jessica.agoric.com -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## Jessie-Frame 2 | 3 | The [Jessie-Frame](https://www.smotaal.io/#/meta/@ses/Jessie-Frame) proposal is a starting point for the full-fledged SES frame. You can look at a [test implementation based on Jessica](jessie-frame/). 4 | -------------------------------------------------------------------------------- /docs/jessie-frame/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Jessica Browser 5 | 6 | 7 | 10 | 11 |

Browser-Based Jessica

12 | 13 |

Write your Jessie code below:

14 | 22 | 23 | 24 | 28 | 29 |
 30 |         
31 | 32 | 64 | 65 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /lang/browser/.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | dist/ 3 | -------------------------------------------------------------------------------- /lang/browser/build.bat: -------------------------------------------------------------------------------- 1 | :; cd `dirname "$0"` || exit $? 2 | :; (cd ../.. && { test -d ./node_modules/@agoric/jessie || npm install; }) || exit $? 3 | :; test -d ./node_modules/parcel-bundler || npm install || exit $? 4 | :; rm -rf dist 5 | :; exec ./node_modules/.bin/parcel build --no-minify --public-url=./ --global=jessica ./index.html 6 | :; exit $? 7 | -------------------------------------------------------------------------------- /lang/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Jessica Browser 5 | 6 | 7 | 12 | 13 |

Browser-Based Jessica

14 | 15 |

Write your Jessie code below:

16 | 24 | 25 | 26 | 30 | 31 |
 32 |         
33 | 34 | 59 | 60 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /lang/browser/jessica.js: -------------------------------------------------------------------------------- 1 | import SES from './node_modules/ses/dist/ses.esm.js'; 2 | import {buildWhitelist} from './whitelist.js'; 3 | 4 | export {translate, parse} from '../../lib/translate.js'; 5 | 6 | export { SES }; 7 | export * from '@agoric/jessie'; 8 | export { slog } from '@michaelfig/slog'; 9 | export const whitelist = buildWhitelist(); 10 | -------------------------------------------------------------------------------- /lang/browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jessica-lang-browser", 3 | "version": "0.0.1", 4 | "description": "Jessica implementation for the browser", 5 | "main": "dist/jessica.js", 6 | "devDependencies": { 7 | "parcel-bundler": "^1.12.4", 8 | "ses": "^0.6.4" 9 | }, 10 | "scripts": { 11 | "build": "build.bat", 12 | "start": "parcel serve -d .cache/dist --global jessica index.html" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/michaelfig/jessica.git" 17 | }, 18 | "author": "Michael FIG ", 19 | "license": "Apache-2.0", 20 | "bugs": { 21 | "url": "https://github.com/michaelfig/jessica/issues" 22 | }, 23 | "homepage": "https://github.com/michaelfig/jessica#readme", 24 | "dependencies": {} 25 | } 26 | -------------------------------------------------------------------------------- /lang/browser/supported.bat: -------------------------------------------------------------------------------- 1 | :; node --version; exit $? 2 | @echo off 3 | set thisdir=%~dp0 4 | set arguments=%* 5 | node --version 6 | -------------------------------------------------------------------------------- /lang/browser/whitelist.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2011 Google Inc. 2 | // Copyright (C) 2018 Agoric 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | /** 17 | * Based on https://github.com/Agoric/SES/blob/master/src/bundle/whitelist.js 18 | * 19 | * @author Mark S. Miller, 20 | */ 21 | export function buildWhitelist() { 22 | "use strict"; 23 | 24 | var j = true; // included in the Jessie runtime 25 | 26 | // These are necessary for most Javascript environments. 27 | const anonIntrinsics = { 28 | ThrowTypeError: {}, 29 | IteratorPrototype: { 30 | next: '*', 31 | constructor: false, 32 | }, 33 | ArrayIteratorPrototype: {}, 34 | StringIteratorPrototype: {}, 35 | MapIteratorPrototype: {}, 36 | SetIteratorPrototype: {}, 37 | 38 | GeneratorFunction: {}, 39 | AsyncGeneratorFunction: {}, 40 | }; 41 | 42 | const namedIntrinsics = { 43 | // In order according to 44 | // http://www.ecma-international.org/ecma-262/ with chapter 45 | // numbers where applicable 46 | 47 | // 18 The Global Object 48 | 49 | Infinity: j, 50 | NaN: j, 51 | undefined: j, 52 | 53 | eval: j, // realms-shim depends on having indirect eval in the globals 54 | 55 | // 19 Fundamental Objects 56 | 57 | Object: { // 19.1 58 | freeze: j, 59 | is: j, // ES-Harmony 60 | preventExtensions: j, 61 | seal: j, 62 | entries: j, 63 | keys: j, 64 | values: j, 65 | prototype: { 66 | toString: '*', 67 | }, 68 | }, 69 | 70 | Boolean: { // 19.3 71 | }, 72 | 73 | // 20 Numbers and Dates 74 | 75 | Number: { // 20.1 76 | isFinite: j, // ES-Harmony 77 | isNaN: j, // ES-Harmony 78 | isSafeInteger: j, // ES-Harmony 79 | MAX_SAFE_INTEGER: j, // ES-Harmony 80 | MIN_SAFE_INTEGER: j, // ES-Harmony 81 | }, 82 | 83 | Math: { // 20.2 84 | E: j, 85 | PI: j, 86 | 87 | abs: j, 88 | ceil: j, 89 | floor: j, 90 | max: j, 91 | min: j, 92 | round: j, 93 | trunc: j // ES-Harmony 94 | }, 95 | 96 | // 21 Text Processing 97 | 98 | String: { // 21.2 99 | fromCharCode: j, 100 | raw: j, // ES-Harmony 101 | prototype: { 102 | charCodeAt: j, 103 | endsWith: j, // ES-Harmony 104 | indexOf: j, 105 | lastIndexOf: j, 106 | slice: j, 107 | split: j, 108 | startsWith: j, // ES-Harmony 109 | 110 | length: '*', 111 | } 112 | }, 113 | 114 | // 22 Indexed Collections 115 | 116 | Array: { // 22.1 117 | from: j, 118 | isArray: j, 119 | of: j, // ES-Harmony? 120 | prototype: { 121 | filter: j, 122 | forEach: j, 123 | indexOf: j, 124 | join: j, 125 | lastIndexOf: j, 126 | map: j, 127 | pop: j, 128 | push: j, 129 | reduce: j, 130 | reduceRight: j, 131 | slice: j, 132 | 133 | // 22.1.4 instances 134 | length: '*', 135 | }, 136 | }, 137 | 138 | // 23 Keyed Collections all ES-Harmony 139 | 140 | Map: { // 23.1 141 | prototype: { 142 | clear: j, 143 | delete: j, 144 | entries: j, 145 | forEach: j, 146 | get: j, 147 | has: j, 148 | keys: j, 149 | set: j, 150 | values: j 151 | } 152 | }, 153 | 154 | Set: { // 23.2 155 | prototype: { 156 | add: j, 157 | clear: j, 158 | delete: j, 159 | entries: j, 160 | forEach: j, 161 | has: j, 162 | keys: j, 163 | values: j 164 | } 165 | }, 166 | 167 | WeakMap: { // 23.3 168 | prototype: { 169 | // Note: coordinate this list with maintenance of repairES5.js 170 | delete: j, 171 | get: j, 172 | has: j, 173 | set: j 174 | } 175 | }, 176 | 177 | WeakSet: { // 23.4 178 | prototype: { 179 | add: j, 180 | delete: j, 181 | has: j 182 | } 183 | }, 184 | 185 | // 24.4 TODO: Omitting Atomics for now 186 | 187 | JSON: { // 24.5 188 | parse: j, 189 | stringify: j 190 | }, 191 | 192 | Promise: { // 25.4 193 | all: j, 194 | race: j, 195 | reject: j, 196 | resolve: j, 197 | prototype: { 198 | catch: j, 199 | then: j, 200 | } 201 | }, 202 | }; 203 | 204 | return {namedIntrinsics, anonIntrinsics}; 205 | } 206 | -------------------------------------------------------------------------------- /lang/c/.gitignore: -------------------------------------------------------------------------------- 1 | jesspipe.exe* 2 | testc.* 3 | -------------------------------------------------------------------------------- /lang/c/.suffix: -------------------------------------------------------------------------------- 1 | c 2 | -------------------------------------------------------------------------------- /lang/c/_jsestate.h: -------------------------------------------------------------------------------- 1 | #define JSE_PASTE(a, b) a ## b 2 | typedef struct JSE_PASTE(_, JSEState) { 3 | JSE *stack; 4 | #if JSESTATE_OBJTABLE 5 | JSEObjHeader *objTable; 6 | void **objFreeList; 7 | #endif 8 | size_t stackSize; 9 | size_t tos; 10 | } *JSEState; 11 | 12 | // Drop N values from top of stack. 13 | // Pick Nth from stack. 14 | 15 | // Push undefined on stack. 16 | // Push null on stack. 17 | // Push boolean on stack. 18 | // Push integer on stack. 19 | // Push double on stack. 20 | // Push new byte array on stack. 21 | // Push new string. 22 | // Push new empty array object on stack. 23 | // Push new empty object on stack. 24 | // Read index operation. 25 | 26 | // Get type of N stack. 27 | // Convert N stack as integer. 28 | // Convert N stack as boolean. 29 | // Convert N stack as double. 30 | // Convert N stack as byte array. 31 | // Convert N stack as string. 32 | // Convert N stack as object (vtable, self). 33 | -------------------------------------------------------------------------------- /lang/c/config.h: -------------------------------------------------------------------------------- 1 | /* #define JSE_PTR_T void * */ 2 | #define JSE_NAN_T double 3 | -------------------------------------------------------------------------------- /lang/c/jessica.c: -------------------------------------------------------------------------------- 1 | /* FIXME: Stub */ 2 | -------------------------------------------------------------------------------- /lang/c/jessica.h: -------------------------------------------------------------------------------- 1 | #ifndef _JESSICA_H 2 | # define _JESSICA_H 1 3 | #include /* The word, or double-word as the case may be. */ 4 | 5 | #if defined(JSE_NAN_T) 6 | # include 7 | #elif defined(JSE_PTR_T) 8 | # include 9 | #else 10 | # error "You must specify the type of JSE" 11 | #endif 12 | #endif /* _JESSICA_H */ 13 | -------------------------------------------------------------------------------- /lang/c/jesspipe.bat: -------------------------------------------------------------------------------- 1 | :; thisdir=`dirname "$0"`;srcs="jesspipe.c jessica.c";exe="jesspipe.exe" 2 | :; (cd "$thisdir" && for src in $srcs *.h `basename "$0"`; do test "$exe" -nt "$src" || { cc -g -O2 -I. -o "$exe" $srcs; exit $?; }; done) || exit $? 3 | :; exec "$thisdir/$exe" ${1+"$@"} 4 | :; echo 'Failed to exec' 1>&2; exit 1 5 | @echo off 6 | set thisdir=%~dp0 7 | set arguments=%* 8 | echo Windows C is not implemented yet! 9 | -------------------------------------------------------------------------------- /lang/c/jesspipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int 6 | main(int argc, char **argv) { 7 | JSEState js = calloc(sizeof(*js), 1); 8 | fprintf(stderr, "FIXME: Would do something, other than alloc %p\n", js); 9 | free(js); 10 | printf("/* FIXME: Stub */\n"); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /lang/c/jsenan.h: -------------------------------------------------------------------------------- 1 | #ifndef _JSENAN_H 2 | # define _JSENAN_H 1 3 | 4 | #include 5 | 6 | #ifndef JSE_NAN_T 7 | #define JSE_NAN_T double 8 | #endif 9 | typedef JSE_NAN_T JSE; 10 | 11 | // The technique used to tag double/floats is called "NaN-boxing". 12 | // Essentially, IEEE-754 provides payload bits for Not-a-Number values, 13 | // allowing to embed different bit patterns as part of a legitimate 14 | // floating-point value. 15 | 16 | #define JSESTATE_OBJTABLE 1 17 | typedef struct _JSEObjHeader { 18 | JSE _vtable; 19 | void *body; 20 | size_t alloced; 21 | } JSEObjHeader; 22 | 23 | #define JSEState JSENanState 24 | #include "_jsestate.h" 25 | #endif /* _JSEDBL_H */ 26 | -------------------------------------------------------------------------------- /lang/c/jseptr.h: -------------------------------------------------------------------------------- 1 | #ifndef _JSEPTR_H 2 | #define _JSEPTR_H 1 3 | // For situations where floating-point performance is not needed, 4 | // we don't have an object table, and we add a header to 16-bit-aligned 5 | // pointers, tagging integers in the low bit. 6 | 7 | #ifndef JSE_PTR_T 8 | #define JSE_PTR_T void * 9 | #endif 10 | typedef JSE_PTR_T JSE; 11 | 12 | #define JSESTATE_OBJTABLE 0 13 | typedef struct _JSEObjHeader { 14 | JSE _vtable; 15 | size_t alloced; 16 | } JSEObjHeader; 17 | 18 | #define JSEState JSEPtrState 19 | #include "_jsestate.h" 20 | #endif /* _JSEPTR_H */ 21 | -------------------------------------------------------------------------------- /lang/c/supported.bat: -------------------------------------------------------------------------------- 1 | :; thisdir=`dirname "$0"`; echo "int main() {return 1;}" > "$thisdir/testc.c" && cc -g -O2 "$thisdir/testc.c" -o "$thisdir/testc.exe" && "$thisdir/testc.exe" 2 | :; status=$?; rm -rf "$thisdir"/testc.*; exit $status 3 | @echo off 4 | set thisdir=%~dp0 5 | set arguments=%* 6 | echo Not implemented for Windows 7 | exit 1 8 | -------------------------------------------------------------------------------- /lang/nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | tsout/ 2 | -------------------------------------------------------------------------------- /lang/nodejs/.suffix: -------------------------------------------------------------------------------- 1 | js 2 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/insulate.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import insulate from '@agoric/jessie/lib/insulate.js'; 3 | 4 | test('insulate(primitives)', () => { 5 | expect(insulate('foo')).toBe('foo'); 6 | expect(insulate(123)).toBe(123); 7 | expect(insulate(0.995)).toBe(0.995); 8 | expect(insulate(false)).toBe(false); 9 | expect(insulate(undefined)).toBe(undefined); 10 | expect(insulate(null)).toBe(null); 11 | expect(insulate((arg: number) => arg)(123)).toBe(123); 12 | }); 13 | 14 | test('insulate(identity)', () => { 15 | const f = (a: any) => a; 16 | const f2 = insulate(f); 17 | expect(f2).not.toBe(f); 18 | const obj = {num: 123}; 19 | expect(f(obj)).toBe(obj); 20 | expect(f2(obj)).toBe(obj); 21 | 22 | // Running another insulate() will create new maps, but 23 | // they will unwrap properly on exit. 24 | const obj2: Record = {num: 345}; 25 | const f3 = insulate(f2); 26 | obj2.num = 456; 27 | expect(f3(obj2)).toBe(f3(obj2)); 28 | obj2.num = 567; 29 | // Mutable, and extensible. 30 | expect(obj2.abc = 'aaa').toBe('aaa'); 31 | expect(f3(obj2)).toBe(obj2); 32 | expect(f3(obj)).toBe(obj); 33 | }); 34 | 35 | test('insulate(protection)', () => { 36 | const mutate = insulate((a: any) => a.bar = 123); 37 | const obj: Record = {bar: 123}; 38 | expect(() => mutate(obj)).toThrow(/^Cannot set property "bar" on insulated object/); 39 | expect(obj.bar).toBe(123); 40 | obj.bar = 234; 41 | expect(obj.bar).toBe(234); 42 | obj.other = 'xyz'; 43 | expect(obj.other).toBe('xyz'); 44 | 45 | const create = insulate((): Record => ({})); 46 | const obj2 = create(); 47 | expect(() => obj2.abc = 'aaa').toThrow(/^Cannot set property "abc"/); 48 | }); 49 | 50 | test('insulate(this-capture)', () => { 51 | const captureThis = insulate((inObj: any) => { 52 | const obj: any = { 53 | a() { return 'innocuous'; }, 54 | priv: 'THIS VALUE MUST NOT LEAK!', 55 | }; 56 | // Idiomatic usage of this-capture. 57 | expect(obj.a()).toBe('innocuous'); 58 | expect([].slice(0)).toEqual([]); 59 | // this-capture probably by accident. 60 | obj.a = inObj.b; 61 | obj.a(); 62 | return 'foo'; 63 | }); 64 | let exfiltrated = 'nothing leaked'; 65 | function getPriv() { 66 | // Use our captured this to get ahold of the object. 67 | exfiltrated = this.priv; 68 | } 69 | 70 | // Throw an unwrapped error from the getPriv function. 71 | expect(() => captureThis({ b: getPriv })).toThrow(TypeError); 72 | expect(exfiltrated).toBe('nothing leaked'); 73 | 74 | // Create an insulated function who attempts to capture this. 75 | let exfiltrated2 = 'still nothing leaked'; 76 | const getPriv2 = insulate(function() { 77 | exfiltrated2 = this.priv2; 78 | return 'really innocuous'; 79 | }); 80 | const obj2 = { 81 | a() { return 'still innocuous'; }, 82 | priv2: 'THIS VALUE ALSO MUST NOT LEAK!', 83 | }; 84 | expect(obj2.a()).toBe('still innocuous'); 85 | obj2.a = getPriv2; 86 | expect(() => obj2.a()).toThrow(/undefined/); 87 | expect(exfiltrated2).toBe('still nothing leaked'); 88 | }); 89 | 90 | test('insulate(avoid doubling)', () => { 91 | const cnst = insulate({toString() { return 'hello'; }}); 92 | const a = insulate((arg) => arg === cnst); 93 | const b = insulate((arg) => a(arg)); 94 | expect(a(cnst)).toBeTruthy(); 95 | expect(b(cnst)).toBeTruthy(); 96 | }); 97 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/interp.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import {insulate} from '@agoric/jessie'; 3 | import {applyMethod, setComputedIndex} from '../jessieDefaults.js'; 4 | 5 | import bootJessica from '../../../lib/boot-jessica.js'; 6 | import repl from '../../../lib/repl.js'; 7 | 8 | import * as fs from 'fs'; 9 | 10 | let capturedData = ''; 11 | const captureWrite = (file: string, data: string) => { 12 | capturedData += data; 13 | }; 14 | 15 | function doRead(file: string) { 16 | return fs.readFileSync(file, { encoding: 'latin1' }); 17 | } 18 | 19 | function dontRead(file: string): never { 20 | throw Error(`Refusing to read ${file}`); 21 | } 22 | 23 | function defaultJessica(reader: (file: string) => string) { 24 | const jessica = bootJessica(applyMethod, reader, setComputedIndex); 25 | return jessica; 26 | } 27 | 28 | function defaultRunModule(reader: (file: string) => string, writer: (file: string, data: string) => void) { 29 | const jessica = defaultJessica(doRead); 30 | const doEval = (src: string, uri?: string) => 31 | jessica.runModule(src, {}, {scriptName: uri}); 32 | const deps = {applyMethod, readInput: reader, setComputedIndex, writeOutput: writer}; 33 | return (module: string, argv: string[]) => 34 | repl(deps, doEval, module, argv); 35 | } 36 | 37 | test('sanity', () => { 38 | const jessica = defaultJessica(dontRead); 39 | expect(jessica.runModule('export default 123;')).toBe(123); 40 | }); 41 | 42 | test('repl', () => { 43 | const runModule = defaultRunModule(doRead, captureWrite); 44 | capturedData = ''; 45 | expect(runModule('../../lib/emit-c.js', [])).toBe(undefined); 46 | expect(capturedData).toBe('/* FIXME: Stub */\n'); 47 | 48 | if (false) { 49 | capturedData = ''; 50 | expect(runModule('../../lib/main-jesspipe.js', ['--', '../../lib/emit-c.js'])).toBe(undefined); 51 | expect(capturedData).toBe('/* FIXME: Stub */\n'); 52 | } 53 | }); 54 | 55 | test('quasi', () => { 56 | const jessica = defaultJessica(dontRead); 57 | const runModule = (src: string) => jessica.runModule(src, {insulate}); 58 | const tag = (template: TemplateStringsArray, ...args: any[]) => 59 | args.reduce((prior, arg, i) => prior + String(arg) + template[i + 1], template[0]); 60 | 61 | expect(runModule('export default insulate(() => `abc 123`);')()) 62 | .toBe('abc 123'); 63 | 64 | expect(runModule('export default insulate((arg) => `abc ${arg} 456`);')(123)) 65 | .toBe('abc 123 456'); 66 | 67 | expect(runModule('export default insulate((tag) => tag`My string`);')(tag)) 68 | .toBe('My string'); 69 | 70 | expect(runModule('export default insulate((tag, arg) => tag`My template ${arg}`);')(tag, 'hello')) 71 | .toBe('My template hello'); 72 | 73 | }); 74 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/jessie-expr.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import bootPeg from '../../../lib/boot-peg.js'; 4 | import bootPegAst from '../../../lib/boot-pegast.js'; 5 | import makePeg from '../../../lib/quasi-peg.js'; 6 | 7 | import makeInsulatedJessie from '../../../lib/quasi-insulate.js'; 8 | import makeJessie from '../../../lib/quasi-jessie.js'; 9 | import makeJSON from '../../../lib/quasi-json.js'; 10 | import makeJustin from '../../../lib/quasi-justin.js'; 11 | import {ast, makeParser} from './parser-utils'; 12 | 13 | function defaultJessieExprParser() { 14 | const pegTag = bootPeg(makePeg, bootPegAst); 15 | const jsonTag = makeJSON(pegTag); 16 | const justinTag = makeJustin(pegTag.extends(jsonTag)); 17 | const [rawJessieTag] = makeJessie(pegTag, pegTag.extends(justinTag)); 18 | const jessieTag = makeInsulatedJessie(pegTag, pegTag.extends(rawJessieTag)); 19 | return makeParser(jessieTag[1]); 20 | } 21 | 22 | test('tildot', () => { 23 | const parse = defaultJessieExprParser(); 24 | expect(parse(`abc~.a1`)).toEqual( 25 | ast(0, 'getLater', ast(0, 'use', 'abc'), 'a1') 26 | ); 27 | 28 | expect(parse(`abc~.(a, 1)`)).toEqual( 29 | ast(0, 'callLater', ast(0, 'use', 'abc'), ast(5, ast(6, 'use', 'a'), ast(9, 'data', 1))) 30 | ); 31 | 32 | const oldError = console.error; 33 | try { 34 | console.error = (): any => undefined; 35 | expect(() => parse(`abc~.1`)).toThrowError('Syntax error at 3 "~" #0:3'); 36 | } finally { 37 | console.error = oldError; 38 | } 39 | }); 40 | 41 | test('get/set', () => { 42 | const parse = defaultJessieExprParser(); 43 | expect(parse(`function doit() { return bar.abcd; }`)).toEqual( 44 | ast(0, 'functionExpr', ast(9, 'def', 'doit'), [], ast(16, 'block', [ 45 | ast(18, 'return', ast(25, 'get', ast(25, 'use', 'bar'), 'abcd')) 46 | ]))); 47 | expect(parse(`function doit() { a ? b : c; }`)).toEqual( 48 | ast(0, 'functionExpr', ast(9, 'def', 'doit'), [], ast(16, 'block', [ 49 | ast(18, 'cond', ast(18, 'use', 'a'), ast(22, 'use', 'b'), ast(26, 'use', 'c')) 50 | ]))); 51 | expect(parse(`function doit() { foo[1] = c; }`)).toEqual( 52 | ast(0, 'functionExpr', ast(9, 'def', 'doit'), [], ast(16, 'block', [ 53 | ast(18, '=', ast(18, 'index', ast(18, 'use', 'foo'), ast(22, 'data', 1)), ast(27, 'use', 'c')) 54 | ]))); 55 | }); 56 | 57 | test('quasi', () => { 58 | const parse = defaultJessieExprParser(); 59 | expect(parse('myTag`hello ${{MODULE}}: ${e}`')).toEqual( 60 | ast(0, 'tag', ast(0, 'use', 'myTag'), 61 | ast(5, 'quasi', [ 62 | 'hello ', 63 | ast(14, 'record', [ 64 | ast(15, 'prop', 'MODULE', ast(15, 'use', 'MODULE')) 65 | ]), 66 | ': ', 67 | ast(27, 'use', 'e'), 68 | '' 69 | ]))); 70 | }); 71 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/jessie.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import bootPeg from '../../../lib/boot-peg.js'; 4 | import bootPegAst from '../../../lib/boot-pegast.js'; 5 | import makePeg from '../../../lib/quasi-peg.js'; 6 | 7 | import makeInsulatedJessie from '../../../lib/quasi-insulate.js'; 8 | import makeJessie from '../../../lib/quasi-jessie.js'; 9 | import makeJSON from '../../../lib/quasi-json.js'; 10 | import makeJustin from '../../../lib/quasi-justin.js'; 11 | import {ast, makeParser} from './parser-utils'; 12 | 13 | function defaultJessieParser() { 14 | const pegTag = bootPeg(makePeg, bootPegAst); 15 | const jsonTag = makeJSON(pegTag); 16 | const justinTag = makeJustin(pegTag.extends(jsonTag)); 17 | const [rawJessieTag] = makeJessie(pegTag, pegTag.extends(justinTag)); 18 | const [jessieTag] = makeInsulatedJessie(pegTag, pegTag.extends(rawJessieTag)); 19 | return makeParser(jessieTag); 20 | } 21 | 22 | test('conciseMethod', () => { 23 | const parse = defaultJessieParser(); 24 | expect(parse(`export default insulate({def(abc) { return abc; }, ghi() { }});`)).toEqual(['module', [ 25 | ast(0, 'exportDefault', ast(15, 'call', ['use', 'insulate'], [ 26 | ast(24, 'record', [ 27 | ast(25, 'method', 'def', [ast(29, 'def', 'abc')], 28 | ast(34, 'block', [ast(36, 'return', ast(43, 'use', 'abc'))])), 29 | ast(51, 'method', 'ghi', [], ast(57, 'block', [])) 30 | ]) 31 | ])) 32 | ]]); 33 | }); 34 | 35 | test('switch', () => { 36 | const parse = defaultJessieParser(); 37 | expect(parse(`export default insulate(() => { 38 | switch (e) { 39 | case 'b':{ 40 | q = '\\b'; 41 | break; 42 | } 43 | } 44 | });`)).toEqual(['module', [ 45 | ast(0, 'exportDefault', ast(15, 'call', ['use', 'insulate'], [ 46 | ast(24, 'arrow', ast(24), ast(30, 'block', [ 47 | ast(40, 'switch', ast(48, 'use', 'e'), [ 48 | ast(65, 'clause', [ast(65, 'case', ast(70, 'data', 'b'))], 49 | ast(92, 'block', [ 50 | ast(92, '=', ast(92, 'use', 'q'), ast(96, 'data', '\b')), 51 | ast(118, 'break') 52 | ])) 53 | ]) 54 | ])) 55 | ])) 56 | ]]); 57 | }); 58 | 59 | test('quasi', () => { 60 | const parse = defaultJessieParser(); 61 | expect(parse(`export default insulate(() => { bar.foo\`baz \${1} \${2}\`; });`)).toEqual(['module', [ 62 | ast(0, 'exportDefault', ast(15, 'call', ['use', 'insulate'], [ 63 | ast(24, 'arrow', ast(24), ast(30, 'block', [ 64 | ast(32, 'tag', ['get', ast(32, 'use', 'bar'), "foo"], 65 | ast(39, 'quasi', ['baz ', ast(46, 'data', 1), ' ', ast(51, 'data', 2), '']) 66 | )])) 67 | ])) 68 | ]]); 69 | }); 70 | 71 | test('insulate', () => { 72 | const parse = defaultJessieParser(); 73 | expect(parse(`export default insulate(a);`)).toEqual(['module', [ 74 | ast(0, 'exportDefault', ast(15, 'call', ['use', 'insulate'], [ 75 | ast(24, 'use', 'a') 76 | ])) 77 | ]]); 78 | }); 79 | 80 | test('exports', () => { 81 | const parse = defaultJessieParser(); 82 | expect(parse(`export default 123;`)).toEqual(['module', [ 83 | ast(0, 'exportDefault', ast(15, 'data', 123)) 84 | ]]); 85 | }); 86 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/json.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import bootPeg from '../../../lib/boot-peg.js'; 4 | import bootPegAst from '../../../lib/boot-pegast.js'; 5 | import makePeg from '../../../lib/quasi-peg.js'; 6 | 7 | import makeJSON from '../../../lib/quasi-json.js'; 8 | import {ast, makeParser} from './parser-utils'; 9 | 10 | function defaultJsonParser() { 11 | const pegTag = bootPeg(makePeg, bootPegAst); 12 | const jsonTag = makeJSON(pegTag); 13 | return makeParser(jsonTag); 14 | } 15 | 16 | test('data', () => { 17 | const parse = defaultJsonParser(); 18 | expect(parse('{}')).toEqual(ast(0, 'record', [])); 19 | expect(parse('[]')).toEqual(ast(0, 'array', [])); 20 | expect(parse('123')).toEqual(ast(0, 'data', 123)); 21 | expect(parse('-12.34')).toEqual(ast(0, 'data', -12.34)); 22 | expect(parse('{"abc": 123}')).toEqual(ast(0, 'record', 23 | [ast(1, 'prop', ast(1, 'data', 'abc'), ast(8, 'data', 123))])); 24 | expect(parse('["abc", 123]')).toEqual(ast(0, 'array', [ast(1, 'data', 'abc'), ast(8, 'data', 123)])); 25 | expect(parse('"\\f\\r\\n\\t\\b"')).toEqual(ast(0, 'data', '\f\r\n\t\b')); 26 | }); 27 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/justin.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import bootPeg from '../../../lib/boot-peg.js'; 4 | import bootPegAst from '../../../lib/boot-pegast.js'; 5 | import makePeg from '../../../lib/quasi-peg.js'; 6 | 7 | import makeJSON from '../../../lib/quasi-json.js'; 8 | import makeJustin from '../../../lib/quasi-justin.js'; 9 | import {ast, makeParser} from './parser-utils'; 10 | 11 | function defaultJustinParser() { 12 | const pegTag = bootPeg(makePeg, bootPegAst); 13 | const jsonTag = makeJSON(pegTag); 14 | const justinTag = makeJustin(pegTag.extends(jsonTag)); 15 | return makeParser(justinTag); 16 | } 17 | 18 | test('data', () => { 19 | const parse = defaultJustinParser(); 20 | expect(parse(`12345`)).toEqual(ast(0, 'data', 12345)); 21 | expect(parse(`{}`)).toEqual(ast(0, 'record', [])); 22 | expect(parse(`[]`)).toEqual(ast(0, 'array', [])); 23 | expect(parse(`{"abc": 123}`)).toEqual(ast(0, 'record', 24 | [ast(1, 'prop', ast(1, 'data', 'abc'), ast(8, 'data', 123))])); 25 | expect(parse('["abc", 123]')).toEqual(ast(0, 'array', [ast(1, 'data', 'abc'), ast(8, 'data', 123)])); 26 | expect(parse(` /* nothing */ 123`)).toEqual(ast(16, 'data', 123)); 27 | expect(parse(`// foo 28 | // bar 29 | // baz 30 | 123`)).toEqual(ast(27, 'data', 123)); 31 | expect(parse('"\\f\\r\\n\\t\\b"')).toEqual(ast(0, 'data', '\f\r\n\t\b')); 32 | }); 33 | 34 | test('binops', () => { 35 | const parse = defaultJustinParser(); 36 | expect(parse(`2 === 2`)).toEqual(ast(0, '===', ast(0, 'data', 2), ast(6, 'data', 2))); 37 | expect(parse(`2 < argv`)).toEqual(ast(0, '<', ast(0, 'data', 2), ast(4, 'use', 'argv'))); 38 | expect(parse(`argv < 2`)).toEqual(ast(0, '<', ast(0, 'use', 'argv'), ast(7, 'data', 2))); 39 | }); 40 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/parser-utils.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import * as util from 'util'; 4 | import tagString from '../../../lib/tag-string.js'; 5 | 6 | let curSrc = ''; 7 | 8 | export function makeParser(tag: IParserTag) { 9 | const stringTag = tagString<{_pegPosition: string}>(tag); 10 | return (src: string, doDump = false, doDebug = false) => { 11 | curSrc = src; 12 | const dtag = doDebug ? stringTag('DEBUG') : stringTag; 13 | const parsed = dtag`${src}`; 14 | if (doDump) { 15 | // tslint:disable-next-line:no-console 16 | console.log('Dump:', util.inspect(parsed, {depth: Infinity})); 17 | doDump = false; 18 | } 19 | return parsed; 20 | }; 21 | } 22 | 23 | export function ast(pos: number, ...args: any[] & {_pegPosition?: string}) { 24 | args._pegPosition = `${JSON.stringify(curSrc[pos])} #0:${pos}`; 25 | return args; 26 | } 27 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/peg.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import bootPeg from '../../../lib/boot-peg.js'; 5 | import bootPegAst from '../../../lib/boot-pegast.js'; 6 | import makePeg from '../../../lib/quasi-peg.js'; 7 | 8 | function defaultPegTag() { 9 | return bootPeg(makePeg, bootPegAst); 10 | } 11 | 12 | test('comments', () => { 13 | function doArith(peg: IPegTag) { 14 | const {HOLE, SKIP} = peg; 15 | return peg` 16 | start <- expr EOF ${(v, _) => v}; 17 | expr <- 18 | term PLUS expr ${(a, _, b) => (...subs: any[]) => a(...subs) + b(...subs)} 19 | / term; 20 | term <- 21 | NUMBER ${n => (..._: any[]) => JSON.parse(n)} 22 | / HOLE ${h => (...subs: any[]) => subs[h]} 23 | / LEFT_PAREN expr RIGHT_PAREN ${(_, v, _2) => v}; 24 | 25 | NUMBER <- WS; 26 | HOLE <- &${HOLE} WS; 27 | MINUS <- "-" WS; 28 | PLUS <- "+" WS; 29 | LEFT_PAREN <- "(" WS; 30 | RIGHT_PAREN <- ")" WS; 31 | WS <- [\t\r\n ]* ${(_) => SKIP}; 32 | EOF <- ~.; 33 | `; 34 | } 35 | 36 | const pegTag = defaultPegTag(); 37 | const arith = doArith(pegTag); 38 | expect(arith`1 + (-2 + ${3 * 11} + ${55 - 11}) + 4`).toBe(80); 39 | }); 40 | 41 | test('json', () => { 42 | const pegTag = defaultPegTag(); 43 | const {HOLE, SKIP} = pegTag; 44 | const quasiJSON = pegTag` 45 | start <- value EOF ${(v, _) => v}; 46 | value <- 47 | prim ${p => (..._: any[]) => JSON.parse(p)} 48 | / array 49 | / record # json.org calls this "object" 50 | / HOLE ${h => (...subs: any[]) => subs[h]}; 51 | prim <- (STRING / NUMBER / "true" / "false" / "null") WS; 52 | 53 | array <- LEFT_BRACKET value ** _COMMA RIGHT_BRACKET 54 | ${(_, vs: Array<(...a: any[]) => any>, _2) => 55 | (...subs: any[]) => 56 | vs.map(v => v(...subs))}; 57 | record <- LEFT_BRACE pair ** _COMMA RIGHT_BRACE 58 | ${(_, pairs, _2) => (...subs: any[]) => { 59 | const result: Record = {}; 60 | for (const [k, v] of pairs) { 61 | result[k(...subs)] = v(...subs); 62 | } 63 | return result; 64 | }}; 65 | pair <- key COLON value ${(k, _, v) => [k, v]}; 66 | key <- 67 | STRING ${p => (..._: any[]) => JSON.parse(p)} 68 | / HOLE ${h => (...subs: any[]) => subs[h]}; 69 | 70 | NUMBER <- WS; 71 | STRING <- <["] (~[\\"] .)* ["]> WS; 72 | HOLE <- &${HOLE} WS; 73 | MINUS <- "-" WS; 74 | _COMMA <- "," WS ${(_) => SKIP}; 75 | COLON <- ":" WS; 76 | LEFT_BRACKET <- "[" WS; 77 | RIGHT_BRACKET <- "]" WS; 78 | LEFT_BRACE <- "{" WS; 79 | RIGHT_BRACE <- "}" WS; 80 | WS <- [\t\r\n ]* ${(_) => SKIP}; 81 | EOF <- ~.; 82 | `; 83 | 84 | const piece = quasiJSON`{${"foo"}: [${33}, 44]}`; 85 | expect(piece).toEqual({foo: [33, 44]}); 86 | 87 | const JSON_PLUS = pegTag.extends(quasiJSON)` 88 | start <- super.start; 89 | key <- 90 | super.key 91 | / IDENT ${id => (..._: any[]) => id}; 92 | 93 | IDENT <- < [A-Za-z_] [A-Za-z0-9_]* > WS; 94 | `; 95 | 96 | const thing = JSON_PLUS`{"bar": ${piece}, baz: [55]}`; 97 | expect(thing).toEqual({bar: piece, baz: [55]}); 98 | }); 99 | 100 | test('scannerish', () => { 101 | const pegTag = defaultPegTag(); 102 | const {HOLE, SKIP} = pegTag; 103 | const scannerish = pegTag` 104 | start <- token* EOF ${toks => (..._: any[]) => toks}; 105 | token <- ("he" / "++" / NUMBER / CHAR / HOLE) WS; 106 | 107 | NUMBER <- <[0-9]+> WS; 108 | CHAR <- . WS; 109 | HOLE <- &${HOLE} WS; 110 | WS <- [\t\r\n ]* ${(_) => SKIP}; 111 | EOF <- ~.; 112 | `; 113 | 114 | const tks = scannerish`33he llo${3}w ++ orld`; 115 | expect(tks).toEqual(["33", 'he', 'l', 'l', 'o', 0, 'w', '++', 'o', 'r', 'l', 'd']); 116 | }); 117 | 118 | test('error', () => { 119 | const pegTag = defaultPegTag(); 120 | const parser = pegTag` 121 | start <- token* EOF ${toks => (..._: any[]) => toks}; 122 | token <- "abc" / "adb"; 123 | EOF <- ~.; 124 | `; 125 | 126 | expect(parser`abcadbadbabc`).toEqual(['abc', 'adb', 'adb', 'abc']); 127 | 128 | // Silence the expected error. 129 | const oldError = console.error; 130 | try { 131 | console.error = (): any => undefined; 132 | expect(() => parser`abcadbabcadd`).toThrowError('Syntax error at 9 "a" #0:9'); 133 | } finally { 134 | console.error = oldError; 135 | } 136 | }); 137 | -------------------------------------------------------------------------------- /lang/nodejs/__tests__/translate.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import {IJessicaResourceParameters, translate} from '../../../lib/translate.js'; 3 | 4 | const params: IJessicaResourceParameters = { 5 | remoteURL: 'http://usercontent.example.org/hash-1234/myscript.js', 6 | sourceType: 'jessie', 7 | sourceURL: 'http://example.org/myscript.js', 8 | target: 'jessie-frame', 9 | targetType: 'function', 10 | }; 11 | 12 | const modParams: IJessicaResourceParameters = { 13 | ...params, 14 | targetType: 'module', 15 | }; 16 | 17 | test.skip('sanity', async () => { 18 | const translatedText = `$h_define( 19 | [], 20 | () => { 21 | const $h_exports = {}; 22 | $h_exports.default = insulate(() => 'hello world'); 23 | return $h_exports; 24 | })`; 25 | expect(await translate(`export default insulate(() => 'hello world');`, params)).toEqual({ 26 | ...params, 27 | translatedText, 28 | }); 29 | const module = `export default insulate(() => 'hello world');`; 30 | expect(await translate(module, modParams)).toEqual({ 31 | ...modParams, 32 | translatedText: module, 33 | }); 34 | }); 35 | 36 | test.skip('imports', async () => { 37 | const translatedText = `$h_define( 38 | ["./foo"], 39 | ($h_star0) => { 40 | const $h_exports = {}; 41 | const {default: $i_foo} = $h_star0; 42 | const foo = insulate($i_foo); 43 | $h_exports.default = insulate(foo); 44 | return $h_exports; 45 | })`; 46 | expect(await translate(`import $i_foo from './foo'; 47 | const foo = insulate($i_foo); 48 | export default insulate(foo); 49 | `, 50 | params)).toEqual({ 51 | ...params, 52 | translatedText, 53 | }); 54 | 55 | const translatedText2 = `$h_define( 56 | ["./foo"], 57 | ($i_bitzy) => { 58 | const $h_exports = {}; 59 | const {default: $i_foo} = $i_bitzy; 60 | const foo = insulate($i_foo), {bat, boz} = insulate($i_bitzy); 61 | $h_exports.foo = foo; 62 | $h_exports.bat = bat; 63 | $h_exports.boz = boz; 64 | $h_exports.default = insulate(boz); 65 | return $h_exports; 66 | })`; 67 | expect(await translate(`import $i_foo, * as $i_bitzy from './foo'; 68 | export const foo = insulate($i_foo), {bat, boz} = insulate($i_bitzy); 69 | export default insulate(boz); 70 | `, 71 | params)).toEqual({ 72 | ...params, 73 | translatedText: translatedText2, 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /lang/nodejs/babel-tessie.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types'); 2 | 3 | module.exports = () => { 4 | const topLevelDecls = new WeakSet(); 5 | const insulateFunction = 'insulate'; 6 | const importPrefix = '$i_'; 7 | const safeModules = ['@agoric/jessie']; 8 | const isSafeModule = (moduleName) => { 9 | return safeModules.indexOf(moduleName) >= 0; 10 | }; 11 | const insulateExpr = (expr) => { 12 | if (expr.type === 'CallExpression' && 13 | expr.callee.type === 'Identifier' && 14 | expr.callee.name === insulateFunction) { 15 | // Already insulated. 16 | return expr; 17 | } 18 | 19 | switch (expr.type) { 20 | case 'NumericLiteral': 21 | case 'StringLiteral': 22 | case 'BooleanLiteral': 23 | // Don't need to insulate. 24 | return expr; 25 | case 'Identifier': 26 | if (!expr.name.startsWith(importPrefix)) { 27 | return expr; 28 | } 29 | break; 30 | } 31 | // Pass it to insulate. 32 | return t.callExpression(t.identifier(insulateFunction), [expr]); 33 | }; 34 | 35 | const makeSafeImport = (local) => { 36 | if (local.type === 'Identifier' && 37 | local.name.startsWith(importPrefix)) { 38 | return undefined; 39 | } 40 | const safeName = local.name; 41 | local.name = importPrefix + safeName; 42 | return [t.variableDeclaration('const', 43 | [t.variableDeclarator(t.identifier(safeName), insulateExpr(local))])]; 44 | }; 45 | 46 | return { 47 | visitor: { 48 | ExportDefaultDeclaration(path) { 49 | path.node.declaration = insulateExpr(path.node.declaration); 50 | }, 51 | ImportNamespaceSpecifier(path) { 52 | if (!isSafeModule(path.parentPath.node.source.value)) { 53 | path.parentPath.insertAfter(makeSafeImport(path.node.local)); 54 | } 55 | }, 56 | ImportDefaultSpecifier(path) { 57 | if (!isSafeModule(path.parentPath.node.source.value)) { 58 | path.parentPath.insertAfter(makeSafeImport(path.node.local)); 59 | } 60 | }, 61 | ImportSpecifier(path) { 62 | if (!isSafeModule(path.parentPath.node.source.value)) { 63 | path.parentPath.insertAfter(makeSafeImport(path.node.local)); 64 | } 65 | }, 66 | VariableDeclaration(path) { 67 | if (path.parent.type !== 'Program' && 68 | path.parent.type !== 'ExportNamedDeclaration') { 69 | return; 70 | } 71 | if (path.node.kind !== 'const') { 72 | throw path.buildCodeFrameError(`Module-level declaration must be "const"`); 73 | } 74 | topLevelDecls.add(path.node); 75 | }, 76 | VariableDeclarator(path) { 77 | if (!topLevelDecls.has(path.parent)) { 78 | return; 79 | } 80 | if (path.node.init) { 81 | path.node.init = insulateExpr(path.node.init); 82 | } 83 | }, 84 | }, 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /lang/nodejs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "presets": [ 3 | "@babel/preset-typescript" 4 | ], 5 | "parserOpts": { 6 | "plugins": ["objectRestSpread"] 7 | }, 8 | "plugins": [ 9 | "@babel/plugin-transform-modules-commonjs" 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /lang/nodejs/build.bat: -------------------------------------------------------------------------------- 1 | :; cd `dirname "$0"` || exit $? 2 | :; (cd ../.. && { test -d ./node_modules/@agoric/jessie || npm install; }) || exit $? 3 | :; test -d ./node_modules/typescript || npm install || exit $? 4 | :; status=0 5 | :; rm -rf tsout 6 | :; ./node_modules/.bin/tsc -p ./tsconfig.app.json --outDir tsout || status=$? 7 | :; for f in tsout/lang/nodejs/*.js; do test -f "$f" || continue; mv "$f" `echo "$f" | sed -e 's!^.*/\(.*\.js\)\.js$!\1!; s!^.*/\(.*\.js\)$!\1!'`; done 8 | :; rm -rf tsout 9 | :; exit $status 10 | -------------------------------------------------------------------------------- /lang/nodejs/check.bat: -------------------------------------------------------------------------------- 1 | :; cd `dirname "$0"` && npm run test 2 | -------------------------------------------------------------------------------- /lang/nodejs/cmparse.bat: -------------------------------------------------------------------------------- 1 | :; thisdir=`dirname "$0"`; 2 | :; test -d "$thisdir/node_modules/esm" || (cd "$thisdir" && npm install 1>&2) 3 | :; athisdir=`cd "$thisdir" && pwd` 4 | :; curdir=`pwd` 5 | :; reldir=`echo "$athisdir" | sed -e "s!^$curdir/!!;"' s!\(.\)$!\1/!'` 6 | :; NODE_PATH="$athisdir/node_modules:$NODE_PATH" exec node --require esm "${reldir}cmparse.js" ${1+"$@"} 7 | :; echo 1>&2 "Failed!";exit 1 8 | @echo off 9 | set thisdir=%~dp0 10 | set arguments=%* 11 | node --require esm "%thisdir%\cmparse.js" %arguments% 12 | -------------------------------------------------------------------------------- /lang/nodejs/cmparse.js: -------------------------------------------------------------------------------- 1 | // jesspipe.ts - Evaluate a Jessie script as part of a pipeline 2 | /// 3 | /// 4 | import { slog } from '@michaelfig/slog'; 5 | import bootPeg from '../../lib/boot-peg.js'; 6 | import bootPegAst from '../../lib/boot-pegast.js'; 7 | import makePeg from '../../lib/quasi-peg.js'; 8 | import makeChainmail from '../../lib/quasi-chainmail.js'; 9 | import tagString from '../../lib/tag-string.js'; 10 | // Read and evaluate the specified module, 11 | if (process.argv.length < 3) { 12 | slog.panic `You must specify a MODULE`; 13 | } 14 | const MODULE = process.argv[2] || '-'; 15 | const ARGV = process.argv.slice(2); 16 | // Make a confined file loader specified by the arguments. 17 | const dashdash = ARGV.indexOf('--'); 18 | const CAN_LOAD_ASSETS = new Set([MODULE]); 19 | if (dashdash >= 0) { 20 | ARGV.slice(dashdash + 1).forEach(file => CAN_LOAD_ASSETS.add(file)); 21 | } 22 | import * as fs from 'fs'; 23 | import makeReadInput from '../../lib/readInput.js'; 24 | const rawReadInput = (path) => fs.readFileSync(path, { encoding: 'latin1' }); 25 | const readInput = makeReadInput(CAN_LOAD_ASSETS, rawReadInput); 26 | // Make a confined file writer. 27 | let written = false; 28 | const writeOutput = (fname, str) => { 29 | if (fname !== '-') { 30 | slog.error `Cannot write to ${{ fname }}: must be -`; 31 | } 32 | written = true; 33 | process.stdout.write(str); 34 | }; 35 | // Create a parser. 36 | // Bootstrap a peg tag. 37 | const pegTag = bootPeg(makePeg, bootPegAst); 38 | // Stack up the parser. 39 | const cmTag = makeChainmail(pegTag); 40 | const tag = tagString(cmTag, MODULE); 41 | const str = readInput(MODULE); 42 | console.log(JSON.stringify(tag `${str}`, undefined, 2)); 43 | -------------------------------------------------------------------------------- /lang/nodejs/cmparse.ts: -------------------------------------------------------------------------------- 1 | // jesspipe.ts - Evaluate a Jessie script as part of a pipeline 2 | 3 | /// 4 | /// 5 | 6 | import { slog } from '@michaelfig/slog'; 7 | import { applyMethod, setComputedIndex } from './jessieDefaults.js'; 8 | 9 | import bootPeg from '../../lib/boot-peg.js'; 10 | import bootPegAst from '../../lib/boot-pegast.js'; 11 | import makePeg from '../../lib/quasi-peg.js'; 12 | import makeChainmail from '../../lib/quasi-chainmail.js'; 13 | import tagString from '../../lib/tag-string.js'; 14 | 15 | 16 | // Read and evaluate the specified module, 17 | if (process.argv.length < 3) { 18 | slog.panic`You must specify a MODULE`; 19 | } 20 | const MODULE = process.argv[2] || '-'; 21 | const ARGV = process.argv.slice(2); 22 | 23 | // Make a confined file loader specified by the arguments. 24 | const dashdash = ARGV.indexOf('--'); 25 | const CAN_LOAD_ASSETS = new Set([MODULE]); 26 | if (dashdash >= 0) { 27 | ARGV.slice(dashdash + 1).forEach(file => CAN_LOAD_ASSETS.add(file)); 28 | } 29 | 30 | import * as fs from 'fs'; 31 | import makeReadInput from '../../lib/readInput.js'; 32 | const rawReadInput = (path: string) => fs.readFileSync(path, {encoding: 'latin1'}); 33 | 34 | const readInput = makeReadInput(CAN_LOAD_ASSETS, rawReadInput); 35 | 36 | // Make a confined file writer. 37 | let written = false; 38 | const writeOutput = (fname: string, str: string) => { 39 | if (fname !== '-') { 40 | slog.error`Cannot write to ${{fname}}: must be -`; 41 | } 42 | written = true; 43 | process.stdout.write(str); 44 | }; 45 | 46 | // Create a parser. 47 | 48 | // Bootstrap a peg tag. 49 | const pegTag = bootPeg>(makePeg, bootPegAst); 50 | 51 | // Stack up the parser. 52 | const cmTag = makeChainmail(pegTag); 53 | const tag = tagString(cmTag, MODULE); 54 | 55 | const str = readInput(MODULE); 56 | console.log(JSON.stringify(tag`${str}`, undefined, 2)); 57 | -------------------------------------------------------------------------------- /lang/nodejs/jessieDefaults.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import * as jessie from '@agoric/jessie'; 4 | import { slog } from '@michaelfig/slog'; 5 | const harden = jessie.harden; 6 | export const applyMethod = harden((thisObj, method, args) => Reflect.apply(method, thisObj, args)); 7 | export const setComputedIndex = harden((obj, index, val) => { 8 | if (index === '__proto__') { 9 | slog.error `Cannot set ${{ index }} object member`; 10 | } 11 | return obj[index] = val; 12 | }); 13 | // Don't insulate the arguments to setComputedIndex. 14 | import insulate, { $h_already, $h_debug } from '@agoric/jessie/lib/insulate.js'; 15 | $h_already.add(setComputedIndex); 16 | $h_debug(harden((caller, definer) => { 17 | slog.debug `Insulate error: ${caller}`; 18 | slog.debug `In insulate definer: ${definer}`; 19 | })); 20 | for (const j of Object.values(jessie)) { 21 | $h_already.add(j); 22 | } 23 | export { insulate }; 24 | // Truncate sourceURL. 25 | import { $h_sourceURLLength } from '@agoric/jessie/lib/confine.js'; 26 | $h_sourceURLLength(40); 27 | -------------------------------------------------------------------------------- /lang/nodejs/jessieDefaults.js.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import * as jessie from '@agoric/jessie'; 5 | import { slog } from '@michaelfig/slog'; 6 | 7 | const harden = jessie.harden; 8 | export const applyMethod = harden((thisObj: any, method: (...args: any) => T, args: any[]): T => 9 | Reflect.apply(method, thisObj, args)); 10 | 11 | export const setComputedIndex = harden((obj: any, index: string | number, val: T) => { 12 | if (index === '__proto__') { 13 | slog.error`Cannot set ${{index}} object member`; 14 | } 15 | return obj[index] = val; 16 | }); 17 | 18 | // Don't insulate the arguments to setComputedIndex. 19 | import insulate, { $h_already, $h_debug } from '@agoric/jessie/lib/insulate.js'; 20 | $h_already.add(setComputedIndex); 21 | $h_debug(harden((caller: Error, definer: Error) => { 22 | slog.debug`Insulate error: ${caller}`; 23 | slog.debug`In insulate definer: ${definer}`; 24 | })); 25 | for (const j of Object.values(jessie)) { 26 | $h_already.add(j); 27 | } 28 | export { insulate }; 29 | 30 | // Truncate sourceURL. 31 | import { $h_sourceURLLength } from '@agoric/jessie/lib/confine.js'; 32 | $h_sourceURLLength(40); 33 | -------------------------------------------------------------------------------- /lang/nodejs/jessparse.bat: -------------------------------------------------------------------------------- 1 | :; thisdir=`dirname "$0"`; 2 | :; test -d "$thisdir/node_modules/esm" || (cd "$thisdir" && npm install 1>&2) 3 | :; athisdir=`cd "$thisdir" && pwd` 4 | :; curdir=`pwd` 5 | :; reldir=`echo "$athisdir" | sed -e "s!^$curdir/!!;"' s!\(.\)$!\1/!'` 6 | :; NODE_PATH="$athisdir/node_modules:$NODE_PATH" exec node --require esm "${reldir}jessparse.js" ${1+"$@"} 7 | :; echo 1>&2 "Failed!";exit 1 8 | @echo off 9 | set thisdir=%~dp0 10 | set arguments=%* 11 | node --require esm "%thisdir%\jessparse.js" %arguments% 12 | -------------------------------------------------------------------------------- /lang/nodejs/jessparse.js: -------------------------------------------------------------------------------- 1 | // jessparse.ts - Parse a Jessie module and output its AST 2 | /// 3 | /// 4 | import { slog } from '@michaelfig/slog'; 5 | import bootPeg from '../../lib/boot-peg.js'; 6 | import bootPegAst from '../../lib/boot-pegast.js'; 7 | import makePeg from '../../lib/quasi-peg.js'; 8 | import makeJSON from '../../lib/quasi-json.js'; 9 | import makeJustin from '../../lib/quasi-justin.js'; 10 | import makeJessie from '../../lib/quasi-jessie.js'; 11 | import tagString from '../../lib/tag-string.js'; 12 | // Read and evaluate the specified module, 13 | if (process.argv.length < 3) { 14 | slog.panic `You must specify a MODULE`; 15 | } 16 | const MODULE = process.argv[2] || '-'; 17 | const ARGV = process.argv.slice(2); 18 | // Make a confined file loader specified by the arguments. 19 | const dashdash = ARGV.indexOf('--'); 20 | const CAN_LOAD_ASSETS = new Set([MODULE]); 21 | if (dashdash >= 0) { 22 | ARGV.slice(dashdash + 1).forEach(file => CAN_LOAD_ASSETS.add(file)); 23 | } 24 | import * as fs from 'fs'; 25 | import makeReadInput from '../../lib/readInput.js'; 26 | const rawReadInput = (path) => fs.readFileSync(path, { encoding: 'latin1' }); 27 | const readInput = makeReadInput(CAN_LOAD_ASSETS, rawReadInput); 28 | // Make a confined file writer. 29 | let written = false; 30 | const writeOutput = (fname, str) => { 31 | if (fname !== '-') { 32 | slog.error `Cannot write to ${{ fname }}: must be -`; 33 | } 34 | written = true; 35 | process.stdout.write(str); 36 | }; 37 | // Create a parser. 38 | // Bootstrap a peg tag. 39 | const pegTag = bootPeg(makePeg, bootPegAst); 40 | // Stack up the parser. 41 | const jsonTag = makeJSON(pegTag); 42 | const justinTag = makeJustin(pegTag.extends(jsonTag)); 43 | const [jessieTag] = makeJessie(pegTag, pegTag.extends(justinTag)); 44 | const tag = tagString(jessieTag, MODULE); 45 | const str = readInput(MODULE); 46 | console.log(JSON.stringify(tag `${str}`, undefined, 2)); 47 | -------------------------------------------------------------------------------- /lang/nodejs/jessparse.ts: -------------------------------------------------------------------------------- 1 | // jessparse.ts - Parse a Jessie module and output its AST 2 | 3 | /// 4 | /// 5 | 6 | import { slog } from '@michaelfig/slog'; 7 | import { applyMethod, setComputedIndex } from './jessieDefaults.js'; 8 | 9 | import bootPeg from '../../lib/boot-peg.js'; 10 | import bootPegAst from '../../lib/boot-pegast.js'; 11 | import makePeg from '../../lib/quasi-peg.js'; 12 | import makeJSON from '../../lib/quasi-json.js'; 13 | import makeJustin from '../../lib/quasi-justin.js'; 14 | import makeJessie from '../../lib/quasi-jessie.js'; 15 | import tagString from '../../lib/tag-string.js'; 16 | 17 | 18 | // Read and evaluate the specified module, 19 | if (process.argv.length < 3) { 20 | slog.panic`You must specify a MODULE`; 21 | } 22 | const MODULE = process.argv[2] || '-'; 23 | const ARGV = process.argv.slice(2); 24 | 25 | // Make a confined file loader specified by the arguments. 26 | const dashdash = ARGV.indexOf('--'); 27 | const CAN_LOAD_ASSETS = new Set([MODULE]); 28 | if (dashdash >= 0) { 29 | ARGV.slice(dashdash + 1).forEach(file => CAN_LOAD_ASSETS.add(file)); 30 | } 31 | 32 | import * as fs from 'fs'; 33 | import makeReadInput from '../../lib/readInput.js'; 34 | const rawReadInput = (path: string) => fs.readFileSync(path, {encoding: 'latin1'}); 35 | 36 | const readInput = makeReadInput(CAN_LOAD_ASSETS, rawReadInput); 37 | 38 | // Make a confined file writer. 39 | let written = false; 40 | const writeOutput = (fname: string, str: string) => { 41 | if (fname !== '-') { 42 | slog.error`Cannot write to ${{fname}}: must be -`; 43 | } 44 | written = true; 45 | process.stdout.write(str); 46 | }; 47 | 48 | // Create a parser. 49 | 50 | // Bootstrap a peg tag. 51 | const pegTag = bootPeg>(makePeg, bootPegAst); 52 | 53 | // Stack up the parser. 54 | const jsonTag = makeJSON(pegTag); 55 | const justinTag = makeJustin(pegTag.extends(jsonTag)); 56 | const [jessieTag] = makeJessie(pegTag, pegTag.extends(justinTag)); 57 | const tag = tagString(jessieTag, MODULE); 58 | 59 | const str = readInput(MODULE); 60 | console.log(JSON.stringify(tag`${str}`, undefined, 2)); 61 | -------------------------------------------------------------------------------- /lang/nodejs/jesspipe.bat: -------------------------------------------------------------------------------- 1 | :; thisdir=`dirname "$0"`; 2 | :; test -d "$thisdir/node_modules/esm" && test -d "$thisdir/node_modules/ses" || (cd "$thisdir" && npm install 1>&2) 3 | :; athisdir=`cd "$thisdir" && pwd` 4 | :; curdir=`pwd` 5 | :; reldir=`echo "$athisdir" | sed -e "s!^$curdir/!!;"' s!\(.\)$!\1/!'` 6 | :; NODE_PATH="$athisdir/node_modules:$NODE_PATH" exec node --require esm "${reldir}jesspipe.js" ${1+"$@"} 7 | :; echo 1>&2 "Failed!";exit 1 8 | @echo off 9 | set thisdir=%~dp0 10 | set arguments=%* 11 | node --require esm "%thisdir%\jesspipe.js" %arguments% 12 | -------------------------------------------------------------------------------- /lang/nodejs/jesspipe.js: -------------------------------------------------------------------------------- 1 | // jesspipe.ts - Evaluate a Jessie script as part of a pipeline 2 | /// 3 | /// 4 | import { slog } from '@michaelfig/slog'; 5 | import { applyMethod, setComputedIndex } from './jessieDefaults.js'; 6 | // Read and evaluate the specified module, 7 | if (process.argv.length < 3) { 8 | slog.panic `You must specify a MODULE`; 9 | } 10 | const MODULE = process.argv[2] || '-'; 11 | const ARGV = process.argv.slice(2); 12 | // Make a confined file loader specified by the arguments. 13 | const dashdash = ARGV.indexOf('--'); 14 | const CAN_LOAD_ASSETS = new Set([MODULE]); 15 | if (dashdash >= 0) { 16 | ARGV.slice(dashdash + 1).forEach(file => CAN_LOAD_ASSETS.add(file)); 17 | } 18 | import * as fs from 'fs'; 19 | import makeReadInput from '../../lib/readInput.js'; 20 | const rawReadInput = (path) => fs.readFileSync(path, { encoding: 'latin1' }); 21 | const readInput = makeReadInput(CAN_LOAD_ASSETS, rawReadInput); 22 | // Make a confined file writer. 23 | let written = false; 24 | const writeOutput = (fname, str) => { 25 | if (fname !== '-') { 26 | slog.error `Cannot write to ${{ fname }}: must be -`; 27 | } 28 | written = true; 29 | process.stdout.write(str); 30 | }; 31 | // Create a Jessica bootstrap interpreter. 32 | import bootJessica from '../../lib/boot-jessica.js'; 33 | const jessica = bootJessica(applyMethod, readInput, setComputedIndex); 34 | // Read, eval, print loop. 35 | import repl from '../../lib/repl.js'; 36 | const runModule = (src, uri) => jessica.runModule(src, { eval: jessica.eval }, { scriptName: uri }); 37 | const deps = { applyMethod, readInput, setComputedIndex, writeOutput }; 38 | try { 39 | repl(deps, runModule, MODULE, ARGV); 40 | } 41 | catch (e) { 42 | if (!written) { 43 | writeOutput('-', `/* FIXME: Stub */\n`); 44 | } 45 | slog.notice `Cannot evaluate ${{ MODULE }}: ${e}`; 46 | } 47 | -------------------------------------------------------------------------------- /lang/nodejs/jesspipe.ts: -------------------------------------------------------------------------------- 1 | // jesspipe.ts - Evaluate a Jessie script as part of a pipeline 2 | 3 | /// 4 | /// 5 | 6 | import { slog } from '@michaelfig/slog'; 7 | import { applyMethod, setComputedIndex } from './jessieDefaults.js'; 8 | 9 | // Read and evaluate the specified module, 10 | if (process.argv.length < 3) { 11 | slog.panic`You must specify a MODULE`; 12 | } 13 | const MODULE = process.argv[2] || '-'; 14 | const ARGV = process.argv.slice(2); 15 | 16 | // Make a confined file loader specified by the arguments. 17 | const dashdash = ARGV.indexOf('--'); 18 | const CAN_LOAD_ASSETS = new Set([MODULE]); 19 | if (dashdash >= 0) { 20 | ARGV.slice(dashdash + 1).forEach(file => CAN_LOAD_ASSETS.add(file)); 21 | } 22 | 23 | import * as fs from 'fs'; 24 | import makeReadInput from '../../lib/readInput.js'; 25 | const rawReadInput = (path: string) => fs.readFileSync(path, {encoding: 'latin1'}); 26 | 27 | const readInput = makeReadInput(CAN_LOAD_ASSETS, rawReadInput); 28 | 29 | // Make a confined file writer. 30 | let written = false; 31 | const writeOutput = (fname: string, str: string) => { 32 | if (fname !== '-') { 33 | slog.error`Cannot write to ${{fname}}: must be -`; 34 | } 35 | written = true; 36 | process.stdout.write(str); 37 | }; 38 | 39 | // Create a Jessica bootstrap interpreter. 40 | import bootJessica from '../../lib/boot-jessica.js'; 41 | const jessica = bootJessica(applyMethod, readInput, setComputedIndex); 42 | 43 | // Read, eval, print loop. 44 | import repl from '../../lib/repl.js'; 45 | const runModule = (src: string, uri?: string) => 46 | jessica.runModule(src, {eval: jessica.eval}, {scriptName: uri}); 47 | const deps = {applyMethod, readInput, setComputedIndex, writeOutput}; 48 | try { 49 | repl(deps, runModule, MODULE, ARGV); 50 | } catch (e) { 51 | if (!written) { 52 | writeOutput('-', `/* FIXME: Stub */\n`); 53 | } 54 | slog.notice`Cannot evaluate ${{MODULE}}: ${e}`; 55 | } 56 | -------------------------------------------------------------------------------- /lang/nodejs/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | testPathIgnorePatterns: [ 4 | // "/node_modules/", 5 | "/skip-", 6 | "parser-utils\\.ts$", 7 | ], 8 | transform: { 9 | '\\.js$': 'babel-jest', 10 | '\\.ts$': 'babel-jest', 11 | }, 12 | transformIgnorePatterns: [ 13 | "/node_modules/(?!(@agoric/jessie|@michaelfig/slog))" 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /lang/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jessica-lang-nodejs", 3 | "version": "0.0.1", 4 | "description": "Jessica implementation for Node.js", 5 | "main": "jesspipe.js", 6 | "dependencies": { 7 | "esm": "^3.2.22" 8 | }, 9 | "devDependencies": { 10 | "@babel/core": "^7.4.0", 11 | "@babel/plugin-transform-modules-commonjs": "^7.4.3", 12 | "@babel/preset-typescript": "^7.3.3", 13 | "@types/jest": "^24.0.9", 14 | "@types/node": "^11.9.6", 15 | "jest": "^24.5.0", 16 | "typescript": "^3.4.5" 17 | }, 18 | "scripts": { 19 | "build": "build.bat", 20 | "test": "jest" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/michaelfig/jessica.git" 25 | }, 26 | "author": "Michael FIG ", 27 | "license": "Apache-2.0", 28 | "bugs": { 29 | "url": "https://github.com/michaelfig/jessica/issues" 30 | }, 31 | "homepage": "https://github.com/michaelfig/jessica#readme" 32 | } 33 | -------------------------------------------------------------------------------- /lang/nodejs/supported.bat: -------------------------------------------------------------------------------- 1 | :; node --version; exit $? 2 | @echo off 3 | set thisdir=%~dp0 4 | set arguments=%* 5 | node --version 6 | -------------------------------------------------------------------------------- /lang/nodejs/tessc.bat: -------------------------------------------------------------------------------- 1 | :; thisdir=`dirname "$0"`; 2 | :; test -d "$thisdir/node_modules/@babel/core" || (cd "$thisdir" && npm install 1>&2) 3 | :; exec node "$thisdir/tessc.js" ${1+"$@"} 4 | :; echo 1>&2 "Failed!";exit 1 5 | @echo off 6 | set thisdir=%~dp0 7 | set arguments=%* 8 | node "%thisdir%\tessc.js" %arguments% 9 | -------------------------------------------------------------------------------- /lang/nodejs/tessc.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const babel = require('@babel/core'); 4 | const babelTs = require('@babel/preset-typescript'); 5 | const thisdir = path.dirname(process.argv[1]); 6 | const opts = { 7 | "sourceType": "module", 8 | "presets": [ 9 | babelTs 10 | ], 11 | "parserOpts": { 12 | "plugins": ["objectRestSpread"] 13 | }, 14 | "generatorOpts": { 15 | "compact": false, 16 | "ast": true, 17 | "retainLines": true 18 | }, 19 | "plugins": [ 20 | thisdir + "/babel-tessie" 21 | ] 22 | }; 23 | 24 | const compile = async (srcs) => { 25 | const compiled = {}; 26 | for (const src of srcs) { 27 | // console.log(`Compiling ${src}`); 28 | const js = src.replace(/\.js\.ts$/, '.js'); 29 | const dst = js === src ? src + '.js' : js; 30 | const base = src.substr(src.lastIndexOf('/') + 1); 31 | const {ast, code: out} = await babel.transformFileAsync(src, opts); 32 | compiled[dst] = `import { insulate } from '@agoric/jessie'; ${out}` 33 | } 34 | for (const dst in compiled) { 35 | // console.log(`Writing ${dst}`); 36 | await new Promise((resolve, reject) => { 37 | fs.writeFile(dst, compiled[dst], 'utf-8', (err) => { 38 | if (err) { 39 | return reject(err); 40 | } 41 | resolve(); 42 | }); 43 | }); 44 | } 45 | }; 46 | 47 | const files = process.argv.slice(2); 48 | if (files.length > 0) { 49 | compile(files); 50 | } else { 51 | console.error('You must specify the *.js.ts files to compile.'); 52 | process.exit(1); 53 | const dir = './lib'; 54 | fs.readdir(dir, async (err, files) => { 55 | if (err) { 56 | console.log(`Cannot read ${dir}`, e) 57 | process.exit(1); 58 | } 59 | await compile(files.filter(f => f.match(/\.ts$/)).map(f => `${dir}/${f}`)); 60 | }); 61 | } -------------------------------------------------------------------------------- /lang/nodejs/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2015", 5 | "module": "esnext", 6 | "noImplicitAny": true, 7 | "noEmitOnError": true, 8 | "alwaysStrict": true, 9 | "typeRoots": ["./node_modules/@types"], 10 | "types": ["node", "../../typings/ses"] 11 | }, 12 | "include": [ 13 | "**/*.ts" 14 | ], 15 | "exclude": [ 16 | "node_modules/**", 17 | "**/__tests__/*.ts", 18 | "tessc.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /lang/nodejs/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "types": [ 7 | "node", 8 | "jest", 9 | "../../typings/jessie-proposed", 10 | ], 11 | }, 12 | "include": [ 13 | "__tests__/**/*.ts" 14 | ] 15 | } 16 | 17 | -------------------------------------------------------------------------------- /lib/boot-jessica.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; // These are exported explicitly below. 2 | /// 3 | import { harden, insulate as rawInsulate, makeMap, makePromise, 4 | makeSet, makeWeakMap, makeWeakSet } from '@agoric/jessie'; 5 | import { slog as $i_slog } from '@michaelfig/slog';const slog = insulate($i_slog); 6 | 7 | import $i_bootPeg from './boot-peg.js';const bootPeg = insulate($i_bootPeg); 8 | import $i_bootPegAst from './boot-pegast.js';const bootPegAst = insulate($i_bootPegAst); 9 | import $i_makeInsulatedJessie from './quasi-insulate.js';const makeInsulatedJessie = insulate($i_makeInsulatedJessie); 10 | import $i_makeJessie from './quasi-jessie.js';const makeJessie = insulate($i_makeJessie); 11 | import $i_makeJSON from './quasi-json.js';const makeJSON = insulate($i_makeJSON); 12 | import $i_makeJustin from './quasi-justin.js';const makeJustin = insulate($i_makeJustin); 13 | import $i_makePeg from './quasi-peg.js';const makePeg = insulate($i_makePeg); 14 | 15 | import $i_makeImporter from './importer.js';const makeImporter = insulate($i_makeImporter); 16 | import $i_jessieEvaluators from './interp-jessie.js';const jessieEvaluators = insulate($i_jessieEvaluators); 17 | import $i_makeInterp from './interp-utils.js';const makeInterp = insulate($i_makeInterp); 18 | import $i_tagString from './tag-string.js';const tagString = insulate($i_tagString); 19 | 20 | 21 | 22 | const bootJessica = insulate(( 23 | applyMethod, 24 | readInput, 25 | setComputedIndex) => { 26 | // Bootstrap a peg tag. 27 | const pegTag = bootPeg(makePeg, bootPegAst); 28 | 29 | // Stack up the parser. 30 | const jsonTag = makeJSON(pegTag); 31 | const justinTag = makeJustin(pegTag.extends(jsonTag)); 32 | const [rawJessieTag] = makeJessie(pegTag, pegTag.extends(justinTag)); 33 | 34 | // FIXME: The insulated Jessie tag. 35 | const [jessieTag, jessieExprTag] = makeInsulatedJessie(pegTag, pegTag.extends(rawJessieTag)); 36 | 37 | // No, this isn't an empty Map. 38 | // (It's initialized after jessica.runExpr is defined.) 39 | const importCache = makeMap(); 40 | const importer = makeImporter(importCache, readInput, jessieTag); 41 | const interpJessie = makeInterp(jessieEvaluators, applyMethod, importer, setComputedIndex); 42 | 43 | const jessica = { 44 | eval: src => { 45 | // Don't inherit any endowments. 46 | return jessica.runExpr(src, {}); 47 | }, 48 | runExpr: (src, env = {}, options = {}) => { 49 | let tag = tagString(jessieExprTag, options.scriptName); 50 | if (options.debug) { 51 | tag = tag('DEBUG'); 52 | } 53 | const ast = tag`${src}`; 54 | const evalenv = { eval: jessica.eval, ...env }; 55 | return interpJessie(ast, evalenv, options || {}); 56 | }, 57 | runModule: (src, env = {}, options = {}) => { 58 | let tag = tagString(jessieTag, options.scriptName); 59 | if (options.debug) { 60 | tag = tag('DEBUG'); 61 | } 62 | const ast = tag`${src}`; 63 | const evalenv = { eval: jessica.eval, ...env }; 64 | return interpJessie(ast, evalenv, options || {}).default; 65 | } }; 66 | 67 | 68 | // Only allow the Jessie library we know about, with a self-hosted 69 | // confine and confineExpr. 70 | const jessie = harden({ 71 | confine(src, env, options = {}) { 72 | // Evaluate as an IIFE, but return nothing. 73 | jessica.runExpr(`(()=>{${src} 74 | ;})()`, env, options); 75 | }, 76 | confineExpr: jessica.runExpr, 77 | harden, 78 | insulate(warmTarget) { 79 | // Don't insulate our existing targets. 80 | if (jessieTargets.has(warmTarget)) { 81 | return warmTarget; 82 | } 83 | return rawInsulate(warmTarget); 84 | }, 85 | makeMap, 86 | makePromise, 87 | makeSet, 88 | makeWeakMap, 89 | makeWeakSet }); 90 | 91 | const jessieTargets = makeSet(Object.values(jessie)); 92 | importCache.set('@agoric/jessie', harden(jessie)); 93 | 94 | // The default slog is also allowed. 95 | importCache.set('@michaelfig/slog', Object.freeze({ slog })); 96 | return jessica; 97 | }); 98 | 99 | export default bootJessica; -------------------------------------------------------------------------------- /lib/boot-jessica.js.ts: -------------------------------------------------------------------------------- 1 | // These are exported explicitly below. 2 | /// 3 | import { harden, insulate as rawInsulate, makeMap, makePromise, 4 | makeSet, makeWeakMap, makeWeakSet } from '@agoric/jessie'; 5 | import { slog } from '@michaelfig/slog'; 6 | 7 | import bootPeg from './boot-peg.js'; 8 | import bootPegAst from './boot-pegast.js'; 9 | import makeInsulatedJessie from './quasi-insulate.js'; 10 | import makeJessie from './quasi-jessie.js'; 11 | import makeJSON from './quasi-json.js'; 12 | import makeJustin from './quasi-justin.js'; 13 | import makePeg from './quasi-peg.js'; 14 | 15 | import makeImporter from './importer.js'; 16 | import jessieEvaluators from './interp-jessie.js'; 17 | import makeInterp from './interp-utils.js'; 18 | import tagString from './tag-string.js'; 19 | 20 | import { ConfineOptions } from '@agoric/jessie/lib/confine.js'; 21 | 22 | const bootJessica = ( 23 | applyMethod: IMainDependencies['applyMethod'], 24 | readInput: IMainDependencies['readInput'], 25 | setComputedIndex: IMainDependencies['setComputedIndex']) => { 26 | // Bootstrap a peg tag. 27 | const pegTag = bootPeg>(makePeg, bootPegAst); 28 | 29 | // Stack up the parser. 30 | const jsonTag = makeJSON(pegTag); 31 | const justinTag = makeJustin(pegTag.extends(jsonTag)); 32 | const [rawJessieTag] = makeJessie(pegTag, pegTag.extends(justinTag)); 33 | 34 | // FIXME: The insulated Jessie tag. 35 | const [jessieTag, jessieExprTag] = makeInsulatedJessie(pegTag, pegTag.extends(rawJessieTag)); 36 | 37 | // No, this isn't an empty Map. 38 | // (It's initialized after jessica.runExpr is defined.) 39 | const importCache = makeMap(); 40 | const importer = makeImporter(importCache, readInput, jessieTag); 41 | const interpJessie = makeInterp(jessieEvaluators, applyMethod, importer, setComputedIndex); 42 | 43 | const jessica = { 44 | eval: (src: string): any => { 45 | // Don't inherit any endowments. 46 | return jessica.runExpr(src, {}); 47 | }, 48 | runExpr: (src: string, env: Record = {}, options: ConfineOptions = {}) => { 49 | let tag = tagString(jessieExprTag, options.scriptName); 50 | if (options.debug) { 51 | tag = tag('DEBUG'); 52 | } 53 | const ast = tag`${src}`; 54 | const evalenv = {eval: jessica.eval, ...env}; 55 | return interpJessie(ast, evalenv, options || {}); 56 | }, 57 | runModule: (src: string, env: Record = {}, options: ConfineOptions = {}) => { 58 | let tag = tagString(jessieTag, options.scriptName); 59 | if (options.debug) { 60 | tag = tag('DEBUG'); 61 | } 62 | const ast = tag`${src}`; 63 | const evalenv = {eval: jessica.eval, ...env}; 64 | return interpJessie(ast, evalenv, options || {}).default; 65 | }, 66 | }; 67 | 68 | // Only allow the Jessie library we know about, with a self-hosted 69 | // confine and confineExpr. 70 | const jessie = harden({ 71 | confine(src: string, env: Record, options: ConfineOptions = {}) { 72 | // Evaluate as an IIFE, but return nothing. 73 | jessica.runExpr(`(()=>{${src} 74 | ;})()`, env, options); 75 | }, 76 | confineExpr: jessica.runExpr, 77 | harden, 78 | insulate(warmTarget: T) { 79 | // Don't insulate our existing targets. 80 | if (jessieTargets.has(warmTarget)) { 81 | return warmTarget; 82 | } 83 | return rawInsulate(warmTarget); 84 | }, 85 | makeMap, 86 | makePromise, 87 | makeSet, 88 | makeWeakMap, 89 | makeWeakSet, 90 | }); 91 | const jessieTargets = makeSet(Object.values(jessie)); 92 | importCache.set('@agoric/jessie', harden(jessie)); 93 | 94 | // The default slog is also allowed. 95 | importCache.set('@michaelfig/slog', Object.freeze({slog})); 96 | return jessica; 97 | }; 98 | 99 | export default bootJessica; 100 | -------------------------------------------------------------------------------- /lib/boot-pegast.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; // boot-pegast.js.ts - AUTOMATICALLY GENERATED by boot-env.js.ts 2 | export default insulate([ 3 | [ 4 | "def", 5 | "Grammar", 6 | [ 7 | "act", 8 | 0, 9 | "_Spacing", 10 | [ 11 | "+", 12 | "Definition"], 13 | 14 | "_EndOfFile"]], 15 | 16 | 17 | [ 18 | "def", 19 | "Definition", 20 | [ 21 | "act", 22 | 2, 23 | "Identifier", 24 | "LEFTARROW", 25 | "Expression", 26 | "SEMI", 27 | [ 28 | "pred", 29 | 1]]], 30 | 31 | 32 | 33 | [ 34 | "def", 35 | "Expression", 36 | [ 37 | "act", 38 | 3, 39 | [ 40 | "**", 41 | "Sequence", 42 | "_SLASH"]]], 43 | 44 | 45 | 46 | [ 47 | "def", 48 | "Sequence", 49 | [ 50 | "act", 51 | 5, 52 | [ 53 | "act", 54 | 4, 55 | [ 56 | "*", 57 | "Prefix"]], 58 | 59 | 60 | [ 61 | "?", 62 | "HOLE"]]], 63 | 64 | 65 | 66 | [ 67 | "def", 68 | "Prefix", 69 | [ 70 | "or", 71 | [ 72 | "act", 73 | 6, 74 | "AND", 75 | "HOLE"], 76 | 77 | [ 78 | "act", 79 | 7, 80 | "AND", 81 | "Suffix"], 82 | 83 | [ 84 | "act", 85 | 8, 86 | "NOT", 87 | "Suffix"], 88 | 89 | [ 90 | "val0", 91 | "Suffix"]]], 92 | 93 | 94 | 95 | [ 96 | "def", 97 | "Suffix", 98 | [ 99 | "or", 100 | [ 101 | "act", 102 | 9, 103 | "Primary", 104 | [ 105 | "or", 106 | [ 107 | "val0", 108 | "STARSTAR"], 109 | 110 | [ 111 | "val0", 112 | "PLUSPLUS"]], 113 | 114 | 115 | "Primary"], 116 | 117 | [ 118 | "act", 119 | 10, 120 | "Primary", 121 | [ 122 | "or", 123 | [ 124 | "val0", 125 | "QUESTION"], 126 | 127 | [ 128 | "val0", 129 | "STAR"], 130 | 131 | [ 132 | "val0", 133 | "PLUS"]]], 134 | 135 | 136 | 137 | [ 138 | "val0", 139 | "Primary"]]], 140 | 141 | 142 | 143 | [ 144 | "def", 145 | "Primary", 146 | [ 147 | "or", 148 | [ 149 | "val0", 150 | "Super"], 151 | 152 | [ 153 | "val0", 154 | "Identifier", 155 | [ 156 | "peekNot", 157 | "LEFTARROW"]], 158 | 159 | 160 | [ 161 | "act", 162 | 11, 163 | "OPEN", 164 | "Expression", 165 | "CLOSE"], 166 | 167 | [ 168 | "act", 169 | 12, 170 | "Literal"], 171 | 172 | [ 173 | "act", 174 | 13, 175 | "Class"], 176 | 177 | [ 178 | "act", 179 | 14, 180 | "DOT"], 181 | 182 | [ 183 | "act", 184 | 15, 185 | "BEGIN"], 186 | 187 | [ 188 | "act", 189 | 16, 190 | "END"]]], 191 | 192 | 193 | 194 | [ 195 | "def", 196 | "Super", 197 | [ 198 | "act", 199 | 17, 200 | [ 201 | "lit", 202 | "super."], 203 | 204 | "Identifier"]], 205 | 206 | 207 | [ 208 | "def", 209 | "Identifier", 210 | [ 211 | "val0", 212 | [ 213 | "begin"], 214 | 215 | "IdentStart", 216 | [ 217 | "*", 218 | "IdentCont"], 219 | 220 | [ 221 | "end"], 222 | 223 | "_Spacing"]], 224 | 225 | 226 | [ 227 | "def", 228 | "IdentStart", 229 | [ 230 | "val0", 231 | [ 232 | "cls", 233 | "a-zA-Z_"]]], 234 | 235 | 236 | 237 | [ 238 | "def", 239 | "IdentCont", 240 | [ 241 | "or", 242 | [ 243 | "val0", 244 | "IdentStart"], 245 | 246 | [ 247 | "val0", 248 | [ 249 | "cls", 250 | "0-9"]]]], 251 | 252 | 253 | 254 | 255 | [ 256 | "def", 257 | "Literal", 258 | [ 259 | "or", 260 | [ 261 | "val0", 262 | [ 263 | "cls", 264 | "'"], 265 | 266 | [ 267 | "begin"], 268 | 269 | [ 270 | "*", 271 | [ 272 | "val0", 273 | [ 274 | "peekNot", 275 | [ 276 | "cls", 277 | "'"]], 278 | 279 | 280 | "Char"]], 281 | 282 | 283 | [ 284 | "end"], 285 | 286 | [ 287 | "cls", 288 | "'"], 289 | 290 | "_Spacing"], 291 | 292 | [ 293 | "val0", 294 | [ 295 | "cls", 296 | "\""], 297 | 298 | [ 299 | "begin"], 300 | 301 | [ 302 | "*", 303 | [ 304 | "val0", 305 | [ 306 | "peekNot", 307 | [ 308 | "cls", 309 | "\""]], 310 | 311 | 312 | "Char"]], 313 | 314 | 315 | [ 316 | "end"], 317 | 318 | [ 319 | "cls", 320 | "\""], 321 | 322 | "_Spacing"]]], 323 | 324 | 325 | 326 | [ 327 | "def", 328 | "Class", 329 | [ 330 | "val0", 331 | [ 332 | "lit", 333 | "["], 334 | 335 | [ 336 | "begin"], 337 | 338 | [ 339 | "*", 340 | [ 341 | "val0", 342 | [ 343 | "peekNot", 344 | [ 345 | "lit", 346 | "]"]], 347 | 348 | 349 | "Range"]], 350 | 351 | 352 | [ 353 | "end"], 354 | 355 | [ 356 | "lit", 357 | "]"], 358 | 359 | "_Spacing"]], 360 | 361 | 362 | [ 363 | "def", 364 | "Range", 365 | [ 366 | "or", 367 | [ 368 | "val0", 369 | "Char", 370 | [ 371 | "lit", 372 | "-"], 373 | 374 | "Char"], 375 | 376 | [ 377 | "val0", 378 | "Char"]]], 379 | 380 | 381 | 382 | [ 383 | "def", 384 | "Char", 385 | [ 386 | "or", 387 | [ 388 | "val0", 389 | [ 390 | "lit", 391 | "\\\\"], 392 | 393 | [ 394 | "cls", 395 | "abefnrtv'\"\\[\\]\\\\\\`\\$"]], 396 | 397 | 398 | [ 399 | "val0", 400 | [ 401 | "lit", 402 | "\\\\x"], 403 | 404 | [ 405 | "cls", 406 | "0-9a-fA-F"], 407 | 408 | [ 409 | "cls", 410 | "0-9a-fA-F"]], 411 | 412 | 413 | [ 414 | "val0", 415 | [ 416 | "lit", 417 | "\\\\"], 418 | 419 | [ 420 | "lit", 421 | "-"]], 422 | 423 | 424 | [ 425 | "val0", 426 | [ 427 | "peekNot", 428 | [ 429 | "lit", 430 | "\\\\"]], 431 | 432 | 433 | [ 434 | "dot"]]]], 435 | 436 | 437 | 438 | 439 | [ 440 | "def", 441 | "LEFTARROW", 442 | [ 443 | "val0", 444 | [ 445 | "lit", 446 | "<-"], 447 | 448 | "_Spacing"]], 449 | 450 | 451 | [ 452 | "def", 453 | "_SLASH", 454 | [ 455 | "act", 456 | 18, 457 | [ 458 | "lit", 459 | "/"], 460 | 461 | "_Spacing"]], 462 | 463 | 464 | [ 465 | "def", 466 | "SEMI", 467 | [ 468 | "val0", 469 | [ 470 | "lit", 471 | ";"], 472 | 473 | "_Spacing"]], 474 | 475 | 476 | [ 477 | "def", 478 | "AND", 479 | [ 480 | "val0", 481 | [ 482 | "lit", 483 | "&"], 484 | 485 | "_Spacing"]], 486 | 487 | 488 | [ 489 | "def", 490 | "NOT", 491 | [ 492 | "val0", 493 | [ 494 | "lit", 495 | "~"], 496 | 497 | "_Spacing"]], 498 | 499 | 500 | [ 501 | "def", 502 | "QUESTION", 503 | [ 504 | "val0", 505 | [ 506 | "lit", 507 | "?"], 508 | 509 | "_Spacing"]], 510 | 511 | 512 | [ 513 | "def", 514 | "STAR", 515 | [ 516 | "val0", 517 | [ 518 | "lit", 519 | "*"], 520 | 521 | "_Spacing"]], 522 | 523 | 524 | [ 525 | "def", 526 | "PLUS", 527 | [ 528 | "val0", 529 | [ 530 | "lit", 531 | "+"], 532 | 533 | "_Spacing"]], 534 | 535 | 536 | [ 537 | "def", 538 | "OPEN", 539 | [ 540 | "val0", 541 | [ 542 | "lit", 543 | "("], 544 | 545 | "_Spacing"]], 546 | 547 | 548 | [ 549 | "def", 550 | "CLOSE", 551 | [ 552 | "val0", 553 | [ 554 | "lit", 555 | ")"], 556 | 557 | "_Spacing"]], 558 | 559 | 560 | [ 561 | "def", 562 | "DOT", 563 | [ 564 | "val0", 565 | [ 566 | "lit", 567 | "."], 568 | 569 | "_Spacing"]], 570 | 571 | 572 | [ 573 | "def", 574 | "_Spacing", 575 | [ 576 | "act", 577 | 19, 578 | [ 579 | "*", 580 | [ 581 | "or", 582 | [ 583 | "val0", 584 | "Space"], 585 | 586 | [ 587 | "val0", 588 | "Comment"]]]]], 589 | 590 | 591 | 592 | 593 | 594 | [ 595 | "def", 596 | "Comment", 597 | [ 598 | "val0", 599 | [ 600 | "lit", 601 | "#"], 602 | 603 | [ 604 | "*", 605 | [ 606 | "val0", 607 | [ 608 | "peekNot", 609 | "EndOfLine"], 610 | 611 | [ 612 | "dot"]]], 613 | 614 | 615 | 616 | "EndOfLine"]], 617 | 618 | 619 | [ 620 | "def", 621 | "Space", 622 | [ 623 | "or", 624 | [ 625 | "val0", 626 | [ 627 | "lit", 628 | " "]], 629 | 630 | 631 | [ 632 | "val0", 633 | [ 634 | "lit", 635 | "\\t"]], 636 | 637 | 638 | [ 639 | "val0", 640 | "EndOfLine"]]], 641 | 642 | 643 | 644 | [ 645 | "def", 646 | "EndOfLine", 647 | [ 648 | "or", 649 | [ 650 | "val0", 651 | [ 652 | "lit", 653 | "\\r\\n"]], 654 | 655 | 656 | [ 657 | "val0", 658 | [ 659 | "lit", 660 | "\\n"]], 661 | 662 | 663 | [ 664 | "val0", 665 | [ 666 | "lit", 667 | "\\r"]]]], 668 | 669 | 670 | 671 | 672 | [ 673 | "def", 674 | "_EndOfFile", 675 | [ 676 | "val0", 677 | [ 678 | "peekNot", 679 | [ 680 | "dot"]]]], 681 | 682 | 683 | 684 | 685 | [ 686 | "def", 687 | "HOLE", 688 | [ 689 | "val0", 690 | [ 691 | "pred", 692 | 20], 693 | 694 | "_Spacing"]], 695 | 696 | 697 | [ 698 | "def", 699 | "BEGIN", 700 | [ 701 | "val0", 702 | [ 703 | "lit", 704 | "<"], 705 | 706 | "_Spacing"]], 707 | 708 | 709 | [ 710 | "def", 711 | "END", 712 | [ 713 | "val0", 714 | [ 715 | "lit", 716 | ">"], 717 | 718 | "_Spacing"]], 719 | 720 | 721 | [ 722 | "def", 723 | "PLUSPLUS", 724 | [ 725 | "val0", 726 | [ 727 | "lit", 728 | "++"], 729 | 730 | "_Spacing"]], 731 | 732 | 733 | [ 734 | "def", 735 | "STARSTAR", 736 | [ 737 | "val0", 738 | [ 739 | "lit", 740 | "**"], 741 | 742 | "_Spacing"]]]); -------------------------------------------------------------------------------- /lib/emit-c.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; const emitMain = insulate((deps, _argv) => { 2 | deps.writeOutput('-', '/* FIXME: Stub */\n'); 3 | }); 4 | 5 | export default emitMain; -------------------------------------------------------------------------------- /lib/emit-c.js.ts: -------------------------------------------------------------------------------- 1 | const emitMain = (deps: IMainDependencies, _argv: string[]) => { 2 | deps.writeOutput('-', '/* FIXME: Stub */\n'); 3 | }; 4 | 5 | export default emitMain; 6 | -------------------------------------------------------------------------------- /lib/importer.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; import { slog as $i_slog } from '@michaelfig/slog';const slog = insulate($i_slog); 2 | import $i_tagString from './tag-string.js';const tagString = insulate($i_tagString); 3 | 4 | const makeImporter = insulate(( 5 | importCache, 6 | readInput, 7 | languageTag) => { 8 | const IMPORT_RECURSION = { 9 | toString: () => 'IMPORT_RECURSION' }; 10 | 11 | 12 | function importer(file, evaluator) { 13 | let val; 14 | if (!importCache.has(file)) { 15 | try { 16 | importCache.set(file, IMPORT_RECURSION); 17 | const src = readInput(file); 18 | const strParser = tagString(languageTag, file); 19 | const ast = strParser`${src}`; 20 | val = evaluator(ast); 21 | } catch (e) { 22 | // Clean up our recursion tag on error. 23 | importCache.delete(file); 24 | throw e; 25 | } 26 | importCache.set(file, val); 27 | } else { 28 | val = importCache.get(file); 29 | if (val === IMPORT_RECURSION) { 30 | slog.error`Import recursion while resolving ${{ file }}`; 31 | } 32 | } 33 | return val; 34 | } 35 | return importer; 36 | }); 37 | 38 | export default makeImporter; -------------------------------------------------------------------------------- /lib/importer.js.ts: -------------------------------------------------------------------------------- 1 | import { slog } from '@michaelfig/slog'; 2 | import tagString from './tag-string.js'; 3 | 4 | const makeImporter = ( 5 | importCache: Map, 6 | readInput: (file: string) => string, 7 | languageTag: IParserTag) => { 8 | const IMPORT_RECURSION = { 9 | toString: () => 'IMPORT_RECURSION', 10 | }; 11 | 12 | function importer(file: string, evaluator: (ast: any[]) => any) { 13 | let val: any; 14 | if (!importCache.has(file)) { 15 | try { 16 | importCache.set(file, IMPORT_RECURSION); 17 | const src = readInput(file); 18 | const strParser = tagString(languageTag, file); 19 | const ast = strParser`${src}`; 20 | val = evaluator(ast); 21 | } catch (e) { 22 | // Clean up our recursion tag on error. 23 | importCache.delete(file); 24 | throw e; 25 | } 26 | importCache.set(file, val); 27 | } else { 28 | val = importCache.get(file); 29 | if (val === IMPORT_RECURSION) { 30 | slog.error`Import recursion while resolving ${{file}}`; 31 | } 32 | } 33 | return val; 34 | } 35 | return importer; 36 | }; 37 | 38 | export default makeImporter; 39 | -------------------------------------------------------------------------------- /lib/indent.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; const indent = insulate((template, ...substs) => { 2 | const result = []; 3 | let newnewline = '\n'; 4 | for (let i = 0, ilen = substs.length; i < ilen; i++) { 5 | let segment = template[i]; 6 | if (i === 0 && segment[0].startsWith('\n')) { 7 | segment = segment.slice(1); 8 | } 9 | const lastnl = segment.lastIndexOf('\n'); 10 | if (lastnl >= 0) { 11 | newnewline = '\n'; 12 | for (let j = segment.length - lastnl; j > 0; j--) { 13 | newnewline += ' '; 14 | } 15 | } 16 | result.push(segment); 17 | // We don't have regexps at our disposal in Jessie. 18 | String(substs[i]).split('\n').forEach((subst, j) => { 19 | if (j !== 0) { 20 | result.push(newnewline); 21 | } 22 | result.push(subst); 23 | }); 24 | } 25 | result.push(template[substs.length]); 26 | return result.join(''); 27 | }); 28 | 29 | export default indent; -------------------------------------------------------------------------------- /lib/indent.js.ts: -------------------------------------------------------------------------------- 1 | const indent = (template: TemplateStringsArray, ...substs: any[]) => { 2 | const result = []; 3 | let newnewline = '\n'; 4 | for (let i = 0, ilen = substs.length; i < ilen; i++) { 5 | let segment = template[i]; 6 | if (i === 0 && segment[0].startsWith('\n')) { 7 | segment = segment.slice(1); 8 | } 9 | const lastnl = segment.lastIndexOf('\n'); 10 | if (lastnl >= 0) { 11 | newnewline = '\n'; 12 | for (let j = segment.length - lastnl; j > 0; j --) { 13 | newnewline += ' '; 14 | } 15 | } 16 | result.push(segment); 17 | // We don't have regexps at our disposal in Jessie. 18 | String(substs[i]).split('\n').forEach((subst, j) => { 19 | if (j !== 0) { 20 | result.push(newnewline); 21 | } 22 | result.push(subst); 23 | }); 24 | } 25 | result.push(template[substs.length]); 26 | return result.join(''); 27 | }; 28 | 29 | export default indent; 30 | -------------------------------------------------------------------------------- /lib/interp-json.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; import { doEval as $i_doEval } from './interp-utils.js';const doEval = insulate($i_doEval); 2 | 3 | const jsonEvaluators = insulate({ 4 | array(self, elems) { 5 | const arr = elems.map(el => doEval(self, el)); 6 | return arr; 7 | }, 8 | data(_self, val) { 9 | return val; 10 | }, 11 | prop(self, name, expr) { 12 | const val = doEval(self, expr); 13 | return [name, val]; 14 | }, 15 | record(self, propDefs) { 16 | const obj = {}; 17 | propDefs.forEach(b => { 18 | const [name, val] = doEval(self, b); 19 | self.setComputedIndex(obj, name, val); 20 | }); 21 | return obj; 22 | } }); 23 | 24 | 25 | export default jsonEvaluators; -------------------------------------------------------------------------------- /lib/interp-json.js.ts: -------------------------------------------------------------------------------- 1 | import {doEval, Evaluators, IEvalContext} from './interp-utils.js'; 2 | 3 | const jsonEvaluators: Evaluators = { 4 | array(self: IEvalContext, elems: any[][]) { 5 | const arr = elems.map(el => doEval(self, el)); 6 | return arr; 7 | }, 8 | data(_self: IEvalContext, val: any) { 9 | return val; 10 | }, 11 | prop(self: IEvalContext, name: string, expr: any[]) { 12 | const val = doEval(self, expr); 13 | return [name, val]; 14 | }, 15 | record(self: IEvalContext, propDefs: any[][]) { 16 | const obj: Record = {}; 17 | propDefs.forEach(b => { 18 | const [name, val] = doEval(self, b); 19 | self.setComputedIndex(obj, name, val); 20 | }); 21 | return obj; 22 | }, 23 | }; 24 | 25 | export default jsonEvaluators; 26 | -------------------------------------------------------------------------------- /lib/interp-justin.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; import $i_jsonEvaluators from './interp-json.js';const jsonEvaluators = insulate($i_jsonEvaluators); 2 | import { BINDING_GET as $i_BINDING_GET, doEval as $i_doEval, err as $i_err, 3 | getRef as $i_getRef, SCOPE_GET as $i_SCOPE_GET } from './interp-utils.js';const SCOPE_GET = insulate($i_SCOPE_GET);const getRef = insulate($i_getRef);const err = insulate($i_err);const doEval = insulate($i_doEval);const BINDING_GET = insulate($i_BINDING_GET); 4 | import { qrepack as $i_qrepack } from './quasi-utils.js';const qrepack = insulate($i_qrepack); 5 | 6 | const justinEvaluators = insulate({ 7 | ...jsonEvaluators, 8 | 'pre:+'(self, expr) { 9 | return +doEval(self, expr); 10 | }, 11 | 'pre:-'(self, expr) { 12 | return -doEval(self, expr); 13 | }, 14 | 'pre:~'(self, expr) { 15 | return ~doEval(self, expr); 16 | }, 17 | 'pre:!'(self, expr) { 18 | return !doEval(self, expr); 19 | }, 20 | '**'(self, a, b) { 21 | const aval = doEval(self, a); 22 | const bval = doEval(self, b); 23 | return aval ** bval; 24 | }, 25 | '*'(self, a, b) { 26 | const aval = doEval(self, a); 27 | const bval = doEval(self, b); 28 | return aval * bval; 29 | }, 30 | '/'(self, a, b) { 31 | const aval = doEval(self, a); 32 | const bval = doEval(self, b); 33 | return aval / bval; 34 | }, 35 | '%'(self, a, b) { 36 | const aval = doEval(self, a); 37 | const bval = doEval(self, b); 38 | return aval % bval; 39 | }, 40 | '+'(self, a, b) { 41 | const aval = doEval(self, a); 42 | const bval = doEval(self, b); 43 | return aval + bval; 44 | }, 45 | '-'(self, a, b) { 46 | const aval = doEval(self, a); 47 | const bval = doEval(self, b); 48 | return aval - bval; 49 | }, 50 | '<<'(self, a, b) { 51 | const aval = doEval(self, a); 52 | const bval = doEval(self, b); 53 | return aval << bval; 54 | }, 55 | '>>>'(self, a, b) { 56 | const aval = doEval(self, a); 57 | const bval = doEval(self, b); 58 | return aval >>> bval; 59 | }, 60 | '>>'(self, a, b) { 61 | const aval = doEval(self, a); 62 | const bval = doEval(self, b); 63 | return aval >> bval; 64 | }, 65 | '<='(self, a, b) { 66 | const aval = doEval(self, a); 67 | const bval = doEval(self, b); 68 | return aval <= bval; 69 | }, 70 | '<'(self, a, b) { 71 | const aval = doEval(self, a); 72 | const bval = doEval(self, b); 73 | return aval < bval; 74 | }, 75 | '>='(self, a, b) { 76 | const aval = doEval(self, a); 77 | const bval = doEval(self, b); 78 | return aval >= bval; 79 | }, 80 | '>'(self, a, b) { 81 | const aval = doEval(self, a); 82 | const bval = doEval(self, b); 83 | return aval > bval; 84 | }, 85 | '!=='(self, a, b) { 86 | const aval = doEval(self, a); 87 | const bval = doEval(self, b); 88 | return aval !== bval; 89 | }, 90 | '==='(self, a, b) { 91 | const aval = doEval(self, a); 92 | const bval = doEval(self, b); 93 | return aval === bval; 94 | }, 95 | '&'(self, a, b) { 96 | const aval = doEval(self, a); 97 | const bval = doEval(self, b); 98 | return aval & bval; 99 | }, 100 | '^'(self, a, b) { 101 | const aval = doEval(self, a); 102 | const bval = doEval(self, b); 103 | return aval ^ bval; 104 | }, 105 | '|'(self, a, b) { 106 | const aval = doEval(self, a); 107 | const bval = doEval(self, b); 108 | return aval | bval; 109 | }, 110 | '&&'(self, a, b) { 111 | const aval = doEval(self, a); 112 | const bval = doEval(self, b); 113 | return aval && bval; 114 | }, 115 | '||'(self, a, b) { 116 | const aval = doEval(self, a); 117 | const bval = doEval(self, b); 118 | return aval || bval; 119 | }, 120 | array(self, elems) { 121 | const arr = elems.reduce((prior, el) => { 122 | const val = doEval(self, el); 123 | if (el[0] === 'spread') { 124 | for (const v of val) { 125 | prior.push(v); 126 | } 127 | } else { 128 | prior.push(val); 129 | } 130 | return prior; 131 | }, []); 132 | return arr; 133 | }, 134 | call(self, func, args) { 135 | const { getter, thisObj } = getRef(self, func); 136 | const evaledArgs = args.map(a => doEval(self, a)); 137 | const method = getter(); 138 | return self.applyMethod(thisObj, method, evaledArgs); 139 | }, 140 | cond(self, c, t, e) { 141 | const cval = doEval(self, c); 142 | if (cval) { 143 | return doEval(self, t); 144 | } 145 | return doEval(self, e); 146 | }, 147 | get(self, objExpr, id) { 148 | const obj = doEval(self, objExpr); 149 | return obj[id]; 150 | }, 151 | index(self, objExpr, expr) { 152 | const obj = doEval(self, objExpr); 153 | const index = doEval(self, expr); 154 | if (typeof index !== 'number') { 155 | err(self)`Index value ${{ index }} is not numeric`; 156 | } 157 | return obj[index]; 158 | }, 159 | quasi(self, parts) { 160 | const argExprs = qrepack(parts); 161 | const [template, ...args] = argExprs.map(expr => doEval(self, expr)); 162 | 163 | return args.reduce((prior, a, i) => 164 | prior + String(a) + template[i + 1], template[0]); 165 | }, 166 | record(self, propDefs) { 167 | const obj = {}; 168 | propDefs.forEach(b => { 169 | if (b[0] === 'spreadObj') { 170 | const spreader = doEval(self, b); 171 | for (const [name, val] of Object.entries(spreader)) { 172 | self.setComputedIndex(obj, name, val); 173 | } 174 | } else { 175 | const [name, val] = doEval(self, b); 176 | self.setComputedIndex(obj, name, val); 177 | } 178 | }); 179 | return obj; 180 | }, 181 | spread(self, arrExpr) { 182 | const arr = doEval(self, arrExpr); 183 | return arr; 184 | }, 185 | spreadObj(self, objExpr) { 186 | const obj = doEval(self, objExpr); 187 | return obj; 188 | }, 189 | tag(self, tagExpr, quasiExpr) { 190 | const { getter, thisObj } = getRef(self, tagExpr); 191 | const [quasi, parts] = quasiExpr; 192 | if (quasiExpr[0] !== 'quasi') { 193 | err(self)`Unrecognized quasi expression ${{ quasi }}`; 194 | } 195 | const argExprs = qrepack(parts); 196 | const args = argExprs.map(expr => doEval(self, expr)); 197 | return self.applyMethod(thisObj, getter(), args); 198 | }, 199 | typeof(self, expr) { 200 | if (expr[0] === 'use') { 201 | const name = expr[1]; 202 | const b = self.scope()[SCOPE_GET](name); 203 | if (b) { 204 | return typeof b[BINDING_GET](); 205 | } 206 | // Special case: just return undefined on missing lookup. 207 | return undefined; 208 | } 209 | 210 | const val = doEval(self, expr); 211 | return typeof val; 212 | }, 213 | use(self, name) { 214 | const b = self.scope()[SCOPE_GET](name); 215 | if (b) { 216 | return b[BINDING_GET](); 217 | } 218 | err(self)`ReferenceError: ${{ name }} is not defined`; 219 | }, 220 | void(self, expr) { 221 | doEval(self, expr); 222 | return undefined; 223 | } }); 224 | 225 | 226 | export default justinEvaluators; -------------------------------------------------------------------------------- /lib/interp-justin.js.ts: -------------------------------------------------------------------------------- 1 | import jsonEvaluators from './interp-json.js'; 2 | import {BINDING_GET, doEval, err, Evaluator, 3 | getRef, IEvalContext, SCOPE_GET} from './interp-utils.js'; 4 | import {qrepack} from './quasi-utils.js'; 5 | 6 | const justinEvaluators: Record = { 7 | ...jsonEvaluators, 8 | 'pre:+'(self: IEvalContext, expr: any[]) { 9 | return +doEval(self, expr); 10 | }, 11 | 'pre:-'(self: IEvalContext, expr: any[]) { 12 | return -doEval(self, expr); 13 | }, 14 | 'pre:~'(self: IEvalContext, expr: any[]) { 15 | return ~doEval(self, expr); 16 | }, 17 | 'pre:!'(self: IEvalContext, expr: any[]) { 18 | return !doEval(self, expr); 19 | }, 20 | '**'(self: IEvalContext, a: any[], b: any[]) { 21 | const aval = doEval(self, a); 22 | const bval = doEval(self, b); 23 | return aval ** bval; 24 | }, 25 | '*'(self: IEvalContext, a: any[], b: any[]) { 26 | const aval = doEval(self, a); 27 | const bval = doEval(self, b); 28 | return aval * bval; 29 | }, 30 | '/'(self: IEvalContext, a: any[], b: any[]) { 31 | const aval = doEval(self, a); 32 | const bval = doEval(self, b); 33 | return aval / bval; 34 | }, 35 | '%'(self: IEvalContext, a: any[], b: any[]) { 36 | const aval = doEval(self, a); 37 | const bval = doEval(self, b); 38 | return aval % bval; 39 | }, 40 | '+'(self: IEvalContext, a: any[], b: any[]) { 41 | const aval = doEval(self, a); 42 | const bval = doEval(self, b); 43 | return aval + bval; 44 | }, 45 | '-'(self: IEvalContext, a: any[], b: any[]) { 46 | const aval = doEval(self, a); 47 | const bval = doEval(self, b); 48 | return aval - bval; 49 | }, 50 | '<<'(self: IEvalContext, a: any[], b: any[]) { 51 | const aval = doEval(self, a); 52 | const bval = doEval(self, b); 53 | return aval << bval; 54 | }, 55 | '>>>'(self: IEvalContext, a: any[], b: any[]) { 56 | const aval = doEval(self, a); 57 | const bval = doEval(self, b); 58 | return aval >>> bval; 59 | }, 60 | '>>'(self: IEvalContext, a: any[], b: any[]) { 61 | const aval = doEval(self, a); 62 | const bval = doEval(self, b); 63 | return aval >> bval; 64 | }, 65 | '<='(self: IEvalContext, a: any[], b: any[]) { 66 | const aval = doEval(self, a); 67 | const bval = doEval(self, b); 68 | return aval <= bval; 69 | }, 70 | '<'(self: IEvalContext, a: any[], b: any[]) { 71 | const aval = doEval(self, a); 72 | const bval = doEval(self, b); 73 | return aval < bval; 74 | }, 75 | '>='(self: IEvalContext, a: any[], b: any[]) { 76 | const aval = doEval(self, a); 77 | const bval = doEval(self, b); 78 | return aval >= bval; 79 | }, 80 | '>'(self: IEvalContext, a: any[], b: any[]) { 81 | const aval = doEval(self, a); 82 | const bval = doEval(self, b); 83 | return aval > bval; 84 | }, 85 | '!=='(self: IEvalContext, a: any[], b: any[]) { 86 | const aval = doEval(self, a); 87 | const bval = doEval(self, b); 88 | return aval !== bval; 89 | }, 90 | '==='(self: IEvalContext, a: any[], b: any[]) { 91 | const aval = doEval(self, a); 92 | const bval = doEval(self, b); 93 | return aval === bval; 94 | }, 95 | '&'(self: IEvalContext, a: any[], b: any[]) { 96 | const aval = doEval(self, a); 97 | const bval = doEval(self, b); 98 | return aval & bval; 99 | }, 100 | '^'(self: IEvalContext, a: any[], b: any[]) { 101 | const aval = doEval(self, a); 102 | const bval = doEval(self, b); 103 | return aval ^ bval; 104 | }, 105 | '|'(self: IEvalContext, a: any[], b: any[]) { 106 | const aval = doEval(self, a); 107 | const bval = doEval(self, b); 108 | return aval | bval; 109 | }, 110 | '&&'(self: IEvalContext, a: any[], b: any[]) { 111 | const aval = doEval(self, a); 112 | const bval = doEval(self, b); 113 | return aval && bval; 114 | }, 115 | '||'(self: IEvalContext, a: any[], b: any[]) { 116 | const aval = doEval(self, a); 117 | const bval = doEval(self, b); 118 | return aval || bval; 119 | }, 120 | array(self: IEvalContext, elems: any[][]) { 121 | const arr = elems.reduce((prior, el) => { 122 | const val = doEval(self, el); 123 | if (el[0] === 'spread') { 124 | for (const v of val) { 125 | prior.push(v); 126 | } 127 | } else { 128 | prior.push(val); 129 | } 130 | return prior; 131 | }, []); 132 | return arr; 133 | }, 134 | call(self: IEvalContext, func: any[], args: any[][]) { 135 | const {getter, thisObj} = getRef(self, func); 136 | const evaledArgs = args.map((a) => doEval(self, a)); 137 | const method = getter(); 138 | return self.applyMethod(thisObj, method, evaledArgs); 139 | }, 140 | cond(self: IEvalContext, c: any[], t: any[], e: any[]) { 141 | const cval = doEval(self, c); 142 | if (cval) { 143 | return doEval(self, t); 144 | } 145 | return doEval(self, e); 146 | }, 147 | get(self: IEvalContext, objExpr: any[], id: string) { 148 | const obj = doEval(self, objExpr); 149 | return obj[id]; 150 | }, 151 | index(self: IEvalContext, objExpr: any[], expr: any[]) { 152 | const obj = doEval(self, objExpr); 153 | const index = doEval(self, expr); 154 | if (typeof index !== 'number') { 155 | err(self)`Index value ${{index}} is not numeric`; 156 | } 157 | return obj[index]; 158 | }, 159 | quasi(self: IEvalContext, parts: any[]) { 160 | const argExprs = qrepack(parts); 161 | const [template, ...args] = argExprs.map(expr => doEval(self, expr)); 162 | 163 | return args.reduce((prior, a, i) => 164 | prior + String(a) + template[i + 1], template[0]); 165 | }, 166 | record(self: IEvalContext, propDefs: any[][]) { 167 | const obj: Record = {}; 168 | propDefs.forEach(b => { 169 | if (b[0] === 'spreadObj') { 170 | const spreader = doEval(self, b); 171 | for (const [name, val] of Object.entries(spreader)) { 172 | self.setComputedIndex(obj, name, val); 173 | } 174 | } else { 175 | const [name, val] = doEval(self, b); 176 | self.setComputedIndex(obj, name, val); 177 | } 178 | }); 179 | return obj; 180 | }, 181 | spread(self: IEvalContext, arrExpr: any[][]) { 182 | const arr = doEval(self, arrExpr); 183 | return arr; 184 | }, 185 | spreadObj(self: IEvalContext, objExpr: any[]) { 186 | const obj = doEval(self, objExpr); 187 | return obj; 188 | }, 189 | tag(self: IEvalContext, tagExpr: any[], quasiExpr: any[]) { 190 | const {getter, thisObj} = getRef(self, tagExpr); 191 | const [quasi, parts] = quasiExpr; 192 | if (quasiExpr[0] !== 'quasi') { 193 | err(self)`Unrecognized quasi expression ${{quasi}}`; 194 | } 195 | const argExprs = qrepack(parts); 196 | const args = argExprs.map(expr => doEval(self, expr)); 197 | return self.applyMethod(thisObj, getter(), args); 198 | }, 199 | typeof(self: IEvalContext, expr: any[]) { 200 | if (expr[0] === 'use') { 201 | const name = expr[1]; 202 | const b = self.scope()[SCOPE_GET](name); 203 | if (b) { 204 | return typeof b[BINDING_GET](); 205 | } 206 | // Special case: just return undefined on missing lookup. 207 | return undefined; 208 | } 209 | 210 | const val = doEval(self, expr); 211 | return typeof val; 212 | }, 213 | use(self: IEvalContext, name: string) { 214 | const b = self.scope()[SCOPE_GET](name); 215 | if (b) { 216 | return b[BINDING_GET](); 217 | } 218 | err(self)`ReferenceError: ${{name}} is not defined`; 219 | }, 220 | void(self: IEvalContext, expr: any[]) { 221 | doEval(self, expr); 222 | return undefined; 223 | }, 224 | }; 225 | 226 | export default justinEvaluators; 227 | -------------------------------------------------------------------------------- /lib/interp-utils.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; import { makeMap } from '@agoric/jessie'; 2 | import { slog as $i_slog } from '@michaelfig/slog';const slog = insulate($i_slog); 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | export const BINDING_GET = 0; 13 | export const BINDING_SET = 1; 14 | 15 | 16 | 17 | 18 | 19 | 20 | export const SCOPE_PARENT = 0; 21 | export const SCOPE_GET = 1; 22 | export const SCOPE_SET = 2; 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | const UNINITIALIZED = insulate({ toString() {return 'UNINITIALIZED';} }); 43 | export const addBinding = insulate(( 44 | self, name, 45 | mutable, init = UNINITIALIZED) => { 46 | let slot, setter; 47 | if (mutable) { 48 | setter = val => slot = val; 49 | } 50 | if (init === UNINITIALIZED) { 51 | if (!mutable) { 52 | let allow = true; 53 | setter = val => { 54 | if (!allow) { 55 | throw err(self)`${{ name }} already initialized`; 56 | } 57 | allow = false; 58 | return slot = val; 59 | }; 60 | } 61 | } else { 62 | slot = init; 63 | } 64 | const b = [() => slot, setter]; 65 | return self.binding(name, b); 66 | }); 67 | 68 | export const err = insulate(self => { 69 | slog.info`${self.uri} at ${self.pos()}`; 70 | return slog.error; 71 | }); 72 | 73 | export const doEval = insulate((self, ast, overrideName, label) => { 74 | const [astName, ...args] = ast; 75 | const name = overrideName || astName; 76 | const ev = self.evaluators[name]; 77 | const pos = ast._pegPosition; 78 | const oldPos = self.pos(pos); 79 | // Always reset the label to either undefined or the one the caller passed. 80 | self.setLabel(label); 81 | try { 82 | if (!ev) { 83 | throw err(self)`No ${{ name }} implementation`; 84 | } 85 | return ev(self, ...args); 86 | } finally { 87 | self.pos(oldPos); 88 | } 89 | }); 90 | 91 | const makeInterp = insulate(( 92 | evaluators, 93 | applyMethod, 94 | importer, 95 | setComputedIndex) => { 96 | function interp(ast, endowments, options) { 97 | const lastSlash = options.scriptName === undefined ? -1 : options.scriptName.lastIndexOf('/'); 98 | const thisDir = lastSlash < 0 ? '.' : options.scriptName.slice(0, lastSlash); 99 | let scope,pos = '',label; 100 | 101 | const self = { 102 | applyMethod, 103 | dir: thisDir, 104 | evaluators, 105 | import(path) { 106 | const val = importer(path, iast => interp(iast, endowments, { scriptName: path })); 107 | slog.info(`imported ${path} as ${{ val }}`); 108 | return val; 109 | }, 110 | setComputedIndex, 111 | binding(name, newBinding) { 112 | if (newBinding) { 113 | scope[SCOPE_SET](name, newBinding); 114 | } else { 115 | newBinding = scope[SCOPE_GET](name); 116 | } 117 | return newBinding; 118 | }, 119 | pos(newPos) { 120 | const oldPos = pos; 121 | if (newPos) { 122 | pos = newPos; 123 | } 124 | return oldPos; 125 | }, 126 | scope(newScope) { 127 | const oldScope = scope; 128 | if (newScope) { 129 | if (newScope === true) { 130 | const map = makeMap(); 131 | newScope = [ 132 | oldScope, 133 | (name) => 134 | map.get(name) || oldScope && oldScope[SCOPE_GET](name), 135 | (name, binding) => 136 | map.has(name) ? err(self)`Cannot redefine ${{ name }}` : map.set(name, binding)]; 137 | 138 | } 139 | scope = newScope; 140 | } 141 | return oldScope; 142 | }, 143 | setLabel(newLabel) { 144 | // This always removes the old label. 145 | const oldLabel = label; 146 | label = newLabel; 147 | return oldLabel; 148 | }, 149 | uri: options.scriptName }; 150 | 151 | 152 | // slog.info`AST: ${{ast}}`; 153 | self.scope(true); 154 | for (const [name, value] of Object.entries(endowments)) { 155 | // slog.info`Adding ${name}, ${value} to bindings`; 156 | addBinding(self, name, false, value); 157 | } 158 | return doEval(self, ast); 159 | } 160 | 161 | return interp; 162 | }); 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | export const getRef = insulate((self, astNode, mutable = true) => { 171 | const oldPos = self.pos(); 172 | try { 173 | const pos = astNode._pegPosition; 174 | self.pos(pos); 175 | switch (astNode[0]) { 176 | case 'use':{ 177 | const name = astNode[1]; 178 | const b = self.binding(name); 179 | if (b) { 180 | return { getter: b[BINDING_GET], setter: b[BINDING_SET] }; 181 | } 182 | throw err(self)`ReferenceError: ${{ name }} is not defined`; 183 | } 184 | 185 | case 'get':{ 186 | const [objExpr, id] = astNode.slice(1); 187 | const obj = doEval(self, objExpr); 188 | return { 189 | getter: () => obj[id], 190 | setter: val => self.setComputedIndex(obj, id, val), 191 | thisObj: obj }; 192 | 193 | } 194 | 195 | case 'def':{ 196 | const name = astNode[1]; 197 | const b = addBinding(self, name, mutable); 198 | return { getter: b[BINDING_GET], setter: b[BINDING_SET] }; 199 | } 200 | 201 | default:{ 202 | throw err(self)`Reference type ${{ type: astNode[0] }} not implemented`; 203 | }} 204 | 205 | } finally { 206 | self.pos(oldPos); 207 | } 208 | }); 209 | 210 | export default makeInterp; -------------------------------------------------------------------------------- /lib/interp-utils.js.ts: -------------------------------------------------------------------------------- 1 | import { makeMap } from '@agoric/jessie'; 2 | import { slog } from '@michaelfig/slog'; 3 | 4 | export interface IEvalOptions { 5 | [key: string]: any; 6 | scriptName?: string; 7 | } 8 | 9 | export type Evaluator = (self: IEvalContext, ...args: any[]) => any; 10 | export type Evaluators = Record; 11 | 12 | export const BINDING_GET = 0; 13 | export const BINDING_SET = 1; 14 | 15 | export interface IBinding { 16 | [BINDING_GET]: () => any; 17 | [BINDING_SET]?: (val: T) => T; 18 | } 19 | 20 | export const SCOPE_PARENT = 0; 21 | export const SCOPE_GET = 1; 22 | export const SCOPE_SET = 2; 23 | export interface IScope { 24 | [SCOPE_PARENT]: IScope | undefined; 25 | [SCOPE_GET]: (name: string) => IBinding | undefined; 26 | [SCOPE_SET]: (name: string, binding: IBinding) => void; 27 | } 28 | 29 | export interface IEvalContext { 30 | applyMethod: (obj: any, lambda: (...args: any[]) => any, args: any[]) => any; 31 | setComputedIndex: (obj: Record, key: string | number, val: T) => T; 32 | binding: (name: string, binding?: IBinding) => IBinding; 33 | dir: string; 34 | evaluators: Evaluators; 35 | import: (path: string) => Record; 36 | setLabel: (label: string | undefined) => string | undefined; 37 | scope: (scope?: IScope | true) => IScope; 38 | pos: (pos?: string) => string; 39 | uri: string; 40 | } 41 | 42 | const UNINITIALIZED = {toString() { return 'UNINITIALIZED'; }}; 43 | export const addBinding = ( 44 | self: IEvalContext, name: string, 45 | mutable: boolean, init: any = UNINITIALIZED): IBinding => { 46 | let slot: any, setter: ((val: T) => T); 47 | if (mutable) { 48 | setter = (val: T) => slot = val; 49 | } 50 | if (init === UNINITIALIZED) { 51 | if (!mutable) { 52 | let allow = true; 53 | setter = (val: T) => { 54 | if (!allow) { 55 | throw err(self)`${{name}} already initialized`; 56 | } 57 | allow = false; 58 | return slot = val; 59 | }; 60 | } 61 | } else { 62 | slot = init; 63 | } 64 | const b: IBinding = [() => slot, setter]; 65 | return self.binding(name, b); 66 | }; 67 | 68 | export const err = (self: IEvalContext) => { 69 | slog.info`${self.uri} at ${self.pos()}`; 70 | return slog.error; 71 | }; 72 | 73 | export const doEval = (self: IEvalContext, ast: any[], overrideName?: string, label?: string): T => { 74 | const [astName, ...args] = ast; 75 | const name = overrideName || astName; 76 | const ev = self.evaluators[name]; 77 | const pos = (ast as any)._pegPosition; 78 | const oldPos = self.pos(pos); 79 | // Always reset the label to either undefined or the one the caller passed. 80 | self.setLabel(label); 81 | try { 82 | if (!ev) { 83 | throw err(self)`No ${{name}} implementation`; 84 | } 85 | return ev(self, ...args); 86 | } finally { 87 | self.pos(oldPos); 88 | } 89 | }; 90 | 91 | const makeInterp = ( 92 | evaluators: Evaluators, 93 | applyMethod: (boundThis: any, method: (...args: any[]) => any, args: any[]) => any, 94 | importer: (path: string, evaluator: (ast: any[]) => any) => Record, 95 | setComputedIndex: (obj: Record, index: string | number, value: T) => T) => { 96 | function interp(ast: any[], endowments: Record, options?: IEvalOptions): any { 97 | const lastSlash = options.scriptName === undefined ? -1 : options.scriptName.lastIndexOf('/'); 98 | const thisDir = lastSlash < 0 ? '.' : options.scriptName.slice(0, lastSlash); 99 | let scope: IScope, pos = '', label: string; 100 | 101 | const self: IEvalContext = { 102 | applyMethod, 103 | dir: thisDir, 104 | evaluators, 105 | import(path) { 106 | const val = importer(path, (iast: any[]) => interp(iast, endowments, {scriptName: path})); 107 | slog.info(`imported ${path} as ${{val}}`); 108 | return val; 109 | }, 110 | setComputedIndex, 111 | binding(name: string, newBinding?: IBinding) { 112 | if (newBinding) { 113 | scope[SCOPE_SET](name, newBinding); 114 | } else { 115 | newBinding = scope[SCOPE_GET](name); 116 | } 117 | return newBinding; 118 | }, 119 | pos(newPos?: string) { 120 | const oldPos = pos; 121 | if (newPos) { 122 | pos = newPos; 123 | } 124 | return oldPos; 125 | }, 126 | scope(newScope?: IScope | true) { 127 | const oldScope = scope; 128 | if (newScope) { 129 | if (newScope === true) { 130 | const map = makeMap(); 131 | newScope = [ 132 | oldScope, 133 | (name: string) => 134 | map.get(name) || (oldScope && oldScope[SCOPE_GET](name)), 135 | (name: string, binding: IBinding) => 136 | map.has(name) ? err(self)`Cannot redefine ${{name}}` : map.set(name, binding), 137 | ]; 138 | } 139 | scope = newScope; 140 | } 141 | return oldScope; 142 | }, 143 | setLabel(newLabel: string | undefined) { 144 | // This always removes the old label. 145 | const oldLabel = label; 146 | label = newLabel; 147 | return oldLabel; 148 | }, 149 | uri: options.scriptName, 150 | }; 151 | 152 | // slog.info`AST: ${{ast}}`; 153 | self.scope(true); 154 | for (const [name, value] of Object.entries(endowments)) { 155 | // slog.info`Adding ${name}, ${value} to bindings`; 156 | addBinding(self, name, false, value); 157 | } 158 | return doEval(self, ast); 159 | } 160 | 161 | return interp; 162 | }; 163 | 164 | interface IRef { 165 | getter: () => any; 166 | setter: (val: T) => T; 167 | thisObj?: any; 168 | } 169 | 170 | export const getRef = (self: IEvalContext, astNode: any[], mutable = true): IRef => { 171 | const oldPos = self.pos(); 172 | try { 173 | const pos = (astNode as any)._pegPosition; 174 | self.pos(pos); 175 | switch (astNode[0]) { 176 | case 'use': { 177 | const name = astNode[1]; 178 | const b = self.binding(name); 179 | if (b) { 180 | return {getter: b[BINDING_GET], setter: b[BINDING_SET]}; 181 | } 182 | throw err(self)`ReferenceError: ${{name}} is not defined`; 183 | } 184 | 185 | case 'get': { 186 | const [objExpr, id] = astNode.slice(1); 187 | const obj = doEval(self, objExpr); 188 | return { 189 | getter: () => obj[id], 190 | setter: (val: T): T => self.setComputedIndex(obj, id, val), 191 | thisObj: obj, 192 | }; 193 | } 194 | 195 | case 'def': { 196 | const name = astNode[1]; 197 | const b = addBinding(self, name, mutable); 198 | return {getter: b[BINDING_GET], setter: b[BINDING_SET]}; 199 | } 200 | 201 | default: { 202 | throw err(self)`Reference type ${{type: astNode[0]}} not implemented`; 203 | } 204 | } 205 | } finally { 206 | self.pos(oldPos); 207 | } 208 | }; 209 | 210 | export default makeInterp; 211 | -------------------------------------------------------------------------------- /lib/main-jesspipe.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; import { makeSet } from '@agoric/jessie'; 2 | import { slog as $i_slog } from '@michaelfig/slog';const slog = insulate($i_slog); 3 | 4 | import $i_bootJessica from './boot-jessica.js';const bootJessica = insulate($i_bootJessica); 5 | import $i_makeReadInput from './readInput.js';const makeReadInput = insulate($i_makeReadInput); 6 | import $i_repl from './repl.js';const repl = insulate($i_repl); 7 | 8 | const jesspipe = insulate((deps, argv) => { 9 | // Read and evaluate the specified module, 10 | if (argv.length < 3) { 11 | slog.panic`You must specify a MODULE`; 12 | } 13 | const MODULE = argv[2] || '-'; 14 | const ARGV = argv.slice(2); 15 | 16 | // Make a confined file loader specified by the arguments. 17 | const dashdash = ARGV.indexOf('--'); 18 | const CAN_LOAD_FILES = makeSet([MODULE]); 19 | if (dashdash >= 0) { 20 | ARGV.slice(dashdash + 1).forEach(file => CAN_LOAD_FILES.add(file)); 21 | } 22 | 23 | const readInput = makeReadInput(CAN_LOAD_FILES, deps.readInput); 24 | 25 | // Make a confined file writer. 26 | const writeOutput = (fname, str) => { 27 | if (fname !== '-') { 28 | slog.error`Cannot write to ${{ fname }}: must be -`; 29 | } 30 | return deps.writeOutput('-', str); 31 | }; 32 | 33 | const jessica = bootJessica(deps.applyMethod, readInput, deps.setComputedIndex); 34 | 35 | const doEval = (src, uri) => 36 | jessica.runModule(src, {}, { scriptName: uri }); 37 | const newDeps = { 38 | applyMethod: deps.applyMethod, 39 | readInput, 40 | setComputedIndex: deps.setComputedIndex, 41 | writeOutput }; 42 | 43 | try { 44 | repl(newDeps, doEval, MODULE, ARGV); 45 | } catch (e) { 46 | deps.writeOutput('-', '/* FIXME: Stub */\n'); 47 | slog.notice`Cannot evaluate ${{ MODULE }}: ${e}`; 48 | } 49 | }); 50 | 51 | export default jesspipe; -------------------------------------------------------------------------------- /lib/main-jesspipe.js.ts: -------------------------------------------------------------------------------- 1 | import { makeSet } from '@agoric/jessie'; 2 | import { slog } from '@michaelfig/slog'; 3 | 4 | import bootJessica from './boot-jessica.js'; 5 | import makeReadInput from './readInput.js'; 6 | import repl from './repl.js'; 7 | 8 | const jesspipe = (deps: IMainDependencies, argv: string[]) => { 9 | // Read and evaluate the specified module, 10 | if (argv.length < 3) { 11 | slog.panic`You must specify a MODULE`; 12 | } 13 | const MODULE = argv[2] || '-'; 14 | const ARGV = argv.slice(2); 15 | 16 | // Make a confined file loader specified by the arguments. 17 | const dashdash = ARGV.indexOf('--'); 18 | const CAN_LOAD_FILES = makeSet([MODULE]); 19 | if (dashdash >= 0) { 20 | ARGV.slice(dashdash + 1).forEach(file => CAN_LOAD_FILES.add(file)); 21 | } 22 | 23 | const readInput = makeReadInput(CAN_LOAD_FILES, deps.readInput); 24 | 25 | // Make a confined file writer. 26 | const writeOutput = (fname: string, str: string) => { 27 | if (fname !== '-') { 28 | slog.error`Cannot write to ${{fname}}: must be -`; 29 | } 30 | return deps.writeOutput('-', str); 31 | }; 32 | 33 | const jessica = bootJessica(deps.applyMethod, readInput, deps.setComputedIndex); 34 | 35 | const doEval = (src: string, uri?: string) => 36 | jessica.runModule(src, {}, {scriptName: uri}); 37 | const newDeps = { 38 | applyMethod: deps.applyMethod, 39 | readInput, 40 | setComputedIndex: deps.setComputedIndex, 41 | writeOutput 42 | }; 43 | try { 44 | repl(newDeps, doEval, MODULE, ARGV); 45 | } catch (e) { 46 | deps.writeOutput('-', '/* FIXME: Stub */\n'); 47 | slog.notice`Cannot evaluate ${{MODULE}}: ${e}`; 48 | } 49 | }; 50 | 51 | export default jesspipe; 52 | -------------------------------------------------------------------------------- /lib/peg.d.ts: -------------------------------------------------------------------------------- 1 | type PegPredicate = (self: any, pos: number) => [number, any]; 2 | interface IStringable { 3 | toString(): string; 4 | } 5 | type PegConstant = Readonly; 6 | 7 | type PegRun = (self: any, ruleOrPatt: PegRuleOrPatt, pos: number, name: string) => [number, string[]]; 8 | 9 | type PegEat = (self: any, pos: number, str: PegExpr) => [number, string | PegConstant]; 10 | type PegAction = (...terms: any[]) => any; 11 | type PegHole = PegConstant | PegAction; 12 | 13 | interface IFlaggedTag, T = any, U = any> { 14 | (template: TemplateStringsArray, ...args: U[]): T; 15 | (flag: string): Base; 16 | } 17 | 18 | interface IBootPegTag { 19 | (template: TemplateStringsArray, ...args: PegHole[]): T; 20 | ACCEPT: PegPredicate; 21 | HOLE: PegPredicate; 22 | SKIP: PegConstant; 23 | } 24 | 25 | interface IParserTag extends IFlaggedTag, T> { 26 | parserCreator: PegParserCreator; 27 | _asExtending: (baseQuasiParser: IParserTag) => IPegTag; 28 | } 29 | 30 | interface IPegParser { 31 | _memo: Map>; 32 | _debug: boolean; 33 | _hits: (n?: number) => number; 34 | _misses: (n?: number) => number; 35 | template: TemplateStringsArray; 36 | start: (parser: IPegParser) => any; 37 | done: (parser: IPegParser) => void; 38 | } 39 | 40 | type PegParserCreator = (template: TemplateStringsArray, debug: boolean) => IPegParser | undefined; 41 | 42 | interface IPegTag extends IFlaggedTag, T, PegHole> { 43 | ACCEPT: PegPredicate; 44 | FAIL: PegConstant; 45 | HOLE: PegPredicate; 46 | SKIP: PegConstant; 47 | EAT: PegEat; 48 | extends: (peg: IParserTag) => IPegTag; 49 | _asExtending: (baseQuasiParser: IParserTag) => IPegTag; 50 | parserCreator: PegParserCreator; 51 | } 52 | 53 | // TODO: Fill out all the tree from PegDef. 54 | type PegExpr = string | any[]; 55 | type PegRuleOrPatt = (((..._args: any[]) => any) & {name: string}) | PegExpr; 56 | type PegDef = any[]; 57 | 58 | type MakePeg = , U = IPegTag>>( 59 | pegTag: IBootPegTag, 60 | metaCompile: (defs: PegDef[]) => (..._: any[]) => U) => T; 61 | -------------------------------------------------------------------------------- /lib/quasi-chainmail.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; /// 2 | 3 | const transformSingleQuote = insulate(s => { 4 | let i = 0,qs = ''; 5 | while (i < s.length) { 6 | const c = s.slice(i, i + 1); 7 | if (c === '\\') { 8 | // Skip one char. 9 | qs += s.slice(i, i + 2); 10 | i += 2; 11 | } else if (c === '"') { 12 | // Quote it. 13 | qs += '\\"'; 14 | i++; 15 | } else { 16 | // Add it directly. 17 | qs += c; 18 | i++; 19 | } 20 | } 21 | return `"${qs}"`; 22 | }); 23 | 24 | const deopt = insulate(opt => { 25 | return opt.length === 0 ? opt : opt[0]; 26 | }); 27 | 28 | 29 | const makeChainmail = insulate(peg => { 30 | const { FAIL, HOLE, SKIP } = peg; 31 | return peg` 32 | # start production 33 | start <- _WS typeDecl**(_SEMI*) _EOF ${v => (..._a) => v}; 34 | 35 | typeDecl <- enumDecl / structDecl / interfaceDecl / constDecl; 36 | 37 | type <- (primType / "List" _WS / "AnyPointer" _WS / IDENT) typeParameterization? 38 | ${(id, parms) => parms.length === 0 ? id : ['ptype', id, ...parms]}; 39 | 40 | LPAREN <- "(" _WS; 41 | RPAREN <- ")" _WS; 42 | _COMMA <- "," _WS ${_ => SKIP}; 43 | 44 | typeParameterization <- LPAREN type**_COMMA RPAREN ${(_, parms, _2) => parms}; 45 | 46 | primType <- "Void" _WS / "Bool" _WS / intType / floatType / "Text" _WS / "Data" _WS; 47 | 48 | # Only "BigInt" is relevant to CapTP. 49 | # Only "BigInt" is not in Cap'n Proto itself, nor maps to anything 50 | # obvious in WASM. 51 | intType <- ("Int8" / "Int16" / "Int32" / "Int64" 52 | / "UInt8" / "UInt16" / "UInt32" / "UInt64" 53 | / "BigInt") _WS; 54 | 55 | # Only "Float64" is relevant to CapTP 56 | floatType <- ("Float32" / "Float64" / "Float128") _WS; 57 | 58 | ENUM <- "enum" _WS; 59 | LBRACE <- "{" _WS; 60 | RBRACE <- "}" _WS; 61 | _SEMI <- ";" _WS ${_ => SKIP}; 62 | enumDecl <- ENUM type LBRACE (IDENT _SEMI)* RBRACE 63 | ${(_, etype, _2, ids, _3) => ['enum', etype, ids]}; 64 | 65 | STRUCT <- "struct" _WS; 66 | structDecl <- STRUCT type LBRACE memberDecl* RBRACE 67 | ${(_, stype, _2, members, _3) => 68 | ['struct', stype[1], stype[2], members]}; 69 | 70 | memberDecl <- paramDecl _SEMI / typeDecl; 71 | 72 | INTERFACE <- "interface" _WS; 73 | interfaceDecl <- INTERFACE type extends? LBRACE methodDecl* RBRACE 74 | ${(_, itype, ext, _2, methods, _3) => 75 | ['interface', itype, deopt(ext), methods]}; 76 | 77 | EXTENDS <- "extends" _WS; 78 | extends <- EXTENDS LPAREN type**_COMMA RPAREN 79 | ${(_, _2, types, _3) => types}; 80 | 81 | RARROW <- "->" _WS; 82 | paramDecls <- LPAREN paramDecl**_COMMA RPAREN 83 | ${(_, decls, _2) => decls}; 84 | resultDecls <- RARROW LPAREN resultDecl**_COMMA RPAREN 85 | ${(_, _2, decls, _3) => decls}; 86 | methodDecl <- IDENT methodTypeParams? paramDecls resultDecls? _SEMI 87 | ${(id, tparams, pdecls, rdecls, _) => ['method', id, deopt(tparams), pdecls, deopt(rdecls)]}; 88 | 89 | LBRACKET <- "[" _WS; 90 | RBRACKET <- "]" _WS; 91 | methodTypeParams <- LBRACKET type**_COMMA RBRACKET 92 | ${(_, types, _2) => types}; 93 | 94 | COLON <- ":" _WS; 95 | EQUALS <- "=" _WS; 96 | default <- EQUALS expr ${(_, expr) => expr}; 97 | paramDecl <- IDENT COLON type default? 98 | ${(id, _, type, dflt) => ['param', id, type, ...dflt]}; 99 | 100 | resultDecl <- IDENT COLON type ${(id, _, type) => ['named', type, id]} 101 | / type ${type => type}; 102 | 103 | CONST <- "const" _WS; 104 | constDecl <- CONST IDENT COLON type EQUALS expr _SEMI 105 | ${(_, id, _2, type, _3, expr, _4) => ['const', id, type, expr]}; 106 | 107 | string <- STRING ${s => ['data', JSON.parse(s)]}; 108 | number <- NUMBER ${n => ['data', JSON.parse(n)]}; 109 | expr <- string / number; 110 | 111 | _EOF <- ~.; 112 | 113 | STRING <- < '"' (~'"' character)* '"' > _WS 114 | / "'" < (~"'" character)* > "'" _WS ${s => transformSingleQuote(s)}; 115 | 116 | utf8 <- 117 | [\xc2-\xdf] utf8cont 118 | / [\xe0-\xef] utf8cont utf8cont 119 | / [\xf0-\xf4] utf8cont utf8cont utf8cont; 120 | 121 | utf8cont <- [\x80-\xbf]; 122 | 123 | character <- 124 | escape 125 | / '\\u' hex hex hex hex 126 | / ~'\\' ([\x20-\x7f] / utf8); 127 | 128 | escape <- '\\' ['"\\bfnrt]; 129 | hex <- digit / [a-fA-F]; 130 | 131 | NUMBER <- < int frac? exp? > _WSN; 132 | 133 | MINUS <- "-" _WS; 134 | int <- [1-9] digit+ 135 | / digit 136 | / MINUS digit 137 | / MINUS [1-9] digit+; 138 | 139 | digit <- [0-9]; 140 | 141 | frac <- '.' digit+; 142 | exp <- [Ee] [+\-]? digit+; 143 | 144 | IDENT <- 145 | < [$A-Za-z_] [$A-Za-z0-9_]* > _WSN; 146 | 147 | # _WSN is whitespace or a non-ident character. 148 | _WSN <- ~[$A-Za-z_] _WS ${_ => SKIP}; 149 | 150 | # Define Javascript-style comments. 151 | _WS <- [\t\n\r ]* (EOL_COMMENT / MULTILINE_COMMENT)? ${_ => SKIP}; 152 | EOL_COMMENT <- "//" (~[\n\r] .)* _WS; 153 | MULTILINE_COMMENT <- "/*" (~"*/" .)* "*/" _WS; 154 | 155 | `; 156 | 157 | }); 158 | 159 | export default makeChainmail; -------------------------------------------------------------------------------- /lib/quasi-chainmail.js.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const transformSingleQuote = (s: string) => { 4 | let i = 0, qs = ''; 5 | while (i < s.length) { 6 | const c = s.slice(i, i + 1); 7 | if (c === '\\') { 8 | // Skip one char. 9 | qs += s.slice(i, i + 2); 10 | i += 2; 11 | } else if (c === '"') { 12 | // Quote it. 13 | qs += '\\"'; 14 | i ++; 15 | } else { 16 | // Add it directly. 17 | qs += c; 18 | i ++; 19 | } 20 | } 21 | return `"${qs}"`; 22 | }; 23 | 24 | const deopt = (opt: [] | [any[]]) => { 25 | return (opt.length === 0 ? opt : opt[0]) 26 | }; 27 | 28 | 29 | const makeChainmail = (peg: IPegTag>) => { 30 | const {FAIL, HOLE, SKIP} = peg; 31 | return peg` 32 | # start production 33 | start <- _WS typeDecl**(_SEMI*) _EOF ${v => (..._a: any[]) => v}; 34 | 35 | typeDecl <- enumDecl / structDecl / interfaceDecl / constDecl; 36 | 37 | type <- (primType / "List" _WS / "AnyPointer" _WS / IDENT) typeParameterization? 38 | ${(id, parms) => (parms.length === 0 ? id : ['ptype', id, ...parms])}; 39 | 40 | LPAREN <- "(" _WS; 41 | RPAREN <- ")" _WS; 42 | _COMMA <- "," _WS ${_ => SKIP}; 43 | 44 | typeParameterization <- LPAREN type**_COMMA RPAREN ${(_, parms, _2) => parms}; 45 | 46 | primType <- "Void" _WS / "Bool" _WS / intType / floatType / "Text" _WS / "Data" _WS; 47 | 48 | # Only "BigInt" is relevant to CapTP. 49 | # Only "BigInt" is not in Cap'n Proto itself, nor maps to anything 50 | # obvious in WASM. 51 | intType <- ("Int8" / "Int16" / "Int32" / "Int64" 52 | / "UInt8" / "UInt16" / "UInt32" / "UInt64" 53 | / "BigInt") _WS; 54 | 55 | # Only "Float64" is relevant to CapTP 56 | floatType <- ("Float32" / "Float64" / "Float128") _WS; 57 | 58 | ENUM <- "enum" _WS; 59 | LBRACE <- "{" _WS; 60 | RBRACE <- "}" _WS; 61 | _SEMI <- ";" _WS ${_ => SKIP}; 62 | enumDecl <- ENUM type LBRACE (IDENT _SEMI)* RBRACE 63 | ${(_, etype, _2, ids, _3) => ['enum', etype, ids]}; 64 | 65 | STRUCT <- "struct" _WS; 66 | structDecl <- STRUCT type LBRACE memberDecl* RBRACE 67 | ${(_, stype, _2, members, _3) => 68 | ['struct', stype[1], stype[2], members]}; 69 | 70 | memberDecl <- paramDecl _SEMI / typeDecl; 71 | 72 | INTERFACE <- "interface" _WS; 73 | interfaceDecl <- INTERFACE type extends? LBRACE methodDecl* RBRACE 74 | ${(_, itype, ext, _2, methods, _3) => 75 | ['interface', itype, deopt(ext), methods]}; 76 | 77 | EXTENDS <- "extends" _WS; 78 | extends <- EXTENDS LPAREN type**_COMMA RPAREN 79 | ${(_, _2, types, _3) => types}; 80 | 81 | RARROW <- "->" _WS; 82 | paramDecls <- LPAREN paramDecl**_COMMA RPAREN 83 | ${(_, decls, _2) => decls}; 84 | resultDecls <- RARROW LPAREN resultDecl**_COMMA RPAREN 85 | ${(_, _2, decls, _3) => decls}; 86 | methodDecl <- IDENT methodTypeParams? paramDecls resultDecls? _SEMI 87 | ${(id, tparams, pdecls, rdecls, _) => ['method', id, deopt(tparams), pdecls, deopt(rdecls)]}; 88 | 89 | LBRACKET <- "[" _WS; 90 | RBRACKET <- "]" _WS; 91 | methodTypeParams <- LBRACKET type**_COMMA RBRACKET 92 | ${(_, types, _2) => types}; 93 | 94 | COLON <- ":" _WS; 95 | EQUALS <- "=" _WS; 96 | default <- EQUALS expr ${(_, expr) => expr}; 97 | paramDecl <- IDENT COLON type default? 98 | ${(id, _, type, dflt) => ['param', id, type, ...dflt]}; 99 | 100 | resultDecl <- IDENT COLON type ${(id, _, type) => ['named', type, id]} 101 | / type ${(type) => type}; 102 | 103 | CONST <- "const" _WS; 104 | constDecl <- CONST IDENT COLON type EQUALS expr _SEMI 105 | ${(_, id, _2, type, _3, expr, _4) => ['const', id, type, expr]}; 106 | 107 | string <- STRING ${(s) => ['data', JSON.parse(s)]}; 108 | number <- NUMBER ${(n) => ['data', JSON.parse(n)]}; 109 | expr <- string / number; 110 | 111 | _EOF <- ~.; 112 | 113 | STRING <- < '"' (~'"' character)* '"' > _WS 114 | / "'" < (~"'" character)* > "'" _WS ${s => transformSingleQuote(s)}; 115 | 116 | utf8 <- 117 | [\xc2-\xdf] utf8cont 118 | / [\xe0-\xef] utf8cont utf8cont 119 | / [\xf0-\xf4] utf8cont utf8cont utf8cont; 120 | 121 | utf8cont <- [\x80-\xbf]; 122 | 123 | character <- 124 | escape 125 | / '\\u' hex hex hex hex 126 | / ~'\\' ([\x20-\x7f] / utf8); 127 | 128 | escape <- '\\' ['"\\bfnrt]; 129 | hex <- digit / [a-fA-F]; 130 | 131 | NUMBER <- < int frac? exp? > _WSN; 132 | 133 | MINUS <- "-" _WS; 134 | int <- [1-9] digit+ 135 | / digit 136 | / MINUS digit 137 | / MINUS [1-9] digit+; 138 | 139 | digit <- [0-9]; 140 | 141 | frac <- '.' digit+; 142 | exp <- [Ee] [+\-]? digit+; 143 | 144 | IDENT <- 145 | < [$A-Za-z_] [$A-Za-z0-9_]* > _WSN; 146 | 147 | # _WSN is whitespace or a non-ident character. 148 | _WSN <- ~[$A-Za-z_] _WS ${_ => SKIP}; 149 | 150 | # Define Javascript-style comments. 151 | _WS <- [\t\n\r ]* (EOL_COMMENT / MULTILINE_COMMENT)? ${_ => SKIP}; 152 | EOL_COMMENT <- "//" (~[\n\r] .)* _WS; 153 | MULTILINE_COMMENT <- "/*" (~"*/" .)* "*/" _WS; 154 | 155 | `; 156 | 157 | }; 158 | 159 | export default makeChainmail; 160 | -------------------------------------------------------------------------------- /lib/quasi-insulate.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; // Subsets of JavaScript, starting from the grammar as defined at 2 | // http://www.ecma-international.org/ecma-262/9.0/#sec-grammar-summary 3 | 4 | // See https://github.com/Agoric/Jessie/blob/master/README.md 5 | // for documentation of the Jessie grammar defined here. 6 | 7 | /// 8 | 9 | // Safe Modules are ones that can be imported without 10 | // insulating their symbols. 11 | const isSafeModule = insulate(moduleName => { 12 | switch (moduleName) { 13 | case '@agoric/jessie':{ 14 | return true; 15 | } 16 | default:{ 17 | return false; 18 | }} 19 | 20 | }); 21 | 22 | 23 | const terminatedBlock = insulate(manyBodies => { 24 | const stmts = manyBodies.reduce((prior, body) => { 25 | const [bs, t] = body; 26 | bs.forEach(b => prior.push(b)); 27 | prior.push(t); 28 | return prior; 29 | }, []); 30 | return ['block', stmts]; 31 | }); 32 | 33 | const makeInsulatedJessie = insulate((peg, jessiePeg) => { 34 | const { FAIL, SKIP } = jessiePeg; 35 | const insulatedTag = jessiePeg` 36 | # Inherit start production. 37 | start <- super.start; 38 | 39 | # A.1 Lexical Grammar 40 | 41 | # insulate is reserved by Jessica. 42 | RESERVED_WORD <- super.RESERVED_WORD / ( "insulate" _WSN ); 43 | 44 | # A.5 Scripts and Modules 45 | 46 | useImport <- IMPORT_PFX IDENT ${(pfx, id) => ['use', pfx + id]}; 47 | defImport <- IMPORT_PFX IDENT ${(pfx, id) => ['def', pfx + id]}; 48 | 49 | # A properly hardened expression without side-effects. 50 | hardenedExpr <- 51 | "insulate" _WS LEFT_PAREN (pureExpr / useImport) RIGHT_PAREN ${(fname, _2, expr, _3) => 52 | ['call', ['use', fname], [expr]]} 53 | / super.hardenedExpr; 54 | 55 | # Safe imports don't need to be prefixed. 56 | safeImportSpecifier <- 57 | IDENT_NAME AS defVar ${(i, _, d) => ['as', i, d[1]]} 58 | / defVar ${d => ['as', d[1], d[1]]} 59 | / "insulate" _WSN ${w => ['as', w, w]}; 60 | 61 | safeModule <- 62 | STRING ${s => {const mod = JSON.parse(s);return isSafeModule(mod) ? mod : FAIL;}}; 63 | `; 64 | 65 | const insulatedExprTag = peg.extends(insulatedTag)` 66 | # Jump to the expr production. 67 | start <- _WS expr _EOF ${e => (..._a) => e}; 68 | `; 69 | 70 | return [insulatedTag, insulatedExprTag]; 71 | }); 72 | 73 | export default makeInsulatedJessie; -------------------------------------------------------------------------------- /lib/quasi-insulate.js.ts: -------------------------------------------------------------------------------- 1 | // Subsets of JavaScript, starting from the grammar as defined at 2 | // http://www.ecma-international.org/ecma-262/9.0/#sec-grammar-summary 3 | 4 | // See https://github.com/Agoric/Jessie/blob/master/README.md 5 | // for documentation of the Jessie grammar defined here. 6 | 7 | /// 8 | 9 | // Safe Modules are ones that can be imported without 10 | // insulating their symbols. 11 | const isSafeModule = (moduleName: string) => { 12 | switch (moduleName) { 13 | case '@agoric/jessie': { 14 | return true; 15 | } 16 | default: { 17 | return false; 18 | } 19 | } 20 | }; 21 | 22 | type TerminatedBody = [any[][], any[]]; 23 | const terminatedBlock = (manyBodies: TerminatedBody[]) => { 24 | const stmts = manyBodies.reduce((prior, body) => { 25 | const [bs, t] = body; 26 | bs.forEach(b => prior.push(b)); 27 | prior.push(t); 28 | return prior; 29 | }, []); 30 | return ['block', stmts]; 31 | }; 32 | 33 | const makeInsulatedJessie = (peg: IPegTag>, jessiePeg: IPegTag>) => { 34 | const {FAIL, SKIP} = jessiePeg; 35 | const insulatedTag = jessiePeg` 36 | # Inherit start production. 37 | start <- super.start; 38 | 39 | # A.1 Lexical Grammar 40 | 41 | # insulate is reserved by Jessica. 42 | RESERVED_WORD <- super.RESERVED_WORD / ( "insulate" _WSN ); 43 | 44 | # A.5 Scripts and Modules 45 | 46 | useImport <- IMPORT_PFX IDENT ${(pfx, id) => ['use', pfx + id]}; 47 | defImport <- IMPORT_PFX IDENT ${(pfx, id) => ['def', pfx + id]}; 48 | 49 | # A properly hardened expression without side-effects. 50 | hardenedExpr <- 51 | "insulate" _WS LEFT_PAREN (pureExpr / useImport) RIGHT_PAREN ${(fname, _2, expr, _3) => 52 | ['call', ['use', fname], [expr]]} 53 | / super.hardenedExpr; 54 | 55 | # Safe imports don't need to be prefixed. 56 | safeImportSpecifier <- 57 | IDENT_NAME AS defVar ${(i, _, d) => ['as', i, d[1]]} 58 | / defVar ${(d) => ['as', d[1], d[1]]} 59 | / "insulate" _WSN ${(w) => ['as', w, w]}; 60 | 61 | safeModule <- 62 | STRING ${(s) => { const mod = JSON.parse(s); return isSafeModule(mod) ? mod : FAIL; }}; 63 | `; 64 | 65 | const insulatedExprTag = peg.extends(insulatedTag)` 66 | # Jump to the expr production. 67 | start <- _WS expr _EOF ${e => (..._a: any[]) => e}; 68 | `; 69 | 70 | return [insulatedTag, insulatedExprTag]; 71 | }; 72 | 73 | export default makeInsulatedJessie; 74 | -------------------------------------------------------------------------------- /lib/quasi-jessie-module.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; // An extension of the Jessie grammar to facilitate rewriting 2 | // imports/exports as AMD. 3 | 4 | /// 5 | 6 | const makeJessieModule = insulate(jessiePeg => { 7 | return jessiePeg` 8 | # Override rather than inherit start production. 9 | # Only module syntax is permitted. 10 | start <- _WS moduleBody _EOF ${b => (..._a) => ['moduleX', b]}; 11 | 12 | # A.5 Scripts and Modules 13 | 14 | insulatedExpr <- < super.insulatedExpr >; 15 | 16 | moduleBody <- moduleItem*; 17 | moduleItem <- 18 | < SEMI > 19 | / importDecl # Same AST as in Jessie. 20 | / exportDecl # Similar AST, but insulatedExpr is source string. 21 | / < moduleDeclaration >; # Source string. 22 | 23 | exportDecl <- 24 | EXPORT DEFAULT < exportableExpr > SEMI ${e => ['exportDefaultX', e]} 25 | / EXPORT moduleDeclaration ${(_, d) => ['exportX', ...d]}; 26 | `; 27 | }); 28 | 29 | export default makeJessieModule; -------------------------------------------------------------------------------- /lib/quasi-jessie-module.js.ts: -------------------------------------------------------------------------------- 1 | // An extension of the Jessie grammar to facilitate rewriting 2 | // imports/exports as AMD. 3 | 4 | /// 5 | 6 | const makeJessieModule = (jessiePeg: IPegTag>) => { 7 | return jessiePeg` 8 | # Override rather than inherit start production. 9 | # Only module syntax is permitted. 10 | start <- _WS moduleBody _EOF ${b => (..._a: any[]) => ['moduleX', b]}; 11 | 12 | # A.5 Scripts and Modules 13 | 14 | insulatedExpr <- < super.insulatedExpr >; 15 | 16 | moduleBody <- moduleItem*; 17 | moduleItem <- 18 | < SEMI > 19 | / importDecl # Same AST as in Jessie. 20 | / exportDecl # Similar AST, but insulatedExpr is source string. 21 | / < moduleDeclaration >; # Source string. 22 | 23 | exportDecl <- 24 | EXPORT DEFAULT < exportableExpr > SEMI ${e => ['exportDefaultX', e]} 25 | / EXPORT moduleDeclaration ${(_, d) => ['exportX', ...d]}; 26 | `; 27 | }; 28 | 29 | export default makeJessieModule; 30 | -------------------------------------------------------------------------------- /lib/quasi-json.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; // Subsets of JavaScript, starting from the grammar as defined at 2 | // http://www.ecma-international.org/ecma-262/9.0/#sec-grammar-summary 3 | 4 | // Defined to be extended into the Jessie grammar. 5 | // See https://github.com/Agoric/Jessie/blob/master/README.md 6 | // for documentation of the Jessie grammar. 7 | 8 | // See also json.org 9 | 10 | /// 11 | 12 | const makeJSON = insulate(peg => { 13 | const { FAIL, HOLE, SKIP } = peg; 14 | return peg` 15 | # to be overridden or inherited 16 | start <- _WS assignExpr _EOF ${v => (..._a) => v}; 17 | 18 | # to be extended 19 | primaryExpr <- dataStructure; 20 | 21 | dataStructure <- 22 | dataLiteral ${n => ['data', JSON.parse(n)]} 23 | / array 24 | / record 25 | / HOLE ${h => ['exprHole', h]}; 26 | 27 | # An expression without side-effects. 28 | # to be extended 29 | pureExpr <- 30 | dataLiteral ${n => ['data', JSON.parse(n)]} 31 | / pureArray 32 | / pureRecord 33 | / HOLE ${h => ['exprHole', h]}; 34 | 35 | dataLiteral <- (("null" / "false" / "true") _WSN / NUMBER / STRING) _WS; 36 | 37 | pureArray <- 38 | LEFT_BRACKET pureExpr ** _COMMA _COMMA? RIGHT_BRACKET ${(_, es, _2) => ['array', es]}; 39 | 40 | array <- 41 | LEFT_BRACKET element ** _COMMA _COMMA? RIGHT_BRACKET ${(_, es, _2) => ['array', es]}; 42 | 43 | # to be extended 44 | element <- assignExpr; 45 | 46 | # The JavaScript and JSON grammars calls records "objects" 47 | 48 | pureRecord <- 49 | LEFT_BRACE purePropDef ** _COMMA _COMMA? RIGHT_BRACE ${(_, ps, _2) => ['record', ps]}; 50 | 51 | record <- 52 | LEFT_BRACE propDef ** _COMMA _COMMA? RIGHT_BRACE ${(_, ps, _2) => ['record', ps]}; 53 | 54 | # to be extended 55 | purePropDef <- propName COLON pureExpr ${(k, _, e) => ['prop', k, e]}; 56 | 57 | # to be extended 58 | propDef <- propName COLON assignExpr ${(k, _, e) => ['prop', k, e]}; 59 | 60 | # to be extended 61 | propName <- STRING ${str => { 62 | const js = JSON.parse(str); 63 | if (js === '__proto__') { 64 | // Don't allow __proto__ behaviour attacks. 65 | return FAIL; 66 | } 67 | return ['data', js]; 68 | }}; 69 | 70 | # to be overridden 71 | assignExpr <- primaryExpr; 72 | 73 | # Lexical syntax 74 | 75 | _EOF <- ~.; 76 | LEFT_BRACKET <- "[" _WS; 77 | RIGHT_BRACKET <- "]" _WS; 78 | LEFT_BRACE <- "{" _WS; 79 | RIGHT_BRACE <- "}" _WS; 80 | _COMMA <- "," _WS ${_ => SKIP}; 81 | COLON <- ":" _WS; 82 | MINUS <- "-" _WS; 83 | HOLE <- &${HOLE} _WS; 84 | 85 | STRING <- < '"' (~'"' character)* '"' > _WS; 86 | 87 | utf8 <- 88 | [\xc2-\xdf] utf8cont 89 | / [\xe0-\xef] utf8cont utf8cont 90 | / [\xf0-\xf4] utf8cont utf8cont utf8cont; 91 | 92 | utf8cont <- [\x80-\xbf]; 93 | 94 | character <- 95 | escape 96 | / '\\u' hex hex hex hex 97 | / ~'\\' ([\x20-\x7f] / utf8); 98 | 99 | escape <- '\\' ['"\\bfnrt]; 100 | hex <- digit / [a-fA-F]; 101 | 102 | NUMBER <- < int frac? exp? > _WSN; 103 | 104 | int <- [1-9] digit+ 105 | / digit 106 | / MINUS [1-9] digit+ 107 | / MINUS digit; 108 | 109 | digit <- [0-9]; 110 | 111 | frac <- '.' digit+; 112 | exp <- [Ee] [+\-]? digit+; 113 | 114 | # _WSN is whitespace or a non-ident character. 115 | _WSN <- ~[$A-Za-z_] _WS ${_ => SKIP}; 116 | _WS <- [\t\n\r ]* ${_ => SKIP}; 117 | `; 118 | 119 | }); 120 | 121 | export default makeJSON; 122 | -------------------------------------------------------------------------------- /lib/quasi-json.js.ts: -------------------------------------------------------------------------------- 1 | // Subsets of JavaScript, starting from the grammar as defined at 2 | // http://www.ecma-international.org/ecma-262/9.0/#sec-grammar-summary 3 | 4 | // Defined to be extended into the Jessie grammar. 5 | // See https://github.com/Agoric/Jessie/blob/master/README.md 6 | // for documentation of the Jessie grammar. 7 | 8 | // See also json.org 9 | 10 | /// 11 | 12 | const makeJSON = (peg: IPegTag>) => { 13 | const {FAIL, HOLE, SKIP} = peg; 14 | return peg` 15 | # to be overridden or inherited 16 | start <- _WS assignExpr _EOF ${v => (..._a: any[]) => v}; 17 | 18 | # to be extended 19 | primaryExpr <- dataStructure; 20 | 21 | dataStructure <- 22 | dataLiteral ${n => ['data', JSON.parse(n)]} 23 | / array 24 | / record 25 | / HOLE ${h => ['exprHole', h]}; 26 | 27 | # An expression without side-effects. 28 | # to be extended 29 | pureExpr <- 30 | dataLiteral ${n => ['data', JSON.parse(n)]} 31 | / pureArray 32 | / pureRecord 33 | / HOLE ${h => ['exprHole', h]}; 34 | 35 | dataLiteral <- (("null" / "false" / "true") _WSN / NUMBER / STRING) _WS; 36 | 37 | pureArray <- 38 | LEFT_BRACKET pureExpr ** _COMMA _COMMA? RIGHT_BRACKET ${(_, es, _2) => ['array', es]}; 39 | 40 | array <- 41 | LEFT_BRACKET element ** _COMMA _COMMA? RIGHT_BRACKET ${(_, es, _2) => ['array', es]}; 42 | 43 | # to be extended 44 | element <- assignExpr; 45 | 46 | # The JavaScript and JSON grammars calls records "objects" 47 | 48 | pureRecord <- 49 | LEFT_BRACE purePropDef ** _COMMA _COMMA? RIGHT_BRACE ${(_, ps, _2) => ['record', ps]}; 50 | 51 | record <- 52 | LEFT_BRACE propDef ** _COMMA _COMMA? RIGHT_BRACE ${(_, ps, _2) => ['record', ps]}; 53 | 54 | # to be extended 55 | purePropDef <- propName COLON pureExpr ${(k, _, e) => ['prop', k, e]}; 56 | 57 | # to be extended 58 | propDef <- propName COLON assignExpr ${(k, _, e) => ['prop', k, e]}; 59 | 60 | # to be extended 61 | propName <- STRING ${(str) => { 62 | const js = JSON.parse(str); 63 | if (js === '__proto__') { 64 | // Don't allow __proto__ behaviour attacks. 65 | return FAIL; 66 | } 67 | return ['data', js]; 68 | }}; 69 | 70 | # to be overridden 71 | assignExpr <- primaryExpr; 72 | 73 | # Lexical syntax 74 | 75 | _EOF <- ~.; 76 | LEFT_BRACKET <- "[" _WS; 77 | RIGHT_BRACKET <- "]" _WS; 78 | LEFT_BRACE <- "{" _WS; 79 | RIGHT_BRACE <- "}" _WS; 80 | _COMMA <- "," _WS ${_ => SKIP}; 81 | COLON <- ":" _WS; 82 | MINUS <- "-" _WS; 83 | HOLE <- &${HOLE} _WS; 84 | 85 | STRING <- < '"' (~'"' character)* '"' > _WS; 86 | 87 | utf8 <- 88 | [\xc2-\xdf] utf8cont 89 | / [\xe0-\xef] utf8cont utf8cont 90 | / [\xf0-\xf4] utf8cont utf8cont utf8cont; 91 | 92 | utf8cont <- [\x80-\xbf]; 93 | 94 | character <- 95 | escape 96 | / '\\u' hex hex hex hex 97 | / ~'\\' ([\x20-\x7f] / utf8); 98 | 99 | escape <- '\\' ['"\\bfnrt]; 100 | hex <- digit / [a-fA-F]; 101 | 102 | NUMBER <- < int frac? exp? > _WSN; 103 | 104 | int <- [1-9] digit+ 105 | / digit 106 | / MINUS [1-9] digit+ 107 | / MINUS digit; 108 | 109 | digit <- [0-9]; 110 | 111 | frac <- '.' digit+; 112 | exp <- [Ee] [+\-]? digit+; 113 | 114 | # _WSN is whitespace or a non-ident character. 115 | _WSN <- ~[$A-Za-z_] _WS ${_ => SKIP}; 116 | _WS <- [\t\n\r ]* ${_ => SKIP}; 117 | `; 118 | 119 | }; 120 | 121 | export default makeJSON; 122 | -------------------------------------------------------------------------------- /lib/quasi-justin.js.ts: -------------------------------------------------------------------------------- 1 | // Subsets of JavaScript, starting from the grammar as defined at 2 | // http://www.ecma-international.org/ecma-262/9.0/#sec-grammar-summary 3 | 4 | // Justin is the safe JavaScript expression language, a potentially 5 | // pure terminating superset of JSON and subset of Jessie, that relieves 6 | // many of the pain points of using JSON as a data format: 7 | // * unquoted indentifier property names. 8 | // * comments. 9 | // * multi-line strings (via template literals). 10 | // * undefined. 11 | // * includes all floating point values: NaN, Infinity, -Infinity 12 | // * will include BigInt once available. 13 | 14 | // Justin also includes most pure JavaScript expressions. Justin does not 15 | // include function expressions or variable or function 16 | // definitions. However, it does include free variable uses and 17 | // function calls; so the purity and termination of Justin depends on 18 | // the endowments provided for these free variable bindings. 19 | 20 | // Justin is defined to be extended into the Jessie grammar, which is 21 | // defined to be extended into the JavaScript grammar. 22 | // See https://github.com/Agoric/Jessie/blob/master/README.md 23 | // for documentation of the Jessie grammar. 24 | 25 | // Justin is defined to be extended into the Chainmail grammar, to 26 | // provide its expression language in a JS-like style. Chainmail 27 | // expressions need to be pure and should be terminating. 28 | 29 | /// 30 | 31 | import {qunpack} from './quasi-utils.js'; 32 | 33 | const binary = (left: PegExpr, rights: any[]) => { 34 | return rights.reduce((prev, [op, right]) => [op, prev, right], left); 35 | }; 36 | 37 | const transformSingleQuote = (s: string) => { 38 | let i = 0, qs = ''; 39 | while (i < s.length) { 40 | const c = s.slice(i, i + 1); 41 | if (c === '\\') { 42 | // Skip one char. 43 | qs += s.slice(i, i + 2); 44 | i += 2; 45 | } else if (c === '"') { 46 | // Quote it. 47 | qs += '\\"'; 48 | i ++; 49 | } else { 50 | // Add it directly. 51 | qs += c; 52 | i ++; 53 | } 54 | } 55 | return `"${qs}"`; 56 | }; 57 | 58 | const makeJustin = (peg: IPegTag>) => { 59 | const {SKIP} = peg; 60 | return peg` 61 | # to be overridden or inherited 62 | start <- _WS assignExpr _EOF ${v => (..._a: any[]) => v}; 63 | 64 | # A.1 Lexical Grammar 65 | 66 | DOT <- "." _WS; 67 | ELLIPSIS <- "..." _WS; 68 | LEFT_PAREN <- "(" _WS; 69 | PLUS <- "+" _WS; 70 | QUESTION <- "?" _WS; 71 | RIGHT_PAREN <- ")" _WS; 72 | STARSTAR <- "**" _WS; 73 | 74 | # Define Javascript-style comments. 75 | _WS <- super._WS (EOL_COMMENT / MULTILINE_COMMENT)? ${_ => SKIP}; 76 | EOL_COMMENT <- "//" (~[\n\r] .)* _WS; 77 | MULTILINE_COMMENT <- "/*" (~"*/" .)* "*/" _WS; 78 | 79 | # Add single-quoted strings. 80 | STRING <- 81 | super.STRING 82 | / "'" < (~"'" character)* > "'" _WS ${s => transformSingleQuote(s)}; 83 | 84 | # Only match if whitespace doesn't contain newline 85 | _NO_NEWLINE <- ~IDENT [ \t]* ${_ => SKIP}; 86 | 87 | IDENT_NAME <- ~(HIDDEN_PFX / "__proto__" _WSN) (IDENT / RESERVED_WORD); 88 | 89 | IDENT <- 90 | ~(HIDDEN_PFX / IMPORT_PFX / RESERVED_WORD) 91 | < [$A-Za-z_] [$A-Za-z0-9_]* > _WSN; 92 | HIDDEN_PFX <- "$h_"; 93 | IMPORT_PFX <- "$i_"; 94 | 95 | # Omit "async", "arguments", "eval", "get", and "set" from IDENT 96 | # in Justin even though ES2017 considers them in IDENT. 97 | RESERVED_WORD <- 98 | (KEYWORD / RESERVED_KEYWORD / FUTURE_RESERVED_WORD 99 | / "null" / "false" / "true" 100 | / "async" / "arguments" / "eval" / "get" / "set") _WSN; 101 | 102 | KEYWORD <- 103 | ("break" 104 | / "case" / "catch" / "const" / "continue" 105 | / "debugger" / "default" 106 | / "else" / "export" 107 | / "finally" / "for" / "function" 108 | / "if" / "import" 109 | / "return" 110 | / "switch" 111 | / "throw" / "try" / "typeof" 112 | / "void" 113 | / "while") _WSN; 114 | 115 | # Unused by Justin but enumerated here, in order to omit them 116 | # from the IDENT token. 117 | RESERVED_KEYWORD <- 118 | ("class" 119 | / "delete" / "do" 120 | / "extends" 121 | / "instanceof" 122 | / "in" 123 | / "new" 124 | / "super" 125 | / "this" 126 | / "var" 127 | / "with" 128 | / "yield") _WSN; 129 | 130 | FUTURE_RESERVED_WORD <- 131 | ("await" / "enum" 132 | / "implements" / "package" / "protected" 133 | / "interface" / "private" / "public") _WSN; 134 | 135 | # Quasiliterals aka template literals 136 | QUASI_CHAR <- "\\" . / ~"\`" .; 137 | QUASI_ALL <- "\`" < (~"\${" QUASI_CHAR)* > "\`" _WS; 138 | QUASI_HEAD <- "\`" < (~"\${" QUASI_CHAR)* > "\${" _WS; 139 | QUASI_MID <- "}" < (~"\${" QUASI_CHAR)* > "\${" _WS; 140 | QUASI_TAIL <- "}" < (~"\${" QUASI_CHAR)* > "\`" _WS; 141 | 142 | 143 | # A.2 Expressions 144 | 145 | undefined <- 146 | "undefined" _WSN ${_ => ['undefined']}; 147 | 148 | dataStructure <- 149 | undefined 150 | / super.dataStructure; 151 | 152 | # Optional trailing commas. 153 | record <- 154 | super.record 155 | / LEFT_BRACE propDef ** _COMMA _COMMA? RIGHT_BRACE ${(_, ps, _2) => ['record', ps]}; 156 | 157 | array <- 158 | super.array 159 | / LEFT_BRACKET element ** _COMMA _COMMA? RIGHT_BRACKET ${(_, es, _2) => ['array', es]}; 160 | 161 | useVar <- IDENT ${id => ['use', id]}; 162 | 163 | # Justin does not contain variable definitions, only uses. However, 164 | # multiple languages that extend Justin will contain defining 165 | # occurrences of variable names, so we put the defVar production 166 | # here. 167 | defVar <- IDENT ${id => ['def', id]}; 168 | 169 | 170 | primaryExpr <- 171 | super.primaryExpr 172 | / quasiExpr 173 | / LEFT_PAREN expr RIGHT_PAREN ${(_, e, _2) => e} 174 | / useVar; 175 | 176 | pureExpr <- 177 | super.pureExpr 178 | / LEFT_PAREN pureExpr RIGHT_PAREN ${(_, e, _2) => e} 179 | / useVar; 180 | 181 | element <- 182 | super.element 183 | / ELLIPSIS assignExpr ${(_, e) => ['spread', e]}; 184 | 185 | propDef <- 186 | super.propDef 187 | / useVar ${u => ['prop', u[1], u]} 188 | / ELLIPSIS assignExpr ${(_, e) => ['spreadObj', e]}; 189 | 190 | purePropDef <- 191 | super.purePropDef 192 | / useVar ${u => ['prop', u[1], u]} 193 | / ELLIPSIS assignExpr ${(_, e) => ['spreadObj', e]}; 194 | 195 | # No computed property name 196 | propName <- 197 | super.propName 198 | / IDENT_NAME 199 | / NUMBER; 200 | 201 | quasiExpr <- 202 | QUASI_ALL ${q => ['quasi', [q]]} 203 | / QUASI_HEAD expr ** QUASI_MID QUASI_TAIL ${(h, ms, t) => ['quasi', qunpack(h, ms, t)]}; 204 | 205 | # to be extended 206 | memberPostOp <- 207 | LEFT_BRACKET indexExpr RIGHT_BRACKET ${(_, e, _3) => ['index', e]} 208 | / DOT IDENT_NAME ${(_, id) => ['get', id]} 209 | / quasiExpr ${q => ['tag', q]}; 210 | 211 | # to be extended 212 | callPostOp <- 213 | memberPostOp 214 | / args ${args => ['call', args]}; 215 | 216 | # Because Justin and Jessie have no "new" or "super", they don't need 217 | # to distinguish callExpr from memberExpr. So justin omits memberExpr 218 | # and newExpr. Instead, in Justin, callExpr jumps directly to 219 | # primaryExpr and updateExpr jumps directly to callExpr. 220 | 221 | # to be overridden. 222 | callExpr <- primaryExpr callPostOp* ${binary}; 223 | 224 | # To be overridden rather than inherited. 225 | # Introduced to impose a non-JS restriction 226 | # Restrict index access to number-names, including 227 | # floating point, NaN, Infinity, and -Infinity. 228 | indexExpr <- 229 | NUMBER ${n => ['data', JSON.parse(n)]} 230 | / PLUS unaryExpr ${(_, e) => [`pre:+`, e]}; 231 | 232 | args <- LEFT_PAREN arg ** _COMMA RIGHT_PAREN ${(_, args, _2) => args}; 233 | 234 | arg <- 235 | assignExpr 236 | / ELLIPSIS assignExpr ${(_, e) => ['spread', e]}; 237 | 238 | # to be overridden 239 | updateExpr <- callExpr; 240 | 241 | unaryExpr <- 242 | preOp unaryExpr ${(op, e) => [op, e]} 243 | / updateExpr; 244 | 245 | # to be extended 246 | # No prefix or postfix "++" or "--". 247 | # No "delete". 248 | preOp <- (("void" / "typeof") _WSN / prePre); 249 | prePre <- ("+" / "-" / "~" / "!") _WS ${op => `pre:${op}`}; 250 | 251 | # Different communities will think -x**y parses in different ways, 252 | # so the EcmaScript grammar forces parens to disambiguate. 253 | powExpr <- 254 | updateExpr STARSTAR powExpr ${(x, op, y) => [op, x, y]} 255 | / unaryExpr; 256 | 257 | multExpr <- powExpr (multOp powExpr)* ${binary}; 258 | addExpr <- multExpr (addOp multExpr)* ${binary}; 259 | shiftExpr <- addExpr (shiftOp addExpr)* ${binary}; 260 | 261 | # Non-standard, to be overridden 262 | # In C-like languages, the precedence and associativity of the 263 | # relational, equality, and bitwise operators is surprising, and 264 | # therefore hazardous. Here, none of these associate with the 265 | # others, forcing parens to disambiguate. 266 | eagerExpr <- shiftExpr (eagerOp shiftExpr)? ${binary}; 267 | 268 | andThenExpr <- eagerExpr (andThenOp eagerExpr)* ${binary}; 269 | orElseExpr <- andThenExpr (orElseOp andThenExpr)* ${binary}; 270 | 271 | multOp <- ("*" / "/" / "%") _WS; 272 | addOp <- ("+" / "-") _WS; 273 | shiftOp <- ("<<" / ">>>" / ">>") _WS; 274 | relOp <- ("<=" / "<" / ">=" / ">") _WS; 275 | eqOp <- ("===" / "!==") _WS; 276 | bitOp <- ("&" / "^" / "|") _WS; 277 | 278 | eagerOp <- relOp / eqOp / bitOp; 279 | 280 | andThenOp <- "&&" _WS; 281 | orElseOp <- "||" _WS; 282 | 283 | condExpr <- 284 | orElseExpr QUESTION assignExpr COLON assignExpr ${(c, _, t, _2, e) => ['cond', c, t, e]} 285 | / orElseExpr; 286 | 287 | # override, to be extended 288 | assignExpr <- condExpr; 289 | 290 | # The comma expression is not in Jessie because we 291 | # opt to pass only insulated expressions as the this-binding. 292 | expr <- assignExpr; 293 | `; 294 | }; 295 | 296 | export default makeJustin; 297 | -------------------------------------------------------------------------------- /lib/quasi-peg.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; // PEG quasi Grammar for PEG quasi Grammars 2 | // Michael FIG , 2019-01-05 3 | // 4 | // This grammar is adapted from: 5 | // http://piumarta.com/software/peg/peg-0.1.18/src/peg.peg 6 | // 7 | // Modified for Jessica to support: 8 | // Semantic actions provided in tagged template HOLEs 9 | // '~' for negative lookahead (instead of '!') 10 | // ';' terminator for definitions 11 | // '**' and '++' for separators 12 | // 'super.RULE' syntax for extended grammars 13 | // '\xFF' instead of octal character literals 14 | // which are adapted from: 15 | // https://github.com/erights/quasiParserGenerator 16 | 17 | /// 18 | 19 | const makePeg = insulate(( 20 | pegTag, 21 | metaCompile) => { 22 | const { ACCEPT, HOLE, SKIP } = pegTag; 23 | 24 | function simple(prefix, list) { 25 | if (list.length === 0) {return ['empty'];} 26 | if (list.length === 1) {return list[0];} 27 | return [prefix, ...list]; 28 | } 29 | 30 | function flatArgs(args) { 31 | return args.reduce((prior, a) => { 32 | prior.push(...flatSeq(a)); 33 | return prior; 34 | }, []); 35 | } 36 | 37 | function flatSeq(term) { 38 | if (Array.isArray(term)) { 39 | if (term.length === 0) { 40 | return []; 41 | } 42 | const [kind, ...terms] = term; 43 | if (kind === 'seq') { 44 | return flatArgs(terms); 45 | } else if (terms.length === 0 && Array.isArray(kind)) { 46 | return flatSeq(kind); 47 | } else { 48 | return [[kind, ...flatArgs(terms)]]; 49 | } 50 | } 51 | 52 | return [term]; 53 | } 54 | 55 | return pegTag` 56 | # Hierarchical syntax 57 | 58 | Grammar <- _Spacing Definition+ _EndOfFile 59 | ${metaCompile}; 60 | 61 | Definition <- Identifier LEFTARROW Expression SEMI &${ACCEPT} 62 | ${(i, _, e, _2) => ['def', i, e]}; 63 | Expression <- Sequence ** _SLASH 64 | ${list => simple('or', list)}; 65 | Sequence <- (Prefix* 66 | ${list => simple('seq', list)}) 67 | HOLE? 68 | ${(seq, optHole) => optHole.length === 0 ? 69 | ['val0', ...flatSeq(seq)] : 70 | ['act', optHole[0], ...flatSeq(seq)]}; 71 | Prefix <- AND HOLE 72 | ${(_, a) => ['pred', a]} 73 | / AND Suffix 74 | ${(_, s) => ['peek', s]} 75 | / NOT Suffix 76 | ${(_, s) => ['peekNot', s]} 77 | / Suffix; 78 | Suffix <- Primary (STARSTAR 79 | / PLUSPLUS) Primary 80 | ${(patt, q, sep) => [q, patt, sep]} 81 | / Primary (QUESTION 82 | / STAR 83 | / PLUS) 84 | ${(patt, optQ) => [optQ[0], patt]} 85 | / Primary; 86 | Primary <- Super 87 | / Identifier ~LEFTARROW 88 | / OPEN Expression CLOSE 89 | ${(_, e, _2) => e} 90 | / Literal 91 | ${s => ['lit', s]} 92 | / Class 93 | ${c => ['cls', c]} 94 | / DOT 95 | ${() => ['dot']} 96 | / BEGIN 97 | ${() => ['begin']} 98 | / END 99 | ${() => ['end']} 100 | ; 101 | 102 | Super <- 'super.' Identifier 103 | ${(_, i) => ['super', i]}; 104 | 105 | # Lexical syntax 106 | 107 | Identifier <- < IdentStart IdentCont* > _Spacing; 108 | IdentStart <- [a-zA-Z_]; 109 | IdentCont <- IdentStart / [0-9]; 110 | Literal <- ['] < (~['] Char )* > ['] _Spacing 111 | / ["] < (~["] Char )* > ["] _Spacing; 112 | Class <- '[' < (~']' Range)* > ']' _Spacing; 113 | Range <- Char '-' Char / Char; 114 | Char <- '\\' [abefnrtv'"\[\]\\\`\$] 115 | / '\\x' [0-9a-fA-F][0-9a-fA-F] 116 | / '\\' '-' 117 | / ~'\\' .; 118 | LEFTARROW <- '<-' _Spacing; 119 | _SLASH <- '/' _Spacing ${_ => SKIP}; 120 | SEMI <- ';' _Spacing; 121 | AND <- '&' _Spacing; 122 | NOT <- '~' _Spacing; 123 | QUESTION <- '?' _Spacing; 124 | STAR <- '*' _Spacing; 125 | PLUS <- '+' _Spacing; 126 | OPEN <- '(' _Spacing; 127 | CLOSE <- ')' _Spacing; 128 | DOT <- '.' _Spacing; 129 | _Spacing <- (Space / Comment)* ${_ => SKIP}; 130 | Comment <- '#' (~EndOfLine .)* EndOfLine; 131 | Space <- ' ' / '\t' / EndOfLine; 132 | EndOfLine <- '\r\n' / '\n' / '\r'; 133 | _EndOfFile <- ~.; 134 | 135 | HOLE <- &${HOLE} _Spacing; 136 | BEGIN <- '<' _Spacing; 137 | END <- '>' _Spacing; 138 | PLUSPLUS <- '++' _Spacing; 139 | STARSTAR <- '**' _Spacing; 140 | `; 141 | }); 142 | 143 | export default makePeg; -------------------------------------------------------------------------------- /lib/quasi-peg.js.ts: -------------------------------------------------------------------------------- 1 | // PEG quasi Grammar for PEG quasi Grammars 2 | // Michael FIG , 2019-01-05 3 | // 4 | // This grammar is adapted from: 5 | // http://piumarta.com/software/peg/peg-0.1.18/src/peg.peg 6 | // 7 | // Modified for Jessica to support: 8 | // Semantic actions provided in tagged template HOLEs 9 | // '~' for negative lookahead (instead of '!') 10 | // ';' terminator for definitions 11 | // '**' and '++' for separators 12 | // 'super.RULE' syntax for extended grammars 13 | // '\xFF' instead of octal character literals 14 | // which are adapted from: 15 | // https://github.com/erights/quasiParserGenerator 16 | 17 | /// 18 | 19 | const makePeg = , U = IPegTag>>( 20 | pegTag: IBootPegTag, 21 | metaCompile: (defs: PegDef[]) => (..._: any[]) => U) => { 22 | const {ACCEPT, HOLE, SKIP} = pegTag; 23 | 24 | function simple(prefix: string, list: PegExpr[]) { 25 | if (list.length === 0) { return ['empty']; } 26 | if (list.length === 1) { return list[0]; } 27 | return [prefix, ...list]; 28 | } 29 | 30 | function flatArgs(args: PegExpr[]) { 31 | return args.reduce((prior, a) => { 32 | prior.push(...flatSeq(a)); 33 | return prior; 34 | }, []); 35 | } 36 | 37 | function flatSeq(term: PegExpr): PegExpr[] { 38 | if (Array.isArray(term)) { 39 | if (term.length === 0) { 40 | return []; 41 | } 42 | const [kind, ...terms] = term; 43 | if (kind === 'seq') { 44 | return flatArgs(terms); 45 | } else if (terms.length === 0 && Array.isArray(kind)) { 46 | return flatSeq(kind); 47 | } else { 48 | return [[kind, ...flatArgs(terms)]]; 49 | } 50 | } 51 | 52 | return [term]; 53 | } 54 | 55 | return pegTag` 56 | # Hierarchical syntax 57 | 58 | Grammar <- _Spacing Definition+ _EndOfFile 59 | ${metaCompile}; 60 | 61 | Definition <- Identifier LEFTARROW Expression SEMI &${ACCEPT} 62 | ${(i, _, e, _2) => ['def', i, e]}; 63 | Expression <- Sequence ** _SLASH 64 | ${list => simple('or', list)}; 65 | Sequence <- (Prefix* 66 | ${list => simple('seq', list)}) 67 | HOLE? 68 | ${(seq, optHole) => optHole.length === 0 ? 69 | ['val0', ...flatSeq(seq)] : 70 | ['act', optHole[0], ...flatSeq(seq)]}; 71 | Prefix <- AND HOLE 72 | ${(_, a) => ['pred', a]} 73 | / AND Suffix 74 | ${(_, s) => ['peek', s]} 75 | / NOT Suffix 76 | ${(_, s) => ['peekNot', s]} 77 | / Suffix; 78 | Suffix <- Primary (STARSTAR 79 | / PLUSPLUS) Primary 80 | ${(patt, q, sep) => [q, patt, sep]} 81 | / Primary (QUESTION 82 | / STAR 83 | / PLUS) 84 | ${(patt, optQ) => [optQ[0], patt]} 85 | / Primary; 86 | Primary <- Super 87 | / Identifier ~LEFTARROW 88 | / OPEN Expression CLOSE 89 | ${(_, e, _2) => e} 90 | / Literal 91 | ${(s) => ['lit', s]} 92 | / Class 93 | ${(c) => ['cls', c]} 94 | / DOT 95 | ${() => ['dot']} 96 | / BEGIN 97 | ${() => ['begin']} 98 | / END 99 | ${() => ['end']} 100 | ; 101 | 102 | Super <- 'super.' Identifier 103 | ${(_, i) => ['super', i]}; 104 | 105 | # Lexical syntax 106 | 107 | Identifier <- < IdentStart IdentCont* > _Spacing; 108 | IdentStart <- [a-zA-Z_]; 109 | IdentCont <- IdentStart / [0-9]; 110 | Literal <- ['] < (~['] Char )* > ['] _Spacing 111 | / ["] < (~["] Char )* > ["] _Spacing; 112 | Class <- '[' < (~']' Range)* > ']' _Spacing; 113 | Range <- Char '-' Char / Char; 114 | Char <- '\\' [abefnrtv'"\[\]\\\`\$] 115 | / '\\x' [0-9a-fA-F][0-9a-fA-F] 116 | / '\\' '-' 117 | / ~'\\' .; 118 | LEFTARROW <- '<-' _Spacing; 119 | _SLASH <- '/' _Spacing ${_ => SKIP}; 120 | SEMI <- ';' _Spacing; 121 | AND <- '&' _Spacing; 122 | NOT <- '~' _Spacing; 123 | QUESTION <- '?' _Spacing; 124 | STAR <- '*' _Spacing; 125 | PLUS <- '+' _Spacing; 126 | OPEN <- '(' _Spacing; 127 | CLOSE <- ')' _Spacing; 128 | DOT <- '.' _Spacing; 129 | _Spacing <- (Space / Comment)* ${_ => SKIP}; 130 | Comment <- '#' (~EndOfLine .)* EndOfLine; 131 | Space <- ' ' / '\t' / EndOfLine; 132 | EndOfLine <- '\r\n' / '\n' / '\r'; 133 | _EndOfFile <- ~.; 134 | 135 | HOLE <- &${HOLE} _Spacing; 136 | BEGIN <- '<' _Spacing; 137 | END <- '>' _Spacing; 138 | PLUSPLUS <- '++' _Spacing; 139 | STARSTAR <- '**' _Spacing; 140 | `; 141 | }; 142 | 143 | export default makePeg; 144 | -------------------------------------------------------------------------------- /lib/quasi-utils.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; export const qunpack = insulate((h, ms, t) => { 2 | return [h, ...ms, t]; 3 | }); 4 | 5 | export const qrepack = insulate(parts => { 6 | // TODO bug: We only provide the raw form at this time. I 7 | // apologize once again for allowing a cooked form into the 8 | // standard. 9 | const raw = [parts[0]]; 10 | const argExprs = []; 11 | const len = parts.length; 12 | for (let i = 1; i < len; i += 2) { 13 | argExprs.push(parts[i]); 14 | raw.push(parts[i + 1]); 15 | } 16 | const template = [...raw]; 17 | template.raw = raw; 18 | return [['data', template], ...argExprs]; 19 | }); -------------------------------------------------------------------------------- /lib/quasi-utils.js.ts: -------------------------------------------------------------------------------- 1 | export const qunpack = (h: string, ms: any[][], t: string) => { 2 | return [h, ...ms, t]; 3 | }; 4 | 5 | export const qrepack = (parts: any[]) => { 6 | // TODO bug: We only provide the raw form at this time. I 7 | // apologize once again for allowing a cooked form into the 8 | // standard. 9 | const raw = [parts[0]]; 10 | const argExprs = []; 11 | const len = parts.length; 12 | for (let i = 1; i < len; i += 2) { 13 | argExprs.push(parts[i]); 14 | raw.push(parts[i + 1]); 15 | } 16 | const template: string[] & { raw?: string[] } = [...raw]; 17 | template.raw = raw; 18 | return [['data', template], ...argExprs]; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/readInput.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; import { slog as $i_slog } from '@michaelfig/slog';const slog = insulate($i_slog); 2 | 3 | const makeReadInput = insulate((CAN_LOAD_FILES, readInput) => { 4 | const loader = file => { 5 | if (!CAN_LOAD_FILES.has(file)) { 6 | slog.error`${{ file }} not in INFILE whitelist`; 7 | } 8 | return readInput(file); 9 | }; 10 | return loader; 11 | }); 12 | 13 | export default makeReadInput; -------------------------------------------------------------------------------- /lib/readInput.js.ts: -------------------------------------------------------------------------------- 1 | import { slog } from '@michaelfig/slog'; 2 | 3 | const makeReadInput = (CAN_LOAD_FILES: Set, readInput: (file: string) => string) => { 4 | const loader = (file: string) => { 5 | if (!CAN_LOAD_FILES.has(file)) { 6 | slog.error`${{file}} not in INFILE whitelist`; 7 | } 8 | return readInput(file); 9 | }; 10 | return loader; 11 | }; 12 | 13 | export default makeReadInput; 14 | -------------------------------------------------------------------------------- /lib/repl.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; // FIXME: Not really a read-eval-print-loop, yet. 2 | const repl = insulate(( 3 | deps, 4 | doEval, 5 | file, 6 | argv) => { 7 | // Read... 8 | const data = deps.readInput(file); 9 | // Eval ... 10 | const main = doEval(data, file); 11 | // Execute as main, if a function. 12 | const val = typeof main === 'function' ? main(deps, argv) : main; 13 | // ... maybe Print. 14 | if (val !== undefined) { 15 | deps.writeOutput('-', JSON.stringify(val, undefined, ' ') + '\n'); 16 | } 17 | }); 18 | 19 | export default repl; -------------------------------------------------------------------------------- /lib/repl.js.ts: -------------------------------------------------------------------------------- 1 | // FIXME: Not really a read-eval-print-loop, yet. 2 | const repl = ( 3 | deps: IMainDependencies, 4 | doEval: (data: string, uri: string) => any, 5 | file: string, 6 | argv: string[]) => { 7 | // Read... 8 | const data = deps.readInput(file); 9 | // Eval ... 10 | const main = doEval(data, file); 11 | // Execute as main, if a function. 12 | const val = typeof main === 'function' ? main(deps, argv) : main; 13 | // ... maybe Print. 14 | if (val !== undefined) { 15 | deps.writeOutput('-', JSON.stringify(val, undefined, ' ') + '\n'); 16 | } 17 | }; 18 | 19 | export default repl; 20 | -------------------------------------------------------------------------------- /lib/rewrite-define.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; import { makeMap, makeSet } from '@agoric/jessie'; 2 | import { slog as $i_slog } from '@michaelfig/slog';const slog = insulate($i_slog); 3 | 4 | 5 | 6 | const hide = insulate(vname => `\$h_${vname}`); 7 | 8 | // Return a string separated by separators. 9 | const separate = insulate((strs, sep) => { 10 | let ret = ''; 11 | let actualSep = ''; 12 | for (const str of strs) { 13 | if (str !== '') { 14 | ret += actualSep + str; 15 | actualSep = sep; 16 | } 17 | } 18 | return ret; 19 | }); 20 | 21 | const moduleRewriteDefine = insulate((moduleAST, DEFINE = hide('define')) => { 22 | const EXPORTS = hide('exports'); 23 | const imports = makeMap(); 24 | const exportVars = makeSet(); 25 | let starName; 26 | let nImport = 0; 27 | const rewriters = { 28 | as(imp, sym) { 29 | if (imp === '*') { 30 | starName = sym; 31 | return ''; 32 | } 33 | return `${imp}: ${sym}`; 34 | }, 35 | bind(def, expr) { 36 | const name = doRewrite(def); 37 | return `${name} = ${doRewrite(expr)}`; 38 | }, 39 | def(name) { 40 | exportVars.add(name); 41 | return name; 42 | }, 43 | exportDefaultX(val) { 44 | return `${EXPORTS}.default = ${doRewrite(val)};`; 45 | }, 46 | exportX(qual, binds) { 47 | exportVars.clear(); 48 | const bindings = separate(binds.map(doRewrite), ', '); 49 | let assign = ''; 50 | for (const vname of exportVars.keys()) { 51 | assign += `${EXPORTS}.${vname} = ${vname};\n`; 52 | } 53 | exportVars.clear(); 54 | return `${qual} ${bindings};\n${assign}`; 55 | }, 56 | import(clause, fromModule) { 57 | // Save the bindings. 58 | starName = undefined; 59 | const bindings = doRewrite(clause); 60 | if (starName === undefined) { 61 | starName = hide(`star${nImport++}`); 62 | } 63 | imports.set(fromModule, starName); 64 | if (bindings) { 65 | return `const {${bindings}} = ${starName};\n`; 66 | } 67 | return ''; 68 | }, 69 | importBind(bindings) { 70 | return separate(bindings.map(doRewrite), ', '); 71 | }, 72 | matchArray(es) { 73 | return `[${separate(es.map(doRewrite), ', ')}]`; 74 | }, 75 | matchProp(kw, prop) { 76 | const rewrite = doRewrite(prop); 77 | if (kw === rewrite) { 78 | return kw; 79 | } 80 | return `${kw}: ${rewrite}`; 81 | }, 82 | matchRecord(es) { 83 | return `{${separate(es.map(doRewrite), ', ')}}`; 84 | }, 85 | moduleX(decls) { 86 | const body = decls.reduce((prior, cur) => prior + doRewrite(cur), ''); 87 | const modules = []; 88 | const names = []; 89 | for (const [mod, name] of imports.entries()) { 90 | modules.push(mod); 91 | names.push(name); 92 | } 93 | const bindings = separate(names, ', '); 94 | return `${DEFINE}( 95 | ${JSON.stringify(modules)}, 96 | (${bindings}) => { 97 | const ${EXPORTS} = {}; 98 | ${body} 99 | return ${EXPORTS}; 100 | })`; 101 | }, 102 | rest(expr) { 103 | return `...${doRewrite(expr)}`; 104 | }, 105 | restObj(expr) { 106 | return `...${doRewrite(expr)}`; 107 | } }; 108 | 109 | 110 | const doRewrite = node => { 111 | if (typeof node === 'string') { 112 | return node; 113 | } 114 | const [name, ...args] = node; 115 | const rewriter = rewriters[name]; 116 | if (!rewriter) { 117 | throw slog.error`No rewriter for ${{ name }}`; 118 | } 119 | return rewriter(...args); 120 | }; 121 | return doRewrite(moduleAST); 122 | }); 123 | 124 | export default moduleRewriteDefine; -------------------------------------------------------------------------------- /lib/rewrite-define.js.ts: -------------------------------------------------------------------------------- 1 | import { makeMap, makeSet } from '@agoric/jessie'; 2 | import { slog } from '@michaelfig/slog'; 3 | 4 | type Rewriter = (...args: any) => string; 5 | type Node = any[] | string; 6 | const hide = (vname: string) => `\$h_${vname}`; 7 | 8 | // Return a string separated by separators. 9 | const separate = (strs: string[], sep: string) => { 10 | let ret = ''; 11 | let actualSep = ''; 12 | for (const str of strs) { 13 | if (str !== '') { 14 | ret += actualSep + str; 15 | actualSep = sep; 16 | } 17 | } 18 | return ret; 19 | }; 20 | 21 | const moduleRewriteDefine = (moduleAST: any[], DEFINE = hide('define')) => { 22 | const EXPORTS = hide('exports'); 23 | const imports = makeMap(); 24 | const exportVars = makeSet(); 25 | let starName: string; 26 | let nImport = 0; 27 | const rewriters: Record = { 28 | as(imp: string, sym: string) { 29 | if (imp === '*') { 30 | starName = sym; 31 | return ''; 32 | } 33 | return `${imp}: ${sym}`; 34 | }, 35 | bind(def: Node, expr: Node) { 36 | const name = doRewrite(def); 37 | return `${name} = ${doRewrite(expr)}`; 38 | }, 39 | def(name: string) { 40 | exportVars.add(name); 41 | return name; 42 | }, 43 | exportDefaultX(val: Node[]) { 44 | return `${EXPORTS}.default = ${doRewrite(val)};`; 45 | }, 46 | exportX(qual: string, binds: Node[]) { 47 | exportVars.clear(); 48 | const bindings = separate(binds.map(doRewrite), ', '); 49 | let assign = ''; 50 | for (const vname of exportVars.keys()) { 51 | assign += `${EXPORTS}.${vname} = ${vname};\n`; 52 | } 53 | exportVars.clear(); 54 | return `${qual} ${bindings};\n${assign}`; 55 | }, 56 | import(clause: Node[], fromModule: string) { 57 | // Save the bindings. 58 | starName = undefined; 59 | const bindings = doRewrite(clause); 60 | if (starName === undefined) { 61 | starName = hide(`star${nImport ++}`); 62 | } 63 | imports.set(fromModule, starName); 64 | if (bindings) { 65 | return `const {${bindings}} = ${starName};\n`; 66 | } 67 | return ''; 68 | }, 69 | importBind(bindings: Node[]) { 70 | return separate(bindings.map(doRewrite), ', '); 71 | }, 72 | matchArray(es: Node[]) { 73 | return `[${separate(es.map(doRewrite), ', ')}]`; 74 | }, 75 | matchProp(kw: string, prop: Node) { 76 | const rewrite = doRewrite(prop); 77 | if (kw === rewrite) { 78 | return kw; 79 | } 80 | return `${kw}: ${rewrite}`; 81 | }, 82 | matchRecord(es: Node[]) { 83 | return `{${separate(es.map(doRewrite), ', ')}}`; 84 | }, 85 | moduleX(decls: Node[]) { 86 | const body = decls.reduce((prior, cur) => prior + doRewrite(cur), ''); 87 | const modules: string[] = []; 88 | const names: string[] = []; 89 | for (const [mod, name] of imports.entries()) { 90 | modules.push(mod); 91 | names.push(name); 92 | } 93 | const bindings = separate(names, ', '); 94 | return `${DEFINE}( 95 | ${JSON.stringify(modules)}, 96 | (${bindings}) => { 97 | const ${EXPORTS} = {}; 98 | ${body} 99 | return ${EXPORTS}; 100 | })`; 101 | }, 102 | rest(expr: Node) { 103 | return `...${doRewrite(expr)}`; 104 | }, 105 | restObj(expr: Node) { 106 | return `...${doRewrite(expr)}`; 107 | }, 108 | }; 109 | 110 | const doRewrite = (node: Node) => { 111 | if (typeof node === 'string') { 112 | return node; 113 | } 114 | const [name, ...args] = node; 115 | const rewriter = rewriters[name]; 116 | if (!rewriter) { 117 | throw slog.error`No rewriter for ${{name}}`; 118 | } 119 | return rewriter(...args); 120 | }; 121 | return doRewrite(moduleAST); 122 | }; 123 | 124 | export default moduleRewriteDefine; 125 | -------------------------------------------------------------------------------- /lib/tag-string.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; /// 2 | const tagString = insulate((tag, uri) => { 3 | 4 | 5 | function tagged(templateOrFlag, ...args) 6 | { 7 | if (typeof templateOrFlag === 'string') { 8 | return tagString(tag(templateOrFlag), uri); 9 | } 10 | const template = templateOrFlag; 11 | const cooked = template.reduce((prior, t, i) => { 12 | prior.push(t, String(args[i])); 13 | return prior; 14 | }, []); 15 | cooked.push(template[template.length - 1]); 16 | const cooked0 = cooked.join(''); 17 | const raw0 = args.reduce((prior, hole, i) => { 18 | prior.push(String(hole), template.raw[i + 1]); 19 | return prior; 20 | }, [template.raw[0]]).join(''); 21 | const sources0 = { 22 | byte: 0, 23 | column: 1, 24 | line: 1, 25 | uri }; 26 | 27 | const tmpl = [cooked0]; 28 | tmpl.raw = [raw0]; 29 | tmpl.sources = [sources0]; 30 | return tag(tmpl); 31 | } 32 | tagged.parserCreator = tag.parserCreator; 33 | tagged._asExtending = tag._asExtending; 34 | return tagged; 35 | }); 36 | 37 | export default tagString; -------------------------------------------------------------------------------- /lib/tag-string.js.ts: -------------------------------------------------------------------------------- 1 | /// 2 | const tagString = (tag: IParserTag, uri?: string) => { 3 | function tagged(flag: string): IParserTag; 4 | function tagged(template: TemplateStringsArray, ...args: any[]): T; 5 | function tagged(templateOrFlag: string | TemplateStringsArray, ...args: any[]): 6 | T | IParserTag { 7 | if (typeof templateOrFlag === 'string') { 8 | return tagString(tag(templateOrFlag), uri); 9 | } 10 | const template = templateOrFlag; 11 | const cooked = template.reduce((prior, t, i) => { 12 | prior.push(t, String(args[i])); 13 | return prior; 14 | }, []); 15 | cooked.push(template[template.length - 1]); 16 | const cooked0 = cooked.join(''); 17 | const raw0 = args.reduce((prior, hole, i) => { 18 | prior.push(String(hole), template.raw[i + 1]); 19 | return prior; 20 | }, [template.raw[0]]).join(''); 21 | const sources0 = { 22 | byte: 0, 23 | column: 1, 24 | line: 1, 25 | uri, 26 | }; 27 | const tmpl: any = [cooked0]; 28 | tmpl.raw = [raw0]; 29 | tmpl.sources = [sources0]; 30 | return tag(tmpl as TemplateStringsArray); 31 | } 32 | tagged.parserCreator = tag.parserCreator; 33 | tagged._asExtending = tag._asExtending; 34 | return tagged; 35 | }; 36 | 37 | export default tagString; 38 | -------------------------------------------------------------------------------- /lib/translate.js: -------------------------------------------------------------------------------- 1 | import { insulate } from '@agoric/jessie'; import { makePromise } from '@agoric/jessie'; 2 | import { slog as $i_slog } from '@michaelfig/slog';const slog = insulate($i_slog); 3 | 4 | import $i_bootPeg from './boot-peg.js';const bootPeg = insulate($i_bootPeg); 5 | import $i_bootPegAst from './boot-pegast.js';const bootPegAst = insulate($i_bootPegAst); 6 | import $i_makePeg from './quasi-peg.js';const makePeg = insulate($i_makePeg); 7 | 8 | import $i_makeJessieModule from './quasi-jessie-module.js';const makeJessieModule = insulate($i_makeJessieModule); 9 | import $i_makeJessie from './quasi-jessie.js';const makeJessie = insulate($i_makeJessie); 10 | import $i_makeJSON from './quasi-json.js';const makeJSON = insulate($i_makeJSON); 11 | import $i_makeJustin from './quasi-justin.js';const makeJustin = insulate($i_makeJustin); 12 | import $i_rewriteModuleDefine from './rewrite-define.js';const rewriteModuleDefine = insulate($i_rewriteModuleDefine); 13 | import $i_tagString from './tag-string.js';const tagString = insulate($i_tagString); 14 | 15 | const pegTag = insulate(bootPeg(makePeg, bootPegAst)); 16 | const jsonTag = insulate(makeJSON(pegTag)); 17 | const justinTag = insulate(makeJustin(pegTag.extends(jsonTag))); 18 | const [jessieTag] = insulate(makeJessie(pegTag, pegTag.extends(justinTag))); 19 | const jessieModuleTag = insulate(makeJessieModule(pegTag.extends(jessieTag))); 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | export const parse = insulate((sourceText, parameters) => 56 | makePromise(resolve => { 57 | const tag = tagString(jessieTag, parameters.sourceURL); 58 | const moduleAst = tag`${sourceText}`; 59 | resolve(moduleAst); 60 | })); 61 | 62 | export const translate = insulate((sourceText, parameters) => 63 | 64 | makePromise(resolve => { 65 | const { sourceType, target, targetType } = parameters; 66 | if (sourceType !== 'jessie') { 67 | throw slog.error`Unrecognized sourceType: ${{ sourceType }}`; 68 | } 69 | if (target !== 'jessie-frame') { 70 | throw slog.error`Unrecognized target: ${{ target }}`; 71 | } 72 | 73 | switch (targetType) { 74 | case 'function':{ 75 | const tag = tagString(jessieModuleTag, parameters.sourceURL); 76 | 77 | // Throw an exception if the sourceText doesn't parse. 78 | const moduleAst = tag`${sourceText}`; 79 | 80 | // Rewrite the ESM imports/exports into an SES-honouring AMD form. 81 | const translatedText = rewriteModuleDefine(moduleAst, '$h_define'); 82 | const result = { 83 | ...parameters, 84 | translatedText }; 85 | 86 | return resolve(result); 87 | } 88 | case 'module':{ 89 | const tag = tagString(jessieTag, parameters.sourceURL); 90 | 91 | // Throw an exception if the sourceText doesn't parse. 92 | tag`${sourceText}`; 93 | 94 | // Return the sourceText verbatim. 95 | const result = { 96 | ...parameters, 97 | translatedText: sourceText }; 98 | 99 | return resolve(result); 100 | } 101 | default:{ 102 | throw slog.error`Unrecognized targetType: ${{ targetType }}`; 103 | }} 104 | 105 | 106 | })); -------------------------------------------------------------------------------- /lib/translate.js.ts: -------------------------------------------------------------------------------- 1 | import { makePromise } from '@agoric/jessie'; 2 | import { slog } from '@michaelfig/slog'; 3 | 4 | import bootPeg from './boot-peg.js'; 5 | import bootPegAst from './boot-pegast.js'; 6 | import makePeg from './quasi-peg.js'; 7 | 8 | import makeJessieModule from './quasi-jessie-module.js'; 9 | import makeJessie from './quasi-jessie.js'; 10 | import makeJSON from './quasi-json.js'; 11 | import makeJustin from './quasi-justin.js'; 12 | import rewriteModuleDefine from './rewrite-define.js'; 13 | import tagString from './tag-string.js'; 14 | 15 | const pegTag = bootPeg(makePeg, bootPegAst); 16 | const jsonTag = makeJSON(pegTag); 17 | const justinTag = makeJustin(pegTag.extends(jsonTag)); 18 | const [jessieTag] = makeJessie(pegTag, pegTag.extends(justinTag)); 19 | const jessieModuleTag = makeJessieModule(pegTag.extends(jessieTag)); 20 | 21 | export type Targets = 'jessie-frame'; 22 | export type SourceTypes = 'jessie'; 23 | export type TargetTypes = 'function' | 'module'; 24 | 25 | export interface ISerializableJessicaParameters { 26 | // The kind of output we want from the `translate` function. 27 | target: Targets; 28 | targetType: TargetTypes; 29 | } 30 | 31 | export interface IResourceRequestParameters { 32 | // Same base as the frame's Service Worker scope. 33 | // https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?hl=en_US&pli=1&pli=1 34 | sourceURL: string; 35 | 36 | sourceType: SourceTypes; 37 | 38 | // Where the resource is actually fetched from by the Service Worker. 39 | remoteURL: string; 40 | } 41 | 42 | export interface IJessicaResourceParameters 43 | extends ISerializableJessicaParameters, IResourceRequestParameters { 44 | 45 | } 46 | 47 | // TODO: Eventually, allow asynchrony, too. 48 | export type Readable = string; 49 | 50 | interface IJessicaTranslationResult extends IJessicaResourceParameters { 51 | // These are the compiler outputs: 52 | translatedText: Readable; 53 | } 54 | 55 | export const parse = (sourceText: Readable, parameters: IJessicaResourceParameters): Promise => 56 | makePromise(resolve => { 57 | const tag = tagString(jessieTag, parameters.sourceURL); 58 | const moduleAst = tag`${sourceText}`; 59 | resolve(moduleAst); 60 | }); 61 | 62 | export const translate = (sourceText: Readable, parameters: IJessicaResourceParameters): 63 | Promise => 64 | makePromise(resolve => { 65 | const {sourceType, target, targetType} = parameters; 66 | if (sourceType !== 'jessie') { 67 | throw slog.error`Unrecognized sourceType: ${{sourceType}}`; 68 | } 69 | if (target !== 'jessie-frame') { 70 | throw slog.error`Unrecognized target: ${{target}}`; 71 | } 72 | 73 | switch (targetType) { 74 | case 'function': { 75 | const tag = tagString(jessieModuleTag, parameters.sourceURL); 76 | 77 | // Throw an exception if the sourceText doesn't parse. 78 | const moduleAst = tag`${sourceText}`; 79 | 80 | // Rewrite the ESM imports/exports into an SES-honouring AMD form. 81 | const translatedText = rewriteModuleDefine(moduleAst, '$h_define'); 82 | const result: IJessicaTranslationResult = { 83 | ...parameters, 84 | translatedText, 85 | }; 86 | return resolve(result); 87 | } 88 | case 'module': { 89 | const tag = tagString(jessieTag, parameters.sourceURL); 90 | 91 | // Throw an exception if the sourceText doesn't parse. 92 | tag`${sourceText}`; 93 | 94 | // Return the sourceText verbatim. 95 | const result: IJessicaTranslationResult = { 96 | ...parameters, 97 | translatedText: sourceText, 98 | }; 99 | return resolve(result); 100 | } 101 | default: { 102 | throw slog.error`Unrecognized targetType: ${{targetType}}`; 103 | } 104 | } 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /makedocs.bat: -------------------------------------------------------------------------------- 1 | :; cd `dirname "$0"` || exit $? 2 | :; set -e 3 | :; ./build.bat 4 | :; ./lang/browser/build.bat 5 | :; rm -rf docs/jessie-frame/ 6 | :; cp -r lang/browser/dist docs/jessie-frame 7 | :; exit $? 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jessica", 3 | "version": "0.0.2", 4 | "description": "Jessica - the Jessie Compiler Architecture", 5 | "scripts": { 6 | "build": "build.bat", 7 | "check-types": "tsc", 8 | "test": "build.bat&&check.bat" 9 | }, 10 | "author": "Michael FIG ", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "@agoric/jessie": "file:unpublished/agoric-jessie-0.2.0.tgz", 14 | "@michaelfig/slog": "0.0.1", 15 | "@types/agoric__harden": "file:unpublished/types-agoric__harden-1.0.0.tgz", 16 | "@types/agoric__jessie": "file:unpublished/types-agoric__jessie-1.0.0.tgz", 17 | "@types/michaelfig__slog": "file:unpublished/types-michaelfig__slog-1.0.0.tgz" 18 | }, 19 | "devDependencies": { 20 | "tslint": "^5.13.0", 21 | "typescript": "^3.4.5" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/michaelfig/jessica.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/michaelfig/jessica/issues" 29 | }, 30 | "homepage": "https://github.com/michaelfig/jessica#readme" 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { "*": ["types/*"] }, 5 | "target": "esnext", 6 | "module": "es6", 7 | "typeRoots": ["./typings", "./node_modules/@types"], 8 | "noImplicitAny": true, 9 | "noEmit": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "alwaysStrict": true, 13 | "types": ["./typings/lib.jessie", "./node_modules/@michaelfig/slog/slog"] 14 | }, 15 | "include": [ 16 | "lib/**/*" 17 | ], 18 | "exclude": [ 19 | "node_modules/**", 20 | "**/*.spec.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended"], 3 | "rules": { 4 | "no-unused-expression": [true, "allow-tagged-template"], 5 | "no-unused-variable": true, 6 | "no-floating-promises": [true, "Promise"], 7 | "one-variable-per-declaration": false, 8 | "variable-name": [true, "allow-leading-underscore"], 9 | "quotemark": false, 10 | "object-literal-key-quotes": [true, "as-needed"], 11 | "arrow-parens": false, 12 | "no-reference": false, 13 | "no-bitwise": false, 14 | "trailing-comma": [true, {"singleline": "never"}], 15 | "no-console": [true, "log"] 16 | }, 17 | "linterOptions": { 18 | "exclude": [ 19 | "**/*.js" 20 | ] 21 | }, 22 | "alwaysShowRuleFailuresAsWarnings": true 23 | } 24 | -------------------------------------------------------------------------------- /typings/jessie-proposed.d.ts: -------------------------------------------------------------------------------- 1 | // jessie-proposed.d.ts - Jessie proposed whitelist 2 | // These are not part of the official Jessie whitelist, but are proposed 3 | // to provide a smoother programming experience. 4 | // 5 | // When they have become a part of: 6 | // https://github.com/Agoric/Jessie/blob/master/src/bundle/whitelist.js 7 | // then they will be moved to lib.jessie.d.ts. 8 | // 9 | // Michael FIG , 2019-02-23 10 | /// 11 | interface IMainDependencies { 12 | applyMethod: (boundThis: any, method: (...args: any[]) => any, args: any[]) => any, 13 | setComputedIndex: (obj: Record, index: string | number, value: T) => T, 14 | readInput: (file: string) => string; 15 | writeOutput: (file: string, data: string) => void; 16 | } 17 | -------------------------------------------------------------------------------- /typings/ses-proposed.d.ts: -------------------------------------------------------------------------------- 1 | interface SourceLocation { 2 | readonly uri: string; 3 | readonly byte: number; 4 | readonly line: number; 5 | readonly column: number; 6 | } 7 | interface TemplateStringsArray extends ReadonlyArray { 8 | readonly sources?: ReadonlyArray; 9 | } 10 | -------------------------------------------------------------------------------- /typings/ses.d.ts: -------------------------------------------------------------------------------- 1 | // SES endowments. 2 | /// 3 | 4 | /* TODO: Add declarations for: 5 | cajaVM: { // Caja support 6 | Nat: j, 7 | def: j, 8 | 9 | confine: j, 10 | }, 11 | */ -------------------------------------------------------------------------------- /unpublished/agoric-jessie-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agoric-labs/jessica/7a6070c8e2af0754e15e6f539611e33a58431c13/unpublished/agoric-jessie-0.1.0.tgz -------------------------------------------------------------------------------- /unpublished/agoric-jessie-0.2.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agoric-labs/jessica/7a6070c8e2af0754e15e6f539611e33a58431c13/unpublished/agoric-jessie-0.2.0.tgz -------------------------------------------------------------------------------- /unpublished/types-agoric__harden-1.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agoric-labs/jessica/7a6070c8e2af0754e15e6f539611e33a58431c13/unpublished/types-agoric__harden-1.0.0.tgz -------------------------------------------------------------------------------- /unpublished/types-agoric__jessie-1.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agoric-labs/jessica/7a6070c8e2af0754e15e6f539611e33a58431c13/unpublished/types-agoric__jessie-1.0.0.tgz -------------------------------------------------------------------------------- /unpublished/types-michaelfig__slog-1.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agoric-labs/jessica/7a6070c8e2af0754e15e6f539611e33a58431c13/unpublished/types-michaelfig__slog-1.0.0.tgz --------------------------------------------------------------------------------