initial commit, pretty much done

master
skybldev 9 months ago
commit 48481825b3

@ -0,0 +1,5 @@
{
"deno.enable": true,
"deno.unstable": true,
"editor.tabSize": 4
}

@ -0,0 +1,86 @@
# Documentation
Documentation for FlowJS.
## Types
FlowJS mostly uses TypeScript-style types, except for `int` and `float` for
feature parity with Flowgorithm. Note that these names are slightly different
from Flowgorithm's, `int` translates to `Integer`, `float` translates to `Real`,
and the rest have similar capitalization.
## Builtins
### `print(x: any)`
Translates to an `Output` statement.
### `input(var: <Identifier: any>)`
Translates to an `Input` statement. `var` must be a variable identifier.
### `attr(key: string, value: string)`
Applies a value to a specific program attribute. Available attributes:
- **name** - the program's name
- **authors** - the program's authors
- **about** - description of the program
- **saved** - a date string in the form of `yyyy-MM-dd HH:mm:ss a` e.g.
`2022-10-23 11:10:23 AM` (keep in mind the 12-hour time format)
**edited** and **created** are also available but are always overwritten upon
transpilation.
### `meta(key: string, value: string)`
Applies a value to a specific meta property of a program. Available keys:
- **username** - your logged in user
- **hostname** - your computer's hostname
- **editVersion** - the program's edit version (starts from 1)
- **editMysteryNumber** - idk what this even is but it starts at like 2300 iirc
### `comment(comment: string)`
Creates a comment with the given string.
### `insist(var: <Identifier: any>)`
**(temporary)** · A lazy temporary soution to make the typechecker believe
that an identifier has been **defined** without affecting the output code.
## Statements
### `for`
For statement syntax is slightly different from JS/TS syntax in order to have
compatibility with Flowgorithm's.
`for (init; <end>; upd) <BlockStatement>`
- `init` can either be `<var> = <start>` with an optional `let`. `<var>` should
always be an `<Identifier: float | int>` and `<start>` should always be a
`<NumericLiteral>`.
- `<end>` is the number to end the loop at. It should always be a
`<NumericLiteral>`.
- `upd` can either be (where `<var>` matches that of `init`):
- `<var>++/--` - This assumes step is 1 and direction is either "inc" (`++`)
or "dec" (`--`).
- `<var> +=/-= <step>` - Here you can define the step number, and direction
is either "inc" (`+=`) or "dec" (`-=`).
#### Examples:
```ts
for (let i = 0; 10; i++) {
print("Going up! " + i);
}
```
```ts
// `i` was declared earlier
for (i = 20; 10; i -= 2) {
print("Going down! " + i )
}
```

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<flowgorithm fileversion="3.0">
<attributes>
<attribute name="name" value="[[name]]"/>
<attribute name="authors" value="[[authors]]"/>
<attribute name="about" value="[[about]]"/>
<attribute name="saved" value="[[saved]]"/>
<attribute name="created" value="[[created]]"/>
<attribute name="edited" value="[[edited]]"/>
</attributes>
<function name="Main" type="None" variable="">
<parameters/>
<body>
[[body]]
</body>
</function>
</flowgorithm>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,72 @@
import * as SWC from "../src/swc.ts";
import { CallExpression, ExpressionStatement } from "../src/swc.ts";
import * as Trans from "../src/transformers.ts";
const input = Deno.readTextFileSync("./benchmarks/benchtest.ts");
const fakeState = {
input: "",
indent: " ",
idents: new Map<string, Trans.Ident>(),
opts: {
indent: 4,
showSourceStatements: false,
attr: {},
meta: {},
}
};
const ast = SWC.parse(input, {
syntax: "typescript",
comments: true,
target: "es2020",
});
const template = Deno.readTextFileSync("./assets/template.fprg");
Deno.bench("transform", () => {
Trans.transform(input, ast, {
indent: 3,
showSourceStatements: true,
}, template);
});
Deno.bench("transformBlock", () => {
Trans.transformBlock(
ast.body,
{ ...fakeState, idents: new Map<string, Trans.Ident>() },
);
});
Deno.bench("transformVariableDecl", () => {
Trans.transformVariableDecl(
ast.body[7] as SWC.VariableDeclaration,
{ ...fakeState, idents: new Map<string, Trans.Ident>() },
);
});
fakeState.idents = new Map<string, Trans.Ident>([
["a", { type: "int", defined: true }],
["b", { type: "int", defined: true }],
]);
Deno.bench("transformExpr :: `20 / ((5 + a) ^ (a + b))`", () => {
Trans.transformExpr(
(ast.body[10] as SWC.VariableDeclaration).declarations[0].init!,
fakeState.idents,
);
});
Deno.bench("transformBlockCallExpr :: `print(a + b)`", () => {
Trans.transformBlockCallExpr(
(ast.body[9] as ExpressionStatement).expression as CallExpression,
fakeState,
);
});
Deno.bench("transformIfStmt", () => {
Trans.transformIfStmt(
ast.body[19] as SWC.IfStatement,
fakeState
);
});

@ -0,0 +1,49 @@
meta("username", "skybl");
meta("hostname", "pond");
meta("editVersion", "5");
meta("editMysteryNumber", "2991");
attr("name", "benchtest.ts");
attr("authors", "skybl, ezfprg");
attr("about", "test program for transpiler benchmarks");
let a: int = 1, b: int;
input(b);
print(a + b);
let c: float = 20 / ((5 + a) ^ (a + b));
print(c);
let e: float = 1.4, f = 2;
let g = e / f;
let h: boolean = false;
print(" " + e + " / " + f + " = " + g + ", h = " + h);
let i = 2 + " hello";
let j = i + " world";
print(j);
if (a < b) {
print("a is less than b");
} else {
print('a is more than b');
}
if (true) {
print("single arm");
}
let k = 0;
while (k < 10) {
print(k);
k = k + 1;
}
for (let l = 0; 10; l++) {
print(l);
}
for (l = 20; 10; l -= 2) {
print(l);
}

@ -0,0 +1,9 @@
//// <reference no-default-lib="true" />
declare type int = number;
declare type float = number;
declare function print(a: unknown): void;
declare function input(a: unknown): void;
declare function meta(a: "username" | "hostname" | "editVersion" | "editMysteryNumber", b: string): void;
declare function attr(a: "name" | "authors" | "about", b: string): void;

@ -0,0 +1,10 @@
{
"folders": [
{
"path": ".."
}
],
"settings": {
"deno.unstable": true
}
}

