Skip to content

Commit

Permalink
Merge pull request #10 from s-kybound/main
Browse files Browse the repository at this point in the history
Upgrade Scm-slang parser
  • Loading branch information
s-kybound authored Mar 21, 2024
2 parents 35fe554 + 241d77b commit 65ad23d
Show file tree
Hide file tree
Showing 63 changed files with 11,616 additions and 2,737 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
8 changes: 8 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
env:
browser: true
es2021: true
extends: standard-with-typescript
parserOptions:
ecmaVersion: latest
sourceType: module
rules: {}
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,25 @@

`scm-slang` is an **experimental** implementation of the [Scheme](https://www.scheme.org/) programming language designed for compatibiity with the online learning environment of [Source Academy](https://sourceacademy.org/). It aims to be faithful to the original *Structure and Interpretation of Computer Programs* (SICP) book whilst maintaining compatibility with modules designed for use with [`js-slang`](https://github.com/source-academy/js-slang), a subset of JavaScript intended for use with the SICP JS book in Source Academy.

`scm-slang` provides a special Scheme parser that is able to parse Scheme code.
`scm-slang` provides a special Scheme parser that is able to parse Scheme code. It also supports standard Scheme data structures such as Lists, Vectors and the Numeric tower.

## How does it work?

`scm-slang` parses a subset of Scheme (minimally, enough to fulfil SICP chapters 1-4) and generates an `estree`-compatible AST. This way, `scm-slang` allows code written in SCM Source Languages to use modules written for JS Source Languages.

## Comparison with Revised⁷ Report on the Algorithmic Language Scheme

`scm-slang` is designed following the [R7RS language specification](https://small.r7rs.org/) of Scheme. However, there are several key deviations that differentiate `scm-slang` from a complete implementation of R7RS Scheme:
`scm-slang` ultimately follows the [R7RS language specification](https://small.r7rs.org/) of Scheme. However, there are several key deviations that differentiate `scm-slang` from a complete implementation of R7RS Scheme:

- Continuations: Continuations do not currently have first-class status in Source. Hence, procedures such as `call/cc`, which operate on continuations, are not yet implemented. Subject to change, with the implementation of the Explict Control Evaluator in js-slang.
- Continuations: The parser itself does not support continuations, but in conjunction with the Source Academy Explicit Control Evaluator, continuations are supported within the Source Academy ecosystem.

- Macros: Not implemented. Subject to change with the future refactoring of scm-slang to use an intermediate AST before final estree translation.
- Macros: Not implemented at the moment. Set for completion in Autumn-Fall 2024.

- Defines of same variable in same scope: Not allowed at the moment. Subject to change with the future refactoring of scm-slang to use an intermediate AST before final estree translation.

- Types: `scm-slang` does not support complex numbers or characters.
- Types: `scm-slang` does not support characters or bytevectors at the moment.

- Parentheses: `scm-slang` supports the use of square brackets (i.e. []) interchangably with parentheses in order to enhance the visual representation of Scheme code, similar to [Racket](https://racket-lang.org/) or [Guile Scheme](https://www.gnu.org/software/guile/). [See relevant discussion here](http://community.schemewiki.org/?scheme-faq-language)

- Named let is not supported. Subject to change with the future refactoring of scm-slang to use an intermediate AST before final estree translation.

- Variadic functions: `scm-slang` does not currently support variadic functions with usage of the `.` operator or the `case-lambda` syntax.
- Named let is not supported.

- Import/Export: `scm-slang` follows a specialised import/export system that deviates from any standard Scheme implementation. It follows more closely to JavaScript syntax so as to maintain compatibility with current Source Academy modules.

Expand All @@ -44,7 +40,7 @@

# Requirements

- `node`: known working version: v16.19.0
- `node`: known working version: v20.11.0

# Usage

Expand Down
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { presets: ["@babel/preset-env"] };
7 changes: 7 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
preset: "ts-jest",
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
};
94 changes: 56 additions & 38 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,58 @@
{
"name": "scm-slang",
"version": "0.5.0",
"license": "Apache-2.0",
"description": "Scheme-based implementations of Source, written in Typescript",
"keywords": [
"Scheme",
"interpreter",
"compiler",
"Source",
"SICP"
],
"author": {
"name": "Source Academy",
"url": "https://github.com/source-academy/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/source-academy/scm-slang.git"
},
"main": "index.js",
"bugs": {
"url": "https://github.com/source-academy/scm-slang/issues"
},
"homepage": "https://github.com/source-academy/scm-slang#readme",
"private": true,
"dependencies": {
"@types/estree": "^1.0.0",
"acorn": "^8.8.2",
"acorn-walk": "^8.2.0",
"js-base64": "^3.7.5"
},
"scripts": {},
"meta": {},
"devDependencies": {
"@types/node": "^18.14.2",
"astring": "^1.8.4",
"source-map": "^0.7.4"
}
"name": "scm-slang",
"version": "1.0.3",
"license": "Apache-2.0",
"description": "Scheme-based implementations of Source, written in Typescript",
"keywords": [
"Scheme",
"interpreter",
"compiler",
"Source",
"SICP"
],
"author": {
"name": "Source Academy",
"url": "https://github.com/source-academy/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/source-academy/scm-slang.git"
},
"main": "index.js",
"bugs": {
"url": "https://github.com/source-academy/scm-slang/issues"
},
"homepage": "https://github.com/source-academy/scm-slang#readme",
"private": true,
"dependencies": {
"@types/estree": "^1.0.0",
"acorn": "^8.8.2",
"acorn-walk": "^8.2.0",
"js-base64": "^3.7.5"
},
"scripts": {
"build-libs": "npx ts-node ./src/compile-libs.ts",
"test": "jest",
"lint": "eslint --ignore-path .eslintignore --ext .ts",
"format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\""
},
"meta": {},
"devDependencies": {
"@babel/preset-env": "^7.23.9",
"@types/jest": "^29.5.11",
"@types/node": "^18.14.2",
"@typescript-eslint/eslint-plugin": "^6.4.0",
"babel-jest": "^29.7.0",
"escodegen": "^2.1.0",
"eslint": "^8.0.1",
"eslint-config-standard-with-typescript": "^43.0.1",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
"eslint-plugin-promise": "^6.0.0",
"jest": "^29.7.0",
"prettier": "^3.2.4",
"source-map": "^0.7.4",
"ts-jest": "^29.1.2",
"typescript": "*"
}
}
45 changes: 45 additions & 0 deletions src/compile-libs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import path from "path";
import fs from "fs";
import { schemeParse } from "./transpiler";
import { estreeEncode } from "./utils/encoder-visitor";
const escodegen = require("escodegen");

function transpile(inputFilePath: string, outputFilePath: string) {
fs.readFile(inputFilePath, "utf8", (err, data) => {
if (err) {
console.error(`Error reading file: ${err}`);
return;
}

// we transpile the file
const transpiledAST = schemeParse(data);
const encodedAST = estreeEncode(transpiledAST);
const transpiledProgram = escodegen.generate(encodedAST);

fs.writeFile(outputFilePath, transpiledProgram, (err) => {
if (err) {
console.error(`Error writing file: ${err}`);
return;
}
console.log(`${inputFilePath} has been transpiled to ${outputFilePath}`);
});
});
}

// get file paths from command line arguments
const inputFilePath: string = process.argv[2];
const outputFilePath: string = process.argv[3]
? process.argv[3]
: inputFilePath.replace(".scm", ".js");

// validate file paths
if (!inputFilePath) {
console.error("Please provide an input file path and an output file path");
}

if (!(path.extname(inputFilePath) === ".scm")) {
console.error("Please provide a .scm file for compilation!");
}

// if everything is fine, we transpile the file
transpile(inputFilePath, outputFilePath);
26 changes: 9 additions & 17 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Tokenizer } from "./tokenizer";
import { Parser } from "./parser";
import { Program } from "estree";
import { encode as b64Encode, decode as b64Decode } from "js-base64";

export * from "./prelude-visitor";
export * as TokenizerError from "./tokenizer-error";
export * as ParserError from "./parser-error";
export * from "./utils/encoder-visitor";
export { LexerError } from "./transpiler";
export { ParserError } from "./transpiler";
export { schemeParse } from "./transpiler";

const JS_KEYWORDS: string[] = [
"break",
Expand Down Expand Up @@ -66,13 +64,13 @@ export function encode(identifier: string): string {
"$scheme_" +
b64Encode(identifier).replace(
/([^a-zA-Z0-9_])/g,
(match: string) => `\$${match.charCodeAt(0)}\$`
(match: string) => `\$${match.charCodeAt(0)}\$`,
)
);
} else {
return identifier.replace(
/([^a-zA-Z0-9_])/g,
(match: string) => `\$${match.charCodeAt(0)}\$`
(match: string) => `\$${match.charCodeAt(0)}\$`,
);
}
}
Expand All @@ -89,18 +87,12 @@ export function decode(identifier: string): string {
identifier
.slice(8)
.replace(/\$([0-9]+)\$/g, (_, code: string) =>
String.fromCharCode(parseInt(code))
)
String.fromCharCode(parseInt(code)),
),
);
} else {
return identifier.replace(/\$([0-9]+)\$/g, (_, code: string) =>
String.fromCharCode(parseInt(code))
String.fromCharCode(parseInt(code)),
);
}
}

export function schemeParse(source: string, chapter?: number): Program {
const tokenizer = new Tokenizer(source);
const parser = new Parser(source, tokenizer.scanTokens(), chapter);
return parser.parse();
}
Loading

0 comments on commit 65ad23d

Please sign in to comment.