@ -0,0 +1,114 @@
const KEYWORDS = [
"let",
"for",
"print",
"input",
"//"
];
const TYPES = [
"int",
"float",
"string",
"bool"
];
const PATTERNS = [
"asgn": [ "ident", "eq", "expr" ],
"let": [ "type", "ident", "eq?", "expr" ],
];
class UnexpectedTokenError extends SyntaxError {
constructor (expectedTokens, token, line, col) {
const expected = expectedTokens.map((t) => `"${t}"`).join(", ");
super(`Expected ${expected} but got ${token}`);
this.lineNumber = line;
this.columnNumber = col;
}
}
class UnexpectedEndOfInputError {
constructor (line, col) {
super(`Unexpected end of input at ${line}:${col}.`);
}
}
const inPath = Deno.args[0];
const dec = new TextDecoder("utf-8);
const raw = Deno.readFile(inPath);
const input = dec.decode(raw);
const lines = input.split("\n");
let tree = [];
let stack = [];
let ctx = "";
// keyword, ident, type, num, string, op, group, eof
let expectedTokens = ["keyword", "ident"];
let tokenType = null;
// ` `, `{`, `}`
let delim = " ";
let token = "";
let idents = [];
for (let line = 0; line < lines.length; line++) {
const currentLine = lines[line];
for (let col = 0; col < currentLine.length; col++) {
if (currentLine[col] === delim) {
tokenType = null;
const tokenHandlers = {
"keyword": () => {
if (KEYWORDS.includes(token)) {
stack.push(token);
tokenType = "keyword";
}
},
"ident": () => {
if (idents.includes(token)) {
stack.push(token);
idents.push(token);
tokenType = "ident";
}
},
};
for (type in expectedTokens) {
tokenHandlers[type]();
if (tokenType !== null) {
break;
}
}
if (!expectedTokens.includes(tokenType)) {
throw new UnexpectedTokenError(expectedTokens, token, line, col);
} else {
switch (tokenType) {
case "keyword":
switch (token) {
case "let":
ctx = patterns["let"];
expectedToken = "ident";
delim = " ";
break;
}
break;
case "ident":
} else {
token.push(line[j]);
}
switch (mode) {
case "keyword": {
const keyword = line.split(" ")[0];
if (KEYWORDS.includes(keyword)) {
stack.push(keyword);
} else {
throw new UnknownKeywordError(keyword, i, 0);

@ -0,0 +1,7 @@
import { parse } from "./src/swc.ts";
import { basename } from "https://deno.land/std@0.159.0/path/posix.ts?s=basename";
const input = Deno.readTextFileSync(Deno.args[0]);
const ast = parse(input, { syntax: "typescript", target: "es2020" });
const json = JSON.stringify(ast, null, 2);
Deno.writeTextFileSync(basename(Deno.args[0]) + ".ast.json", json);

@ -0,0 +1,59 @@
name: ci
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: denolib/setup-deno@master
with:
deno-version: 1.12.2
- name: Format Check
run: deno fmt --check --ignore=swc_wasm
swc-deno-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: denolib/setup-deno@master
with:
deno-version: 1.23.0
- uses: hecrj/setup-rust-action@v1
with:
rust-version: nightly
- name: Install wasm32-unknown-unknown target
run: rustup target add wasm32-unknown-unknown
- name: Install wasm-bindgen
run: cargo install --version 0.2.72 wasm-bindgen-cli
- name: Cache Cargo home
uses: actions/cache@v2
with:
# See https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: f-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}
- name: Cache build output
uses: actions/cache@03e00da99d75a2204924908e1cca7902cafce66b
with:
path: target
key: |
f-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-
- name: Build
run: deno task build
- name: Test
run: deno task test

@ -0,0 +1,6 @@
target/
swc_wasm/pkg
builds/
*.exe
config.toml
.vscode/settings.json

@ -0,0 +1,3 @@
max_width = 80
tab_spaces = 2
edition = "2021"

2324
lib/deno_swc/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,8 @@
[workspace]
members = [
"swc_wasm/"
]
[profile.release]
lto = true
opt-level = "s"

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-22 Divy Srivastava
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,78 @@
<br />
<p align="center">
<a href="https://github.com/littledivy/deno_swc">
<img src="https://raw.githubusercontent.com/littledivy/deno_swc/master/assets/deno_swc.png" alt="deno_swc logo" width="310">
</a>
<h3 align="center">deno_swc</h3>
<p align="center">
The SWC compiler for Deno.
</p>
</p>
![ci](https://github.com/littledivy/deno_swc/workflows/ci/badge.svg)
![](https://img.shields.io/github/v/release/littledivy/deno_swc?style=plastic)
# Usage
`parse()`
```typescript
import { parse, print } from "https://deno.land/x/swc@0.2.1/mod.ts";
const code = `const x: string = "Hello, Deno SWC!"`;
const ast = parse(code, {
target: "es2019",
syntax: "typescript",
comments: false,
});
// {
// type: "Module",
// span: { start: 0, end: 36, ctxt: 0 },
// body: [
// {
// type: "VariableDeclaration",
// span: [Object],
// kind: "const",
// declare: false,
// declarations: [Array]
// }
// ],
// interpreter: null
// }
```
`print()`
```typescript
const { code } = print(ast, {
minify: true,
module: {
type: "commonjs",
},
});
// const x = "Hello, Deno SWC!"
```
...and `transform()`
```typescript
const { code } = transform("const x: number = 2;", {
jsc: {
target: "es2016",
parser: {
syntax: "typescript",
},
},
});
// const x = 2;
```
## Copyright
deno_swc is licensed under the MIT license. Please see the [LICENSE](LICENSE)
file.

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@ -0,0 +1,4 @@
import { compress } from "https://deno.land/x/lz4@v0.1.2/mod.ts";
const name = "./lib/deno_swc_bg.wasm";
Deno.writeFileSync(name, compress(Deno.readFileSync(name)));

@ -0,0 +1,10 @@
{
"tasks": {
"build": "deno run -A --unstable https://raw.githubusercontent.com/denoland/wasmbuild/0e62b100df246567bee43eea227456222b7fc1dd/main.ts && deno task build:compress",
// Use a canary / local version of wasmbuild
"build:local": "deno run -A --unstable ../wasmbuild/main.ts && deno task build:compress",
"build:compress": "deno run --allow-read --allow-write compress.ts",
"fmt": "deno fmt --ignore=swc_wasm,lib,target --unstable && cargo fmt",
"test": "deno test -A --no-check tests/"
}
}

@ -0,0 +1,13 @@
import { parse } from "../mod.ts";
const start = performance.now();
console.log(parse(
`
import * as a from "./a.ts";
`,
{
syntax: "ecmascript",
},
));
const end = performance.now() - start;
console.log(`parse time: ${end}ms`);

@ -0,0 +1,32 @@
import { parse, print } from "../mod.ts";
const code = `
interface H {
h: string;
}
const x: string = \`Hello, $\{"Hello"} Deno SWC!\`;
switch (x) {
case "value":
console.log(x);
break;
default:
break;
}
`;
const ast = parse(code, {
target: "es2019",
syntax: "typescript",
comments: false,
});
const regeneratedCode = print(ast, {
minify: true,
module: {
type: "commonjs",
},
}).code;
console.log(regeneratedCode);

@ -0,0 +1,13 @@
import { transform } from "../mod.ts";
const { code } = transform("const x: number = 2;", {
// @ts-ignore: TransformConfig typings for swc_wasm and node_swc are different
"jsc": {
"target": "es2016",
"parser": {
"syntax": "typescript",
},
},
});
console.log(code);

@ -0,0 +1,378 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// @generated file from build script, do not edit
// deno-lint-ignore-file
// source-hash: cd615b66c9fc26758ad0fac7a225b8fdeeb8cc3f
let wasm;
const heap = new Array(32).fill(undefined);
heap.push(undefined, null, true, false);
function getObject(idx) {
return heap[idx];
}
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
const cachedTextDecoder = new TextDecoder("utf-8", {
ignoreBOM: true,
fatal: true,
});
cachedTextDecoder.decode();
let cachedUint8Memory0;
function getUint8Memory0() {
if (cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
let WASM_VECTOR_LEN = 0;
const cachedTextEncoder = new TextEncoder("utf-8");
const encodeString = function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
};
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
let cachedInt32Memory0;
function getInt32Memory0() {
if (cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachedInt32Memory0;
}
/**
* @param {string} s
* @param {any} opts
* @returns {any}
*/
export function minifySync(s, opts) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(
s,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
const len0 = WASM_VECTOR_LEN;
wasm.minifySync(retptr, ptr0, len0, addHeapObject(opts));
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var r2 = getInt32Memory0()[retptr / 4 + 2];
if (r2) {
throw takeObject(r1);
}
return takeObject(r0);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* @param {string} s
* @param {any} opts
* @returns {any}
*/
export function parseSync(s, opts) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(
s,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
const len0 = WASM_VECTOR_LEN;
wasm.parseSync(retptr, ptr0, len0, addHeapObject(opts));
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var r2 = getInt32Memory0()[retptr / 4 + 2];
if (r2) {
throw takeObject(r1);
}
return takeObject(r0);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* @param {any} s
* @param {any} opts
* @returns {any}
*/
export function printSync(s, opts) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
wasm.printSync(retptr, addHeapObject(s), addHeapObject(opts));
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var r2 = getInt32Memory0()[retptr / 4 + 2];
if (r2) {
throw takeObject(r1);
}
return takeObject(r0);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* @param {string} s
* @param {any} opts
* @returns {any}
*/
export function transformSync(s, opts) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(
s,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
const len0 = WASM_VECTOR_LEN;
wasm.transformSync(retptr, ptr0, len0, addHeapObject(opts));
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var r2 = getInt32Memory0()[retptr / 4 + 2];
if (r2) {
throw takeObject(r1);
}
return takeObject(r0);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
const imports = {
__wbindgen_placeholder__: {
__wbg_new0_6b49a1fca8534d39: function () {
const ret = new Date();
return addHeapObject(ret);
},
__wbg_getTimezoneOffset_d7a89256f8181a06: function (arg0) {
const ret = getObject(arg0).getTimezoneOffset();
return ret;
},
__wbindgen_object_drop_ref: function (arg0) {
takeObject(arg0);
},
__wbg_getTime_7c8d3b79f51e2b87: function (arg0) {
const ret = getObject(arg0).getTime();
return ret;
},
__wbg_new_693216e109162396: function () {
const ret = new Error();
return addHeapObject(ret);
},
__wbg_stack_0ddaca5d1abfb52f: function (arg0, arg1) {
const ret = getObject(arg1).stack;
const ptr0 = passStringToWasm0(
ret,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
},
__wbg_error_09919627ac0992f5: function (arg0, arg1) {
try {
console.error(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(arg0, arg1);
}
},
__wbindgen_string_new: function (arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
},
__wbindgen_json_serialize: function (arg0, arg1) {
const obj = getObject(arg1);
const ret = JSON.stringify(obj === undefined ? null : obj);
const ptr0 = passStringToWasm0(
ret,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
const len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
},
__wbindgen_json_parse: function (arg0, arg1) {
const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
},
__wbindgen_throw: function (arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
},
},
};
const wasm_url = new URL("deno_swc_bg.wasm", import.meta.url);
/**
* Decompression callback
*
* @callback decompressCallback
* @param {Uint8Array} compressed
* @return {Uint8Array} decompressed
*/
/** Instantiates an instance of the Wasm module returning its functions.
* @remarks It is safe to call this multiple times and once successfully
* loaded it will always return a reference to the same object.
* @param {decompressCallback=} transform
*/
export async function instantiate(transform) {
return (await instantiateWithInstance(transform)).exports;
}
let instanceWithExports;
let lastLoadPromise;
/** Instantiates an instance of the Wasm module along with its exports.
* @remarks It is safe to call this multiple times and once successfully
* loaded it will always return a reference to the same object.
* @param {decompressCallback=} transform
* @returns {Promise<{
* instance: WebAssembly.Instance;
* exports: { minifySync: typeof minifySync; parseSync: typeof parseSync; printSync: typeof printSync; transformSync: typeof transformSync }
* }>}
*/
export function instantiateWithInstance(transform) {
if (instanceWithExports != null) {
return Promise.resolve(instanceWithExports);
}
if (lastLoadPromise == null) {
lastLoadPromise = (async () => {
try {
const instance = (await instantiateModule(transform)).instance;
wasm = instance.exports;
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
instanceWithExports = {
instance,
exports: { minifySync, parseSync, printSync, transformSync },
};
return instanceWithExports;
} finally {
lastLoadPromise = null;
}
})();
}
return lastLoadPromise;
}
/** Gets if the Wasm module has been instantiated. */
export function isInstantiated() {
return instanceWithExports != null;
}
async function instantiateModule(transform) {
switch (wasm_url.protocol) {
case "file:": {
if (typeof Deno !== "object") {
throw new Error("file urls are not supported in this environment");
}
if ("permissions" in Deno) {
Deno.permissions.request({ name: "read", path: wasm_url });
}
const wasmCode = await Deno.readFile(wasm_url);
return WebAssembly.instantiate(
!transform ? wasmCode : transform(wasmCode),
imports,
);
}
case "https:":
case "http:": {
if (typeof Deno === "object" && "permissions" in Deno) {
Deno.permissions.request({ name: "net", host: wasm_url.host });
}
const wasmResponse = await fetch(wasm_url);
if (transform) {
const wasmCode = new Uint8Array(await wasmResponse.arrayBuffer());
return WebAssembly.instantiate(transform(wasmCode), imports);
}
if (
wasmResponse.headers.get("content-type")?.toLowerCase().startsWith(
"application/wasm",
)
) {
return WebAssembly.instantiateStreaming(wasmResponse, imports);
} else {
return WebAssembly.instantiate(
await wasmResponse.arrayBuffer(),
imports,
);
}
}
default:
throw new Error(`Unsupported protocol: ${wasm_url.protocol}`);
}
}

Binary file not shown.

@ -0,0 +1,21 @@
import { decompress } from "https://deno.land/x/lz4@v0.1.2/mod.ts";
import type {
Config,
ParseOptions,
Program,
} from "https://esm.sh/@swc/core@1.2.212/types.d.ts";
import { instantiate } from "./lib/deno_swc.generated.js";
const { parseSync, printSync, transformSync } = await instantiate(decompress);
export function parse(source: string, opts: ParseOptions): Program {
return parseSync(source, opts);
}
export function print(program: Program, opts?: Config): { code: string } {
return printSync(program, opts || {});
}
export function transform(source: string, opts: Config): { code: string } {
return transformSync(source, opts);
}

@ -0,0 +1,30 @@
[package]
name = "deno_swc"
version = "0.0.1"
authors = ["Divy Srivastava <dj.srivastava23@gmail.com>"]
edition = "2018"
publish = false
[lib]
crate_type = ["cdylib"]
path = "lib.rs"
[dependencies]
anyhow = "1.0.42"
wee_alloc = { version = "0.4.5", optional = true }
console_error_panic_hook = "0.1.6"
once_cell = "1.3.1"
path-clean = "0.1"
serde = {version = "1", features = ["derive"]}
serde_json = "1"
swc = { git = "https://github.com/swc-project/swc", rev = "fd3501b" }
swc_ecmascript = { git = "https://github.com/swc-project/swc", rev = "fd3501b" }
swc_common = { git = "https://github.com/swc-project/swc", rev = "fd3501b" }
wasm-bindgen = {version = "0.2", features = ["serde-serialize"]}
wasm-bindgen-futures = "0.4.8"
syn = "1.0.65"
url = "2.2.2"
[features]
default = ["wee_alloc"]

@ -0,0 +1,106 @@
import { encode } from "https://deno.land/std@0.103.0/encoding/base64.ts";
import Terser from "https://esm.sh/terser@4.8.0";
import * as lz4 from "https://deno.land/x/lz4@v0.1.2/mod.ts";
const name = "deno_swc";
const encoder = new TextEncoder();
async function requires(...executables) {
const where = Deno.build.os === "windows" ? "where" : "which";
for (const executable of executables) {
const process = Deno.run({
cmd: [where, executable],
stderr: "null",
stdin: "null",
stdout: "null",
});
if (!(await process.status()).success) {
err(`Could not find required build tool ${executable}`);
}
}
}
async function run(msg, cmd) {
log(msg);
const process = Deno.run({ cmd });
if (!(await process.status()).success) {
err(`${msg} failed`);
}
}
function log(text) {
console.log(`[log] ${text}`);
}
function err(text) {
console.log(`[err] ${text}`);
return Deno.exit(1);
}
await requires("rustup", "rustc", "cargo", "wasm-bindgen");
if (!(await Deno.stat("Cargo.toml")).isFile) {
err(`the build script should be executed in the "${name}" root`);
}
await run("building wasm", ["cargo", "build", "--release", "--target", "wasm32-unknown-unknown"]);
await run(
"building using wasm-pack",
["wasm-bindgen", "target/wasm32-unknown-unknown/release/deno_swc.wasm" , "--target", "deno", "--weak-refs", "--out-dir", "pkg/"],
);
const wasm = await Deno.readFile(`pkg/${name}_bg.wasm`);
const compressed = lz4.compress(wasm);
console.log(
`compressed wasm using lz4 (reduction: ${wasm.length -
compressed.length} bytes, size: ${compressed.length} bytes)`,
);
const encoded = encode(compressed);
log(
`encoded wasm using base64, size increase: ${encoded.length -
wasm.length} bytes`,
);
log("inlining wasm in js");
const source = `import * as lz4 from "https://deno.land/x/lz4@v0.1.2/mod.ts";export const source=lz4.decompress(Uint8Array.from(atob("${encoded}"),c=>c.charCodeAt(0)));`;
let init = await Deno.readTextFile(`pkg/${name}.js`);
let lines = init.split('\n');
// We want to replace this code.
for (let i = 1; i < 4; i++) lines.splice(-i);
init = lines.join('\n');
init += `\nconst wasmModule = new WebAssembly.Module(source);\nconst wasmInstance = new WebAssembly.Instance(wasmModule, imports);\nconst wasm = wasmInstance.exports;\n`;
console.log(init)
log("minifying js");
const output = Terser.minify(`${source}\n${init}`, {
mangle: { module: true },
output: {
preamble: "//deno-fmt-ignore-file",
},
});
if (output.error) {
err(`encountered error when minifying: ${output.error}`);
}
const reduction = new Blob([(`${source}\n${init}`)]).size -
new Blob([output.code]).size;
log(`minified js, size reduction: ${reduction} bytes`);
log(`writing output to file ("wasm.js")`);
await Deno.writeFile("wasm.js", encoder.encode(output.code));
const outputFile = await Deno.stat("wasm.js");
log(
`output file ("wasm.js"), final size is: ${outputFile.size} bytes`,
);

@ -0,0 +1,184 @@
// From https://github.com/swc-project/swc/blob/main/crates/binding_core_wasm/src/lib.rs
use anyhow::{Context, Error};
use once_cell::sync::Lazy;
use std::sync::Arc;
use swc::{
config::{
ErrorFormat, JsMinifyOptions, Options, ParseOptions, SourceMapsConfig,
},
try_with_handler, Compiler,
};
use swc_common::{comments::Comments, FileName, FilePathMapping, SourceMap};
use swc_ecmascript::ast::{EsVersion, Program};
use wasm_bindgen::prelude::*;
fn convert_err(err: Error, error_format: ErrorFormat) -> JsValue {
error_format.format(&err).into()
}
#[wasm_bindgen(js_name = "minifySync")]
pub fn minify_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
console_error_panic_hook::set_once();
let c = compiler();
try_with_handler(
c.cm.clone(),
swc::HandlerOpts {
..Default::default()
},
|handler| {
c.run(|| {
let opts: JsMinifyOptions =
opts.into_serde().context("failed to parse options")?;
let fm = c.cm.new_source_file(FileName::Anon, s.into());
let program = c
.minify(fm, handler, &opts)
.context("failed to minify file")?;
JsValue::from_serde(&program).context("failed to serialize json")
})
},
)
.map_err(|e| convert_err(e, ErrorFormat::Normal))
}
#[wasm_bindgen(js_name = "parseSync")]
pub fn parse_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
console_error_panic_hook::set_once();
let c = compiler();
try_with_handler(
c.cm.clone(),
swc::HandlerOpts {
..Default::default()
},
|handler| {
c.run(|| {
let opts: ParseOptions =
opts.into_serde().context("failed to parse options")?;
let fm = c.cm.new_source_file(FileName::Anon, s.into());
let cmts = c.comments().clone();
let comments = if opts.comments {
Some(&cmts as &dyn Comments)
} else {
None
};
let program = c
.parse_js(
fm,
handler,
opts.target,
opts.syntax,
opts.is_module,
comments,
)
.context("failed to parse code")?;
JsValue::from_serde(&program).context("failed to serialize json")
})
},
)
.map_err(|e| convert_err(e, ErrorFormat::Normal))
}
#[wasm_bindgen(js_name = "printSync")]
pub fn print_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
console_error_panic_hook::set_once();
let c = compiler();
try_with_handler(
c.cm.clone(),
swc::HandlerOpts {
..Default::default()
},
|_handler| {
c.run(|| {
let opts: Options =
opts.into_serde().context("failed to parse options")?;
let program: Program =
s.into_serde().context("failed to deserialize program")?;
let s = c
.print(
&program,
None,
None,
true,
opts.codegen_target().unwrap_or(EsVersion::Es2020),
opts
.source_maps
.clone()
.unwrap_or(SourceMapsConfig::Bool(false)),
&Default::default(),
None,
opts.config.minify.into(),
None,
opts.config.emit_source_map_columns.into_bool(),
false,
)
.context("failed to print code")?;
JsValue::from_serde(&s).context("failed to serialize json")
})
},
)
.map_err(|e| convert_err(e, ErrorFormat::Normal))
}
#[wasm_bindgen(js_name = "transformSync")]
pub fn transform_sync(s: &str, opts: JsValue) -> Result<JsValue, JsValue> {
console_error_panic_hook::set_once();
let c = compiler();
let opts: Options = opts
.into_serde()
.context("failed to parse options")
.map_err(|e| convert_err(e, ErrorFormat::Normal))?;
let error_format = opts.experimental.error_format.unwrap_or_default();
try_with_handler(
c.cm.clone(),
swc::HandlerOpts {
..Default::default()
},
|handler| {
c.run(|| {
let fm = c.cm.new_source_file(
if opts.filename.is_empty() {
FileName::Anon
} else {
FileName::Real(opts.filename.clone().into())
},
s.into(),
);
let out = c
.process_js_file(fm, handler, &opts)
.context("failed to process input file")?;
JsValue::from_serde(&out).context("failed to serialize json")
})
},
)
.map_err(|e| convert_err(e, error_format))
}
/// Get global sourcemap
fn compiler() -> Arc<Compiler> {
static C: Lazy<Arc<Compiler>> = Lazy::new(|| {
let cm = Arc::new(SourceMap::new(FilePathMapping::empty()));
Arc::new(Compiler::new(cm))
});
C.clone()
}

@ -0,0 +1 @@
export { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts";

@ -0,0 +1,49 @@
import { parse } from "../mod.ts";
import { assertEquals } from "./deps.ts";
Deno.test("parse (no error)", () => {
const result = parse("const x: number = 2;", {
"syntax": "typescript",
});
assertEquals(result, {
type: "Module",
body: [
{
declarations: [
{
definite: false,
id: {
optional: false,
span: { ctxt: 0, end: 8, start: 7 },
type: "Identifier",
typeAnnotation: {
span: { ctxt: 0, end: 16, start: 8 },
type: "TsTypeAnnotation",
typeAnnotation: {
kind: "number",
span: { ctxt: 0, end: 16, start: 10 },
type: "TsKeywordType",
},
},
value: "x",
},
init: {
raw: "2",
span: { ctxt: 0, end: 20, start: 19 },
type: "NumericLiteral",
value: 2,
},
span: { ctxt: 0, end: 20, start: 7 },
type: "VariableDeclarator",
},
],
declare: false,
kind: "const",
span: { ctxt: 0, end: 21, start: 1 },
type: "VariableDeclaration",
},
],
interpreter: null,
span: { ctxt: 0, end: 21, start: 1 },
});
});

@ -0,0 +1,32 @@
import { print } from "../mod.ts";
import { assertEquals } from "./deps.ts";
Deno.test("print (no error)", () => {
const result = print({
"type": "Module",
"span": { "start": 21, "end": 33, "ctxt": 0 },
"body": [{
"type": "ClassDeclaration",
"identifier": {
"type": "Identifier",
"span": { "start": 27, "end": 28, "ctxt": 0 },
"value": "X",
"optional": false,
},
"declare": false,
"span": { "start": 21, "end": 32, "ctxt": 0 },
"decorators": [],
"body": [],
"superClass": null,
"isAbstract": false,
"typeParams": null,
"superTypeParams": null,
"implements": [],
}, {
"type": "EmptyStatement",
"span": { "start": 32, "end": 33, "ctxt": 0 },
}],
"interpreter": null,
}, {});
assertEquals(result.code.trim(), "class X {\n}\n;");
});

@ -0,0 +1,16 @@
import { transform } from "../mod.ts";
import { assertEquals } from "./deps.ts";
Deno.test("transform (no error)", () => {
const result = transform("const x: number = 2; console.log(x);", {
// deno-lint-ignore ban-ts-comment
// @ts-ignore
"jsc": {
"target": "es2016",
"parser": {
"syntax": "typescript",
},
},
});
assertEquals(result.code.trim(), "const x = 2;\nconsole.log(x);");
});

@ -0,0 +1 @@
export const version = "0.2.1";

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<flowgorithm fileversion="3.0">
<attributes>
<attribute name="name" value=""/>
<attribute name="authors" value="skybl"/>
<attribute name="about" value=""/>
<attribute name="saved" value="2022-10-11 01:08:26 AM"/>
<attribute name="created" value="c2t5Ymw7UE9ORDsyMDIyLTEwLTExOzAxOjA1OjQ0IEFNOzIxMDY="/>
<attribute name="edited" value="c2t5Ymw7UE9ORDsyMDIyLTEwLTExOzAxOjA4OjI2IEFNOzQ7MjIyMA=="/>
</attributes>
<function name="Main" type="None" variable="">
<parameters/>
<body/>
</function>
</flowgorithm>

@ -0,0 +1,67 @@
<?xml version="1.0"?>
<flowgorithm fileversion="3.0">
<attributes>
<attribute name="name" value="benchtest.ts"/>
<attribute name="authors" value="skybl, ezfprg"/>
<attribute name="about" value="test program for transpiler benchmarks"/>
<attribute name="saved" value="2022-10-19 01:17:24 AM"/>
<attribute name="created" value="c2t5Ymw7cG9uZDsyMDIyLTEwLTE5OzAxOjE3OjI0IEFN"/>
<attribute name="edited" value="c2t5Ymw7cG9uZDsyMDIyLTEwLTE5OzAxOjE3OjI0IEFNOzU7Mjk5MQ=="/>
</attributes>
<function name="Main" type="None" variable="">
<parameters/>
<body>
<declare name="a, b" type="Integer" array="False" size=""/>
<assign variable="a" expression="1"/>
<input variable="b"/>
<output expression="a + b" newline="True"/>
<declare name="c" type="Real" array="False" size=""/>
<assign variable="c" expression="20 / ((5 + a) ^ (a + b))"/>
<output expression="c" newline="True"/>
<declare name="f" type="Integer" array="False" size=""/>
<declare name="e" type="Real" array="False" size=""/>
<assign variable="e" expression="1.4"/>
<assign variable="f" expression="2"/>
<declare name="g" type="Real" array="False" size=""/>
<assign variable="g" expression="e / f"/>
<declare name="h" type="Boolean" array="False" size=""/>
<assign variable="h" expression="false"/>
<output expression="&quot; &quot; &amp; e &amp; &quot; / &quot; &amp; f &amp; &quot; = &quot; &amp; g &amp; &quot;, h = &quot; &amp; h" newline="True"/>
<declare name="i" type="String" array="False" size=""/>
<assign variable="i" expression="2 &amp; &quot; hello&quot;"/>
<declare name="j" type="String" array="False" size=""/>
<assign variable="j" expression="i &amp; &quot; world&quot;"/>
<output expression="j" newline="True"/>
<if expression="a &lt; b">
<then>
<output expression="&quot;a is less than b&quot;" newline="True"/>
</then>
<else>
<output expression="&quot;a is more than b&quot;" newline="True"/>
</else>
</if>
<if expression="true">
<then>
<output expression="&quot;single arm&quot;" newline="True"/>
</then>
<else>
</else>
</if>
<declare name="k" type="Integer" array="False" size=""/>
<assign variable="k" expression="0"/>
<while expression="k &lt; 10">
<output expression="k" newline="True"/>
<assign variable="k" expression="k + 1"/>
</while>
<declare name="l" type="Integer" array="False" size=""/>
<assign variable="l" expression="0"/>
<for variable="l" start="0" end="10" direction="inc" step="1">
<output expression="l" newline="True"/>
</for>
<for variable="l" start="20" end="10" direction="dec" step="2">
<output expression="l" newline="True"/>
</for>
</body>
</function>
</flowgorithm>

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<flowgorithm fileversion="3.0">
<attributes>
<attribute name="name" value=""/>
<attribute name="authors" value="skybl"/>
<attribute name="about" value=""/>
<attribute name="saved" value="2022-10-07 06:41:15 PM"/>
<attribute name="created" value="c2t5Ymw7UE9ORDsyMDIyLTEwLTA3OzA2OjQwOjQ4IFBNOzIxMzQ="/>
<attribute name="edited" value="c2t5Ymw7UE9ORDsyMDIyLTEwLTA3OzA2OjQxOjE1IFBNOzE7MjIzNw=="/>
</attributes>
<function name="Main" type="None" variable="">
<parameters/>
<body>
<output expression="10 / 2" newline="True"/>
</body>
</function>
</flowgorithm>

@ -0,0 +1,45 @@
<?xml version="1.0"?>
<flowgorithm fileversion="3.0">
<attributes>
<attribute name="name" value="Test Name"/>
<attribute name="authors" value="skybl"/>
<attribute name="about" value="Test Description"/>
<attribute name="saved" value="2022-10-07 01:22:57 AM"/>
<attribute name="created" value="c2t5Ymw7REVTS1RPUC03VEFWOEpBOzIwMjItMTAtMDY7MDE6NDU6MDMgQU07Mjg3Mg=="/>
<attribute name="edited" value="c2t5Ymw7REVTS1RPUC03VEFWOEpBOzIwMjItMTAtMDc7MDE6MjI6NTcgQU07NTsyOTg5"/>
</attributes>
<function name="Main" type="None" variable="">
<parameters/>
<body>
<comment text="this is a comment"/>
<declare name="a, b" type="Integer" array="False" size=""/>
<assign variable="a" expression="1"/>
<input variable="b"/>
<output expression="a + b" newline="True"/>
<if expression="a &lt; b">
<then>
<output expression="&quot;good&quot;" newline="True"/>
</then>
<else>
<output expression="&quot;bad&quot;" newline="True"/>
</else>
</if>
<while expression="a &lt; 5">
<output expression="&quot;a is now &quot; &amp; a" newline="True"/>
<assign variable="a" expression="a + 1"/>
</while>
<for variable="b" start="0" end="10" direction="inc" step="1">
<output expression="&quot;b is now &quot; &amp; b" newline="True"/>
</for>
</body>
</function>
<function name="foo" type="Integer" variable="b">
<parameters>
<parameter name="a" type="Integer" array="False"/>
</parameters>
<body>
<declare name="b" type="Integer" array="False" size=""/>
<assign variable="b" expression="a"/>
</body>
</function>
</flowgorithm>

@ -0,0 +1,44 @@
<?xml version="1.0"?>
<flowgorithm fileversion="3.0">
<attributes>
<attribute name="name" value="Test Name"/>
<attribute name="authors" value="skybl"/>
<attribute name="about" value="Test Description"/>
<attribute name="saved" value="2022-10-07 12:29:19 AM"/>
<attribute name="created" value="c2t5Ymw7REVTS1RPUC03VEFWOEpBOzIwMjItMTAtMDY7MDE6NDU6MDMgQU07Mjg3Mg=="/>
<attribute name="edited" value="c2t5Ymw7REVTS1RPUC03VEFWOEpBOzIwMjItMTAtMDc7MTI6Mjk6MTkgQU07NDsyOTk1"/>
</attributes>
<function name="Main" type="None" variable="">
<parameters/>
<body>
<declare name="a, b" type="Integer" array="False" size=""/>
<assign variable="a" expression="1"/>
<input variable="b"/>
<output expression="a + b" newline="True"/>
<if expression="a &lt; b">
<then>
<output expression="&quot;good&quot;" newline="True"/>
</then>
<else>
<output expression="&quot;bad&quot;" newline="True"/>
</else>
</if>
<while expression="a &lt; 5">
<output expression="&quot;a is now &quot; &amp; a" newline="True"/>
<assign variable="a" expression="a + 1"/>
</while>
<for variable="b" start="0" end="10" direction="inc" step="1">
<output expression="&quot;b is now &quot; &amp; b" newline="True"/>
</for>
</body>
</function>
<function name="foo" type="Integer" variable="b">
<parameters>
<parameter name="a" type="Integer" array="False"/>
</parameters>
<body>
<declare name="b" type="Integer" array="False" size=""/>
<assign variable="b" expression="a"/>
</body>
</function>
</flowgorithm>

@ -0,0 +1,44 @@
<?xml version="1.0"?>
<flowgorithm fileversion="3.0">
<attributes>
<attribute name="name" value="Test Name"/>
<attribute name="authors" value="skybl"/>
<attribute name="about" value="Test Description"/>
<attribute name="saved" value="2022-10-07 12:17:19 AM"/>
<attribute name="created" value="c2t5Ymw7REVTS1RPUC03VEFWOEpBOzIwMjItMTAtMDY7MDE6NDU6MDMgQU07Mjg3Mg=="/>
<attribute name="edited" value="c2t5Ymw7REVTS1RPUC03VEFWOEpBOzIwMjItMTAtMDc7MTI6MTc6MTkgQU07MjsyOTkw"/>
</attributes>
<function name="Main" type="None" variable="">
<parameters/>
<body>
<declare name="a, b" type="Integer" array="False" size=""/>
<assign variable="a" expression="1"/>
<input variable="b"/>
<output expression="a + b" newline="True"/>
<if expression="a &lt; b">
<then>
<output expression="&quot;good&quot;" newline="True"/>
</then>
<else>
<output expression="&quot;bad&quot;" newline="True"/>
</else>
</if>
<while expression="a &lt; 5">
<output expression="&quot;a is now &quot; &amp; a" newline="True"/>
<assign variable="a" expression="a + 1"/>
</while>
<for variable="b" start="0" end="10" direction="inc" step="1">
<output expression="&quot;b is now &quot; &amp; b" newline="True"/>
</for>
</body>
</function>
<function name="foo" type="Integer" variable="b">
<parameters>
<parameter name="a" type="Integer" array="False"/>
</parameters>
<body>
<declare name="b" type="Integer" array="False" size=""/>
<assign variable="b" expression="a"/>
</body>
</function>
</flowgorithm>

@ -0,0 +1,45 @@
<?xml version="1.0"?>
<flowgorithm fileversion="3.0">
<attributes>
<attribute name="name" value="Test Name"/>
<attribute name="authors" value="skybl"/>
<attribute name="about" value="Test Description"/>
<attribute name="saved" value="2022-10-06 01:53:41 AM"/>
<attribute name="created" value="c2t5Ymw7REVTS1RPUC03VEFWOEpBOzIwMjItMTAtMDY7MDE6NDU6MDMgQU07Mjg3Mg=="/>
<attribute name="edited" value="c2t5Ymw7REVTS1RPUC03VEFWOEpBOzIwMjItMTAtMDY7MDE6NTM6NDEgQU07MTsyOTgx"/>
</attributes>
<function name="Main" type="None" variable="">
<parameters/>
<body>
<declare name="a, b" type="Integer" array="False" size=""/>
<assign variable="a" expression="1"/>
<input variable="b"/>
<output expression="a + b" newline="True"/>
<if expression="a &lt; b">
<then>
<output expression="&quot;good&quot;" newline="True"/>
</then>
<else>
<output expression="&quot;bad&quot;" newline="True"/>
</else>
</if>
<while expression="a &lt; 5">
<output expression="&quot;a is now &quot; &amp; a" newline="True"/>
<assign variable="a" expression="a + 1"/>
</while>
<for variable="b" start="0" end="10" direction="inc" step="1">
<output expression="&quot;b is now &quot; &amp; b" newline="True"/>
</for>
<call expression="foo(a)"/>
</body>
</function>
<function name="foo" type="Integer" variable="b">
<parameters>
<parameter name="a" type="Integer" array="False"/>
</parameters>
<body>
<declare name="b" type="Integer" array="False" size=""/>
<assign variable="b" expression="a"/>
</body>
</function>
</flowgorithm>

@ -0,0 +1,22 @@
meta("username", "skybl");
meta("hostname", "pond");
meta("editVersion", "4");
meta("editMysteryNumber", "2982");
attr("name", "DESIGN/CREATE A PROGRAM USING THE SEQUENCE STRUCTURE AND NAMED CONSTANTS");
attr("authors", "Jacob Teatro");
attr("about", "Current Semester: 1\nCourse Section: 0072\nBlackboard Username: jtteatro");
let userBal: int, userOverdrafts: int;
let OVERDRAFTfee = 2, MINfee = 0.01;
print("Bank Calculator v0.0.1");
print("Please enter your current balance: ");
input(userBal);
print("Please enter the number of overdrafts you have had this month: ");
input(userOverdrafts);
let totalMinFee = userBal * MINfee;
let totalOverdraftFee = userOverdrafts * OVERDRAFTfee;
print("Your total fee is: " + (totalMinFee + totalOverdraftFee));
print("Thank you for using Bank Calculator v0.0.1");

@ -0,0 +1,7 @@
meta("username", "skybl");
meta("hostname", "pond");
meta("editVersion", "4");
meta("editMysteryNumber", "2982");
attr("name", "DESIGN/CREATE A PROGRAM USING THE SEQUENCE STRUCTURE AND NAMED CONSTANTS");
attr("authors", "Jacob Teatro");
attr("about", "Current Semester: 1\nCourse Section: 0072\nBlackboard Username: jtteatro");

@ -0,0 +1,7 @@
let y = 0;
while (y < 2) {
let i = 0;
print(i);
}
print(i);

@ -0,0 +1,72 @@
benchmark time (avg) (min … max) p75 p99 p995
----------------------------------------------------------------------------------- -----------------------------
transform 53.55 µs/iter (40.47 µs … 3.71 ms) 49.03 µs 221.54 µs 332.98 µs
transformBlock 11.67 µs/iter (9.46 µs … 869.33 µs) 10.8 µs 33.09 µs 36.68 µs
transformVariableDecl 612.18 ns/iter (531.9 ns … 960.45 ns) 615.87 ns 960.45 ns 960.45 ns
transformExpr :: `20 / ((5 + a) ^ (a + b))` 509.35 ns/iter (466.79 ns … 773.72 ns) 515.96 ns 756.48 ns 773.72 ns
transformBlockCallExpr :: `print(a + b)` 451 ns/iter (403.7 ns … 772.28 ns) 462.06 ns 700.4 ns 772.28 ns
[+] check if string contains escapable characters before
replacing
transform 36.27 µs/iter (29.66 µs … 1.42 ms) 33.1 µs 104.82 µs 247.19 µs
transformBlock 5.72 µs/iter (5.63 µs … 6.17 µs) 5.71 µs 6.17 µs 6.17 µs
transformVariableDecl 312.97 ns/iter (289.81 ns … 432.07 ns) 319.6 ns 389.74 ns 432.07 ns
transformExpr :: `20 / ((5 + a) ^ (a + b))` 453.37 ns/iter (429.78 ns … 523.84 ns) 459.99 ns 495.72 ns 523.84 ns
transformBlockCallExpr :: `print(a + b)` 194.72 ns/iter (184.87 ns … 239.98 ns) 198.91 ns 227.68 ns 239.71 ns
[-] move idents into TransformState, had to change
benchmarks slightly
transform 37.73 µs/iter (29.89 µs … 1.74 ms) 34.41 µs 147.62 µs 244.81 µs
transformBlock 6.17 µs/iter (5.36 µs … 660.08 µs) 6.28 µs 8.71 µs 11.31 µs
transformVariableDecl 329.67 ns/iter (300.06 ns … 503.69 ns) 332.52 ns 492.28 ns 503.69 ns
transformExpr :: `20 / ((5 + a) ^ (a + b))` 473.44 ns/iter (431.62 ns … 637.82 ns) 479.82 ns 582.41 ns 637.82 ns
transformBlockCallExpr :: `print(a + b)` 197.26 ns/iter (179.65 ns … 319.4 ns) 202.18 ns 241.29 ns 244.67 ns
[-] switch back to using strings as types
transform 37.13 µs/iter (31.27 µs … 2.54 ms) 33.76 µs 112.42 µs 226.41 µs
transformBlock 7.41 µs/iter (6.83 µs … 576.56 µs) 7.19 µs 10.87 µs 21.64 µs
transformVariableDecl 523.43 ns/iter (503.78 ns … 672.13 ns) 525.52 ns 628.98 ns 672.13 ns
transformExpr :: `20 / ((5 + a) ^ (a + b))` 450.4 ns/iter (430.57 ns … 542.85 ns) 455.2 ns 514.45 ns 542.85 ns
transformBlockCallExpr :: `print(a + b)` 189.01 ns/iter (180.33 ns … 286.61 ns) 192.17 ns 219.97 ns 248.98 ns
[+] iterate through typeNames instead of Object.entries()
in transformVariableDecl()
transform 36.18 µs/iter (29.69 µs … 3.26 ms) 32.74 µs 94.19 µs 242.39 µs
transformBlock 5.93 µs/iter (5.44 µs … 832.39 µs) 5.85 µs 8.3 µs 9.77 µs
transformVariableDecl 333.23 ns/iter (316.84 ns … 426.59 ns) 336.35 ns 412.75 ns 426.59 ns
transformExpr :: `20 / ((5 + a) ^ (a + b))` 445.46 ns/iter (426.16 ns … 512.61 ns) 450.72 ns 501.26 ns 512.61 ns
transformBlockCallExpr :: `print(a + b)` 191.11 ns/iter (183.08 ns … 240.99 ns) 194.36 ns 216 ns 223.62 ns
[+] made indent a string in state, add indent string
before statement instead of passing it
transform 34.06 µs/iter (28.41 µs … 1.44 ms) 30.99 µs 110.45 µs 222.66 µs
transformBlock 5.62 µs/iter (5.01 µs … 1.08 ms) 5.58 µs 6.8 µs 8.48 µs
transformVariableDecl 306.54 ns/iter (291.54 ns … 392.86 ns) 309.48 ns 344.42 ns 392.86 ns
transformExpr :: `20 / ((5 + a) ^ (a + b))` 457.71 ns/iter (431.29 ns … 581.16 ns) 461.86 ns 526.14 ns 581.16 ns
transformBlockCallExpr :: `print(a + b)` 184.39 ns/iter (172.76 ns … 208.97 ns) 188.07 ns 200.83 ns 201.95 ns
[-] dammit turns out that the regex escape thing wasn't
even matching
transform 42.14 µs/iter (35.64 µs … 1.48 ms) 38.14 µs 148.44 µs 270.71 µs
transformBlock 7.67 µs/iter (7.17 µs … 533.04 µs) 7.57 µs 9.11 µs 10.88 µs
transformVariableDecl 305.22 ns/iter (291.67 ns … 367.68 ns) 308.55 ns 348.5 ns 367.68 ns
transformExpr :: `20 / ((5 + a) ^ (a + b))` 442.14 ns/iter (425.39 ns … 481.67 ns) 446.63 ns 479.34 ns 481.67 ns
transformBlockCallExpr :: `print(a + b)` 173.47 ns/iter (166.02 ns … 210.7 ns) 177.27 ns 190.81 ns 198.78 ns
[-] added if statement support, longer benchtest.ts
transform 50.17 µs/iter (41.6 µs … 2.42 ms) 44.86 µs 200.54 µs 320.54 µs
transformBlock 10.34 µs/iter (9.74 µs … 548.07 µs) 10.14 µs 14.01 µs 20.94 µs
transformVariableDecl 302.8 ns/iter (290.01 ns … 361.69 ns) 305.75 ns 338.17 ns 361.69 ns
transformExpr :: `20 / ((5 + a) ^ (a + b))` 468.38 ns/iter (451.07 ns … 499.1 ns) 472.42 ns 497.34 ns 499.1 ns
transformBlockCallExpr :: `print(a + b)` 180.34 ns/iter (173.4 ns … 205.18 ns) 183.79 ns 196.68 ns 204.69 ns
transformIfStmt 1.56 µs/iter (1.53 µs … 1.67 µs) 1.58 µs 1.67 µs 1.67 µs
? try making each type declaration group a string and directly append to it?

@ -0,0 +1,9 @@
// new
function a(arg: unknown): asserts arg is string;
// my impl
function b<T>(arg: T): T extends string ? void : never;
declare let v: string | number;
a(v);
let x: string = v;

@ -0,0 +1,28 @@
const lua: string[] = [
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine"
];
Deno.bench("randomgen", () => {
lua[Math.round(Math.random() * 10)];
});
Deno.bench("includes", () => {
const input = lua[Math.round(Math.random() * 10)];
["zero", "five", "six", "nine"].includes(input);
});
Deno.bench("logic", () => {
const input = lua[Math.round(Math.random() * 10)];
input === "zero" || input === "five" || input === "six" || input === "nine";
});

@ -0,0 +1,82 @@
import { Type as NType } from "../src/type_utils.ts";
const sTypes = ["int", "string", "bool"];
let i = 0;
setInterval(() => {
const r1 = Math.floor(Math.random() * 10 / 3);
const r2 = Math.floor(Math.random() * 10 / 3);
console.log(`run ${i} comparing ${sTypes[r1]} and ${sTypes[r2]}`);
const _a: NType = r1;
const _b: NType = r2
Deno.bench("NType (boolop)", () => {
_a === _b || _a + _b !== 8;
});
Deno.bench("NType (switch)", () => {
let c: boolean;
if (_a === _b) {
c = true;
} else {
switch (_a + _b) {
case 4:
case 10: c = true; break
case 8: c = false;
}
}
});
const a = sTypes[r1];
const b = sTypes[r2];
Deno.bench("SType (if)", () => {
let c: boolean;
if (a === b) {
c = true;
} else {
if ((a === "int" && b === "string") || (a === "string" && b === "int")) {
c = true;
} else if ((a === "int" && b === "boolean") || (a === "boolean" && b === "int")) {
c = false;
} else if ((a === "boolean" && b === "string") || (a === "string" && b === "boolean")) {
c = true;
}
}
});
Deno.bench("SType (boolop)", () => {
let c: boolean;
if (a === b) {
c = true;
} else {
c = (
((a === "int" && b === "string") || (a === "string" && b === "int"))
|| ((a === "boolean" && b === "string") || (a === "string" && b === "boolean"))
|| !((a === "int" && b === "boolean") || (a === "boolean" || b === "int"))
);
}
});
Deno.bench("SType (if+includes)", () => {
let c: boolean;
if (a === b) {
c = true;
} else {
c = (
([a, b].includes("int") && [a, b].includes("string"))
|| ([a, b].includes("boolean") && [a, b].includes("string"))
|| !([a, b].includes("int") && [a, b].includes("boolean"))
);
}
});
i++;
}, 5000);

@ -0,0 +1,54 @@
const lua: string[] = [
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine"
];
Deno.bench("randomgen", () => {
lua[Math.round(Math.random() * 10)];
});
Deno.bench("switch", () => {
const input = lua[Math.round(Math.random() * 10)];
let a;
switch (input) {
case "zero": a = 0; break;
case "one": a = 1; break;
case "two": a = 2; break;
case "three": a = 3; break;
case "four": a = 4; break;
case "five": a = 5; break;
case "six": a = 6; break;
case "seven": a = 7; break;
case "eight": a = 8; break;
case "nine": a = 9; break;
}
});
Deno.bench("lookup table", () => {
const input = lua[Math.round(Math.random() * 10)];
const lut: Record<string, number> = {
zero: 0,
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
six: 6,
seven: 7,
eight: 8,
nine: 9
};
const a = lut[input];
});

@ -0,0 +1,92 @@
import { Type, validKeys } from "./type_utils.ts";
export class InvalidTypeAnnotation extends SyntaxError {
constructor (value: string, position: number) {
super(`Could not determine type from \`<: ${value}>\`. (at ${position})`);
}
}
export class MissingType extends SyntaxError {
constructor (variable: string, position: number) {
super(`A type was not given when declaring \`${variable}\`. (at ${position})`);
}
}
export class InvalidType extends TypeError {
constructor (expected: Type | string, got: Type | string, position: number) {
super(`Expected \`${expected}\`, got \`${got}\`. (at ${position})`);
}
}
export class InvalidSyntaxType extends SyntaxError {
constructor (expected: string, got: string, position: number) {
super(`Expected \`<${expected}>\`, got \`<${got}>\`. (at ${position})`);
}
}
export class InvalidExpression extends SyntaxError {
constructor (type: string, position: number) {
super(`Unknown or unsupported expression or literal type \`${type}\`. (at ${position})`);
}
}
export class InvalidBinaryOp extends TypeError {
constructor (a: Type, b: Type, op: string, position: number) {
super(`Cannot perform operation \`${op}\` on \`${a}\` and \`${b}\`. (at ${position})`);
}
}
export class UndefinedReference extends ReferenceError {
constructor (name: string, position: number) {
super(`\`${name}\` is not defined, or is a builtin that cannot be used in an expression. (at ${position})`);
}
}
export class InvalidAssignmentTarget extends SyntaxError {
constructor (position: number) {
super(`Left-hand side of assignment must be an \`Identifier\`. Destructuring not supported. (at ${position})`);
}
}
export class InvalidArgumentCount extends SyntaxError {
constructor (name: string, count: number, expected: number, position: number) {
super(`\`${name}\` expects ${expected} arguments, got ${count}. (at ${position})`);
}
}
export class InvalidAttrOrMeta extends Error {
constructor (key: string, group: "attr" | "meta", position: number) {
super(`Expected \`${validKeys[group].join(", ")}\`, got \`${key}\`. (at ${position})`);
}
}
export class AlreadyDefined extends ReferenceError {
constructor (decl: string, position: number) {
super(`\`${decl}\` is already defined. (at ${position})`);
}
}
export class ForMissingEnd extends SyntaxError {
constructor (position: number) {
super(`Missing direction and end in for statement (at ${position})\nSee DOCS.md for more info on for statements.`);
}
}
export class ForMissingInit extends SyntaxError {
constructor (position: number) {
super(`Missing init in for statement (at ${position})\nSee DOCS.md for more info on for statements.`);
}
}
export class ForVariableMismatch extends SyntaxError {
constructor (expected: string, got: string, position: number) {
super(`Expected \`${expected}\`, got \`${got}\` (at ${position})\nSee DOCS.md for more info on for statements.`);
}
}
export class ForInvalidOperator extends InvalidSyntaxType {
constructor (op: string, position: number) {
super("+= | -=", op, position);
this.message = `Invalid operator in for statement: ${this.message}\nSee DOCS.md for more info on for statements.`;
}
}

@ -0,0 +1,36 @@
import { parse } from "./swc.ts";
import { transform } from "./transformers.ts";
import { join, basename, dirname } from "https://deno.land/std/path/mod.ts";
const inPath = Deno.args[0];
const input = Deno.readTextFileSync(inPath);
const ast = parse(input, {
syntax: "typescript",
comments: true,
target: "es2020",
});
const template = Deno.readTextFileSync("./assets/template.fprg");
const output = transform(input, ast, {
indent: 3,
showSourceStatements: false
}, template);
if (Deno.args.length > 1) {
Deno.writeTextFileSync(Deno.args[1], output);
} else {
const inPathBasename = basename(inPath);
const dot = inPathBasename.lastIndexOf(".");
const outPath = join(
dirname(inPath),
dot > -1
? inPathBasename.slice(0, dot) + ".fprg"
: inPathBasename + ".fprg"
);
console.log(outPath);
Deno.writeTextFileSync(outPath, output);
}
console.log(output);

@ -0,0 +1,78 @@
import { Type } from "./type_utils.ts";
import { escapeXML, capitalize } from "./utils.ts";
const typeToFPRGType: Record<Type, string> = {
int: "Integer",
float: "Real",
string: "String",
boolean: "Boolean",
}
export function declare (name: string, type: Type): string {
return `<declare `
+ `name="${name}" `
+ `type="${typeToFPRGType[type]}" `
+ `array="False" `
+ `size=""/>\n`;
}
export function assign (variable: string, expr: string): string {
return `<assign `
+ `variable="${variable}" `
+ `expression="${escapeXML(expr)}"/>\n`;
}
export function output (expr: string, newline: boolean): string {
return `<output `
+ `expression="${escapeXML(expr)}" `
+ `newline="${capitalize(newline.toString())}"/>\n`;
}
export function input (variable: string): string {
return `<input `
+ `variable="${variable}"/>\n`;
}
export function comment (text: string): string {
return `<comment text="${escapeXML(text)}"/>\n`;
}
export function _if (
indent: string,
expr: string,
arm1: string,
arm2?: string
): string {
const exIndent = indent + " ";
return `${indent}<if expression="${escapeXML(expr)}">\n`
+ `${exIndent}<then>\n${arm1}${exIndent}</then>\n`
+ `${exIndent}<else>\n${arm2 ?? ""}${exIndent}</else>\n`
+ `${indent}</if>\n`;
}
export function _while (
indent: string,
expr: string,
arm: string
): string {
return `${indent}<while expression="${escapeXML(expr)}">\n`
+ `${arm}${indent}</while>\n`;
}
export function _for (
indent: string,
variable: string,
start: number,
end: number,
direction: "inc" | "dec",
step: number,
body: string
): string {
return `${indent}<for `
+ `variable="${variable}" `
+ `start="${start}" `
+ `end="${end}" `
+ `direction="${direction}" `
+ `step="${step}">\n`
+ `${body}${indent}</for>\n`;
}

@ -0,0 +1,32 @@
import {instantiate} from "../lib/deno_swc/lib/deno_swc.generated.js";
import {decompress} from "https://deno.land/x/lz4@v0.1.2/mod.ts";
import type {
Config,
JsMinifyOptions,
ParseOptions,
Program,
} from "https://esm.sh/@swc/core@1.2.212/types.d.ts";
const {
parseSync,
printSync,
transformSync,
minifySync,
} = await instantiate(decompress);
export function parse(source: string, opts: ParseOptions): Program{
return parseSync(source, opts);
}
export function print(program: Program, opts?: Config): string{
return printSync(program, opts ?? {}).code;
}
return transformSync(source, opts).code;
}
export function minify(source: string, opts?: JsMinifyOptions): string{
return minifySync(source, opts ?? {}).code;
}
export * from "https://esm.sh/@swc/core@1.2.212/types.d.ts";

@ -0,0 +1,738 @@
import { format } from "https://deno.land/std@0.146.0/datetime/mod.ts";
import { Type } from "./type_utils.ts";
import * as TypeUtils from "./type_utils.ts";
import { capitalize, escapeXML } from "./utils.ts";
import * as SWC from "./swc.ts";
import * as Errors from "./errors.ts";
import * as Statements from "./statements.ts";
import { BlockStatement, NumericLiteral, Statement } from "./swc.ts";
export interface Ident { type: Type; defined: boolean; }
export interface Assignment { name: string; value: string }
export type Idents = Map<string, Ident>;
export type ProgramAttributes = Partial<{
[index: string]: string;
name: string;
authors: string;
about: string;
saved: string;
created: string;
edited: string;
}>
export type ProgramMetadata = Partial<{
[index: string]: string | number;
username: string;
hostname: string;
editversion: number;
editMysteryNumber: number;
}>;
export interface TransformOptions {
[index: string]: number | boolean | ProgramAttributes | ProgramMetadata;
showSourceStatements: boolean;
attr: ProgramAttributes;
meta: ProgramMetadata;
};
export interface TransformState {
input: string;
idents: Map<string, Ident>;
indent: string;
opts: TransformOptions;
}
export function transform (
input: string,
ast: SWC.Module | SWC.Program,
options: Partial<TransformOptions>,
customTemplate?: string
): string {
const state: TransformState = {
input: input,
indent: " ".repeat(3),
idents: new Map(),
opts: {
showSourceStatements: options.showSourceStatements ?? false,
attr: { ...options.attr },
meta: { ...options.meta },
}
};
const body = transformBlock(ast.body, state);
const meta: ProgramMetadata = {
username: state.opts.meta.username ?? "ezfprg",
hostname: state.opts.meta.hostname ?? "deno",
editVersion: state.opts.meta.editVersion ?? 1,
editMysteryNumber: state.opts.meta.editMysteryNumber ?? 2980,
};
const now = format(new Date(), "yyyy-MM-dd hh:mm:ss a");
const created = `${meta.username};${meta.hostname};${now.replace(" ", ";")}`;
const edited = `${created};${meta.editVersion};${meta.editMysteryNumber}`;
const attr: ProgramAttributes = {
name: state.opts.attr.name ?? "Untitled",
authors: state.opts.attr.authors ?? "ezfprg",
about: state.opts.attr.about ?? "Converted from TypeScript by ezfprg",
saved: state.opts.attr.saved ?? now,
created: btoa(created),
edited: btoa(edited),
};
let template = customTemplate
?? Deno.readTextFileSync("./assets/template.fprg");
for (const [k, v] of Object.entries(attr)) {
template = template.replace(`[[${k}]]`, v!);
}
return template.replace("[[body]]", () => body);
}
export function transformBlock (
block: Array<SWC.Statement> | Array<SWC.ModuleItem>,
state: TransformState,
) {
let output = "";
for (const stmt of block) {
if (state.opts.showSourceStatements) {
const rawStmt = state.input.slice(
stmt.span.start - 1,
stmt.span.end - 1
);
output += state.indent + Statements.comment(rawStmt);
}
switch (stmt.type) {
case "VariableDeclaration":
output += transformVariableDecl(stmt, state);
break;
case "ExpressionStatement": {
switch (stmt.expression.type) {
case "AssignmentExpression":
output += transformAssignmentExpr(stmt.expression, state);
break;
case "CallExpression":
output += transformBlockCallExpr(stmt.expression, state);
break;
}
} break;
case "IfStatement":
output += transformIfStmt(stmt, state);
break;
case "WhileStatement":
output += transformWhileStmt(stmt, state);
break;
case "ForStatement":
output += transformForStmt(stmt, state);
break;
}
}
return output;
}
export function transformNewScope (
body: BlockStatement,
state: TransformState,
): string {
if (body.stmts.length === 0) return "";
const oldIndent = state.indent.toString();
const oldIdents: Idents = new Map(state.idents);
state.indent = state.indent + " ";
const out = transformBlock(body.stmts, state);
state.indent = oldIndent;
state.idents = oldIdents;
return out;
}
export function transformExpr (
expr: SWC.Literal | SWC.Expression,
idents: Map<string, Ident>,
): [string, Type] {
switch (expr.type) {
case "NumericLiteral": {
// @ts-ignore - ts thinks that raw does not exist in a NumericLiteral
const val = "raw" in expr ? expr.raw : expr.value.toString();
return [val, val.includes(".") ? "float" : "int"];
}
case "StringLiteral":
return [`"${expr.value}"`, "string"];
case "BooleanLiteral":
return [expr.value.toString(), "boolean"];
case "Identifier":
case "CallExpression": {
let name: string;
if (expr.type === "Identifier") {
name = expr.value;
} else if ("value" in expr.callee) {
name = expr.callee.value.toString();
} else {
throw new Errors.InvalidExpression(expr.callee.type, expr.span.start);
}
const matchedIdent = idents.get(name);
if (!matchedIdent || !matchedIdent.defined) {
throw new Errors.UndefinedReference(name, expr.span.start);
}
return [name, matchedIdent.type];
}
case "ParenthesisExpression": {
const [_expr, exprType] = transformExpr(expr.expression, idents);
return [`(${_expr})`, exprType];
}
case "UnaryExpression": {
const [_expr, exprType] = transformExpr(expr.argument, idents);
return [`${expr.operator}${_expr}`, exprType];
}
case "BinaryExpression": {
const [left, lType] = transformExpr(expr.left, idents);
const [right, rType] = transformExpr(expr.right, idents);
if (!TypeUtils.validateBinaryOpTypes(lType, rType, expr.operator)) {
throw new Errors.InvalidBinaryOp(
lType,
rType,
expr.operator,
expr.span.start
);
}
const op = [lType, rType].includes("string") && expr.operator === "+"
? "&"
: expr.operator;
const type = TypeUtils.castValidBinaryOpTypes(lType, rType, op);
return [`${left} ${op} ${right}`, type];
}
default:
// @ts-ignore: fuck you ts
throw new Errors.InvalidExpression(expr.type, expr.span.start);
}
}
export function transformBlockCallExpr (
expr: SWC.CallExpression,
state: TransformState,
): string {
let name: string;
if ("value" in expr.callee) {
name = expr.callee.value.toString();
} else {
throw new Errors.InvalidExpression(
expr.callee.type,
expr.span.start
);
}
switch (name) {
case "println": {
if (expr.arguments.length < 1) {
throw new Errors.InvalidArgumentCount(
name,
expr.arguments.length,
1,
expr.span.start
);
}
const [newExpr, _] = transformExpr(
expr.arguments[0].expression,
state.idents,
);
return state.indent + Statements.output(newExpr, true);
}
case "print": {
if (expr.arguments.length < 1) {
throw new Errors.InvalidArgumentCount(
name,
expr.arguments.length,
1,
expr.span.start
);
}
const [newExpr, _] = transformExpr(
expr.arguments[0].expression,
state.idents,
);
return state.indent + Statements.output(newExpr, false);
}
case "input": {
if (expr.arguments.length !== 1) {
throw new Errors.InvalidArgumentCount(
name,
expr.arguments.length,
1,
expr.span.start
);
}
if (expr.arguments[0].expression.type !== "Identifier") {
throw new Errors.InvalidSyntaxType(
expr.arguments[0].expression.type,
"<Identifier>",
expr.span.start
);
}
const variable = expr.arguments[0].expression.value.toString();
const matchedIdent = state.idents.get(variable);
if (!matchedIdent) {
throw new Errors.UndefinedReference(variable, expr.span.start);
}
state.idents.set(variable, { ...matchedIdent, defined: true });
return state.indent + Statements.input(variable);
}
case "attr":
case "meta": {
if (expr.arguments.length !== 2) {
throw new Errors.InvalidArgumentCount(
name,
expr.arguments.length,
2,
expr.span.start
);
}
for (const arg of expr.arguments) {
if (arg.expression.type !== "StringLiteral") {
throw new Errors.InvalidSyntaxType(
expr.arguments[0].expression.type,
"<StringLiteral>",
expr.span.start
);
}
}
const args = expr.arguments
.map((a) => a.expression as SWC.StringLiteral);
const key: string = args[0].value;
if (TypeUtils.validKeys[name].includes(key)) {
if (state.opts[name][key] === undefined) {
state.opts[name][key] = args[1].value;
}
} else {
throw new Errors.InvalidAttrOrMeta(key, name, expr.span.start);
}
return "";
}
case "comment": {
if (expr.arguments.length !== 1) {
throw new Errors.InvalidArgumentCount(
name,
expr.arguments.length,
1,
expr.span.start
);
}
if (expr.arguments[0].expression.type !== "StringLiteral") {
throw new Errors.InvalidSyntaxType(
expr.arguments[0].expression.type,
"<StringLiteral>",
expr.span.start
);
}
const comment = expr.arguments[0].expression.value.toString();
return state.indent + Statements.comment(comment);
}
case "insist": {
if (expr.arguments.length !== 1) {
throw new Errors.InvalidArgumentCount(
name,
expr.arguments.length,
1,
expr.span.start
);
}
if (expr.arguments[0].expression.type !== "Identifier") {
throw new Errors.InvalidSyntaxType(
expr.arguments[0].expression.type,
"<Identifier>",
expr.span.start
);
}
const ident = expr.arguments[0].expression.value;
const matchedIdent = state.idents.get(ident);
if (!matchedIdent) {
throw new Errors.UndefinedReference(ident, expr.span.start);
}
state.idents.set(ident, { ...matchedIdent, defined: true });
return "";
}
default:
throw new Errors.UndefinedReference(name, expr.span.start);
}
}
export function transformAssignmentExpr (
expr: SWC.AssignmentExpression,
state: TransformState
): string {
if (expr.left.type !== "Identifier") {
throw new Errors.InvalidAssignmentTarget(expr.span.start);
}
const name = expr.left.value;
const matchedIdent = state.idents.get(name);
if (!matchedIdent) {
throw new Errors.UndefinedReference(name, expr.span.start);
}
const type = matchedIdent.type;
const [newExpr, exprType] = transformExpr(expr.right, state.idents);
if (!TypeUtils.validateAssignmentTypes(type, exprType)) {
throw new Errors.InvalidType(type, exprType, expr.span.start);
}
if (!matchedIdent.defined) {
state.idents.set(name, { ...matchedIdent, defined: true });
}
return state.indent + Statements.assign(name, newExpr);
}
export function transformVariableDecl (
stmt: SWC.VariableDeclaration,
state: TransformState,
) {
let output = "";
const declarations: Record<Type, string[]> = {
int: [],
float: [],
string: [],
boolean: [],
};
const assignments = new Array<Assignment>();
for (const decl of stmt.declarations) {
if (decl.id.type !== "Identifier") {
throw new Errors.InvalidAssignmentTarget(decl.span.start);
}
if (state.idents.has(decl.id.value)) {
throw new Errors.AlreadyDefined(decl.id.value, decl.id.span.start);
}
let type: Type | undefined;
if (decl.id.typeAnnotation) {
const annot = decl.id.typeAnnotation.typeAnnotation;
if ("kind" in annot) {
type = annot.kind as Type;
} else if ("typeName" in annot) {
// @ts-ignore: who the fuck asked + L + ratio
type = annot.typeName.value;
} else {
throw new Errors.InvalidTypeAnnotation(
annot.type,
decl.id.span.start
);
}
if (!TypeUtils.typeNames.includes(type!)) {
throw new Errors.InvalidType(
"int | float | string | boolean",
type!,
stmt.span.start
);
}
}
if (decl.init) {
const [expr, exprType] = transformExpr(decl.init, state.idents);
if (type && !TypeUtils.validateAssignmentTypes(type!, exprType)) {
throw new Errors.InvalidType(type, exprType, decl.span.start);
} else if (!type) {
type = exprType;
}
assignments.push({ name: decl.id.value, value: expr });
}
if (!type) {
throw new Errors.MissingType(decl.id.value, stmt.span.start);
}
declarations[type].push(decl.id.value);
state.idents.set(
decl.id.value,
{ type: type, defined: decl.init !== null }
);
}
TypeUtils.typeNames.forEach((type) => {
const names = declarations[type];
if (names.length > 0) {
output += state.indent + Statements.declare(
names.join(", "),
type as Type
);
}
});
assignments.forEach((a) => {
output += state.indent + Statements.assign(a.name, a.value);
});
return output;
}
export function transformIfStmt (
stmt: SWC.IfStatement,
state: TransformState
): string {
const [expr, exprType] = transformExpr(stmt.test, state.idents);
if (exprType !== "boolean") {
throw new Errors.InvalidType("boolean", exprType, stmt.span.start);
}
let arm1: string;
if (stmt.consequent && stmt.consequent.type === "BlockStatement") {
arm1 = transformNewScope(stmt.consequent, state);
} else {
throw new Errors.InvalidSyntaxType(
"BlockStatement",
stmt.consequent.type,
stmt.span.start
);
}
let arm2 = "";
if (stmt.alternate) {
if (stmt.alternate.type === "BlockStatement") {
arm2 = transformNewScope(stmt.alternate, state);
} else if (stmt.alternate.type === "IfStatement") {
const oldIndent = state.indent;
state.indent += " ";
arm2 = transformIfStmt(stmt.alternate, state);
state.indent = oldIndent;
} else {
throw new Errors.InvalidSyntaxType(
"BlockStatement",
stmt.alternate.type,
stmt.span.start
);
}
}
return Statements._if(state.indent, expr, arm1, arm2);
}
export function transformWhileStmt (
stmt: SWC.WhileStatement,
state: TransformState
): string {
const [expr, exprType] = transformExpr(stmt.test, state.idents);
if (exprType !== "boolean") {
throw new Errors.InvalidType("boolean", exprType, stmt.span.start);
}
let body: string;
if (stmt.body && stmt.body.type === "BlockStatement") {
body = transformNewScope(stmt.body, state);
} else {
throw new Errors.InvalidSyntaxType(
"BlockStatement",
stmt.body.type,
stmt.span.start
);
}
return Statements._while(state.indent, expr, body);
}
export function transformForStmt (
stmt: SWC.ForStatement,
state: TransformState
): string {
let output = "";
let variable: string;
let start: number;
if (!stmt.init) {
throw new Errors.ForMissingInit(stmt.span.start);
} else if (stmt.init.type === "VariableDeclaration") {
const decl = stmt.init.declarations[0];
if (decl.id.type !== "Identifier") {
throw new Errors.InvalidSyntaxType(
"Identifier",
decl.id.type,
stmt.init.span.start
);
} else if (!decl.init || decl.init.type !== "NumericLiteral") {
throw new Errors.InvalidSyntaxType(
"NumericLiteral",
decl.init?.type ?? "Nothing",
stmt.init.span.start
);
}
variable = (decl.id as SWC.Identifier).value;
start = (decl.init as SWC.NumericLiteral).value;
output += state.indent + transformVariableDecl(stmt.init, state);
} else if (stmt.init.type === "AssignmentExpression") {
if (stmt.init.left.type !== "Identifier") {
throw new Errors.InvalidSyntaxType(
"Identifier",
stmt.init.left.type,
stmt.init.span.start
);
} else if (stmt.init.right.type !== "NumericLiteral") {
throw new Errors.InvalidSyntaxType(
"NumericLiteral",
stmt.init?.type,
stmt.init.span.start
);
}
variable = stmt.init.left.value;
start = stmt.init.right.value;
} else {
throw new Errors.InvalidSyntaxType(
"VariableDeclaration | AssignmentExpression",
stmt.init.type,
stmt.span.start
);
}
if (!stmt.test) {
throw new SyntaxError(
);
} else if (stmt.test.type !== "NumericLiteral") {
throw new Errors.InvalidSyntaxType(
"NumericLiteral",
stmt.test.type,
stmt.span.start
);
}
const end: number = stmt.test.value;
let step: number;
let direction: "inc" | "dec";
if (!stmt.update) {
throw new Errors.ForMissingEnd(stmt.span.start);
} else if (stmt.update.type === "UpdateExpression") {
if (stmt.update.argument.type !== "Identifier") {
throw new Errors.InvalidSyntaxType(
"Identifier",
stmt.update.argument.type,
stmt.update.span.start
);
} else if (stmt.update.argument.value !== variable) {
throw new Errors.ForVariableMismatch(
variable,
stmt.update.argument.value,
stmt.update.span.start
);
} else if (stmt.update.operator === "++") {
step = 1;
direction = "inc";
} else if (stmt.update.operator === "--") {
step = 1;
direction = "dec";
} else {
throw new Errors.ForInvalidOperator(
stmt.update.operator,
stmt.update.span.start
);
}
} else if (stmt.update.type === "AssignmentExpression") {
if (stmt.update.left.type !== "Identifier") {
throw new Errors.InvalidSyntaxType(
"Identifier",
stmt.update.left.type,
stmt.update.span.start
);
} else if (stmt.update.left.value !== variable) {
throw new Errors.ForVariableMismatch(
variable,
stmt.update.left.value,
stmt.update.span.start
);
}
if (stmt.update.operator === "+=") {
direction = "inc";
} else if (stmt.update.operator === "-=") {
direction = "dec";
} else {
throw new Errors.ForInvalidOperator(
stmt.update.operator,
stmt.update.span.start
);
}
if (stmt.update.right.type !== "NumericLiteral") {
throw new Errors.InvalidSyntaxType(
"NumericLiteral",
stmt.update.right.type,
stmt.update.span.start
);
}
step = stmt.update.right.value;
} else {
throw new Errors.InvalidSyntaxType(
"UpdateExpression | AssignmentExpression",
stmt.update.type,
stmt.span.start
);
}
let body: string;
if (stmt.body && stmt.body.type === "BlockStatement") {
body = transformNewScope(stmt.body, state);
} else {
throw new Errors.InvalidSyntaxType(
"BlockStatement",
stmt.body.type,
stmt.span.start
);
}
output += Statements._for(
state.indent,
variable,
start,
end,
direction,
step,
body
);
return output;
}

@ -0,0 +1,51 @@
import * as SWC from "./swc.ts";
export type Type = "int" | "float" | "string" | "boolean";
export const typeNames: Array<Type> = ["int", "float", "string", "boolean"];
export const validKeys: Record<string, Array<string>> = {
attr: ["name", "authors", "about"],
meta: ["username", "hostname", "editVersion", "editMysteryNumber"],
};
export const numOps = [
"+", "-", "*", "/", "%", "^",
"<=", ">=", "<", ">", "==", "!="
];
export const boolOps = ["&&", "||", "!", "==", "!="];
export const stringOps = ["+", "==", "!="];
export function validateBinaryOpTypes (
a: Type,
b: Type,
op: SWC.BinaryOperator,
): boolean {
// For purposes of validation, float and int are the same
a = a === "float" ? "int" : a;
b = b === "float" ? "int" : b;
if (a === b) {
switch (a) {
case "int": return numOps.includes(op);
case "string": return stringOps.includes(op);
case "boolean": return boolOps.includes(op);
}
}
return [a, b].includes("string") && op === "+";
}
export function validateAssignmentTypes (a: Type, b: Type) {
return a === b
|| (a === "float" && b === "int")
|| (a === "int" && b === "float");
}
export function castValidBinaryOpTypes (
a: Type,
b: Type,
op: SWC.BinaryOperator
): Type {
return ["<", ">", "<=", ">=", "==", "!=", "&&", "||"].includes(op)
? "boolean"
: a === b
? a
: [a, b].includes("string") ? "string" : "float";
}

@ -0,0 +1,13 @@
export function escapeXML (input: string): string {
if (input.match(/[&<>"\n]/)) return input
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll(`"`, "&quot;")
.replaceAll("\n", "&#13;&#10;");
return input;
}
export function capitalize (str: string): string {
return str[0].toUpperCase() + str.slice(1);
}
Loading…
Cancel
Save