Skip to content

Commit 4849b22

Browse files
committed
Run Scala interpreter in the browser
1 parent 9f94dc2 commit 4849b22

File tree

71 files changed

+21149
-574
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+21149
-574
lines changed

BROWSER_COMPILER_DESIGN.md

Lines changed: 606 additions & 0 deletions
Large diffs are not rendered by default.

BROWSER_COMPILER_DESIGN_SIMPLE.md

Lines changed: 909 additions & 0 deletions
Large diffs are not rendered by default.

CROSS_COMPILATION_FEASIBILITY.md

Lines changed: 862 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# GitHub Actions workflow to build and deploy the browser compiler demo to GitHub Pages
2+
3+
name: Deploy Demo to GitHub Pages
4+
5+
on:
6+
push:
7+
branches: [main, master]
8+
paths:
9+
- 'browser-interpreter/**'
10+
pull_request:
11+
branches: [main, master]
12+
paths:
13+
- 'browser-interpreter/**'
14+
workflow_dispatch: # Allow manual trigger from any branch
15+
inputs:
16+
deploy:
17+
description: 'Deploy to GitHub Pages'
18+
required: false
19+
default: true
20+
type: boolean
21+
22+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
23+
permissions:
24+
contents: read
25+
pages: write
26+
id-token: write
27+
28+
# Allow only one concurrent deployment
29+
concurrency:
30+
group: "pages"
31+
cancel-in-progress: true
32+
33+
jobs:
34+
build:
35+
runs-on: ubuntu-latest
36+
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@v4
40+
41+
- name: Setup Java
42+
uses: actions/setup-java@v4
43+
with:
44+
distribution: 'temurin'
45+
java-version: '21'
46+
cache: 'sbt'
47+
48+
- name: Build Scala.js
49+
working-directory: browser-interpreter
50+
run: sbt js/fastLinkJS
51+
52+
- name: Prepare deployment files
53+
run: |
54+
mkdir -p _site
55+
56+
# Find the Scala version from the build output
57+
SCALA_VERSION=$(ls browser-interpreter/js/target/ | grep scala | head -1)
58+
echo "Scala version: $SCALA_VERSION"
59+
60+
# Copy the demo HTML as index.html, updating the JS path if needed
61+
sed "s|js/target/scala-[0-9.]*|js/target/$SCALA_VERSION|g" \
62+
browser-interpreter/demo-compiler.html > _site/index.html
63+
64+
# Copy the generated JavaScript
65+
mkdir -p "_site/js/target/$SCALA_VERSION"
66+
cp -r "browser-interpreter/js/target/$SCALA_VERSION/browser-interpreter-js-fastopt" \
67+
"_site/js/target/$SCALA_VERSION/"
68+
69+
# Add .nojekyll to prevent Jekyll processing
70+
touch _site/.nojekyll
71+
72+
# Show what we're deploying
73+
echo "Deployment contents:"
74+
find _site -type f | head -20
75+
76+
- name: Setup Pages
77+
uses: actions/configure-pages@v4
78+
79+
- name: Upload artifact
80+
uses: actions/upload-pages-artifact@v3
81+
with:
82+
path: '_site'
83+
84+
deploy:
85+
# Only deploy on push to main/master or manual trigger with deploy=true
86+
if: |
87+
github.event_name == 'push' ||
88+
(github.event_name == 'workflow_dispatch' && inputs.deploy)
89+
environment:
90+
name: github-pages
91+
url: ${{ steps.deployment.outputs.page_url }}
92+
runs-on: ubuntu-latest
93+
needs: build
94+
95+
steps:
96+
- name: Deploy to GitHub Pages
97+
id: deployment
98+
uses: actions/deploy-pages@v4
99+

browser-interpreter/.nojekyll

Whitespace-only changes.

browser-interpreter/README.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Scala Browser Interpreter
2+
3+
A proof-of-concept TASTy-based interpreter that runs Scala code in the browser.
4+
5+
## Quick Start
6+
7+
### Run the Demo
8+
9+
Simply open `demo.html` in your web browser:
10+
11+
```bash
12+
open demo.html
13+
```
14+
15+
The demo includes several built-in examples:
16+
- Hello World - Basic println
17+
- Arithmetic - Math operations
18+
- Fibonacci - Recursive functions
19+
- Pattern Matching - Match expressions with guards
20+
- List Operations - map, filter, foldLeft
21+
- Option Handling - Some/None
22+
- Try/Catch - Exception handling
23+
- Higher-Order Functions - Functions as values
24+
- Factorial - Recursion
25+
- Closures - Captured variables
26+
27+
### Convert Real Scala Code
28+
29+
1. **Compile Scala to TASTy:**
30+
```bash
31+
scalac -Yretain-trees examples/HelloWorld.scala -d out
32+
```
33+
34+
2. **Convert TASTy to JSON:**
35+
```bash
36+
sbt "browserInterpreterJvm/run out/HelloWorld.tasty"
37+
```
38+
39+
3. **Paste the JSON output into the demo and run!**
40+
41+
## Project Structure
42+
43+
```
44+
browser-interpreter/
45+
├── demo.html # Self-contained browser demo
46+
├── build.sbt # SBT build configuration
47+
├── jvm/ # JVM tools
48+
│ └── src/main/scala/browser/
49+
│ ├── TastyToJsonConverter.scala # TASTy → JSON converter
50+
│ └── AstSerializer.scala # Tree serializer
51+
├── js/ # Scala.js browser module
52+
│ └── src/main/scala/browser/
53+
│ └── BrowserInterpreter.scala # Scala.js interpreter
54+
└── examples/ # Example Scala programs
55+
├── HelloWorld.scala
56+
├── Fibonacci.scala
57+
├── PatternMatching.scala
58+
└── ListOperations.scala
59+
```
60+
61+
## JSON AST Format
62+
63+
The interpreter uses a simple JSON AST format:
64+
65+
```json
66+
// Literals
67+
{"tag": "Literal", "type": "Int", "value": 42}
68+
{"tag": "Literal", "type": "String", "value": "hello"}
69+
70+
// Variables
71+
{"tag": "Ident", "name": "x"}
72+
73+
// Binary operations
74+
{"tag": "BinaryOp", "op": "+", "lhs": {...}, "rhs": {...}}
75+
76+
// Blocks
77+
{"tag": "Block", "stats": [...], "expr": {...}}
78+
79+
// Conditionals
80+
{"tag": "If", "cond": {...}, "thenp": {...}, "elsep": {...}}
81+
82+
// Loops
83+
{"tag": "While", "cond": {...}, "body": {...}}
84+
85+
// Function definitions
86+
{"tag": "DefDef", "name": "add", "params": ["a", "b"], "body": {...}}
87+
88+
// Function calls
89+
{"tag": "Apply", "fn": {...}, "args": [...]}
90+
91+
// Lambdas
92+
{"tag": "Lambda", "params": ["x"], "body": {...}}
93+
94+
// Pattern matching
95+
{"tag": "Match", "selector": {...}, "cases": [
96+
{"pattern": {...}, "guard": {...}, "body": {...}}
97+
]}
98+
99+
// Exceptions
100+
{"tag": "Try", "block": {...}, "catches": [...], "finalizer": {...}}
101+
{"tag": "Throw", "expr": {...}}
102+
```
103+
104+
## Supported Features
105+
106+
| Feature | Status |
107+
|---------|--------|
108+
| Literals (Int, String, Boolean, etc.) ||
109+
| Variables (val, var) ||
110+
| Arithmetic (+, -, *, /, %) ||
111+
| Comparisons (<, >, <=, >=, ==, !=) ||
112+
| Boolean operators (&&, \|\|, !) ||
113+
| Conditionals (if/else) ||
114+
| Loops (while) ||
115+
| Blocks with local definitions ||
116+
| Functions (def) ||
117+
| Recursion ||
118+
| Lambdas ||
119+
| Closures with captured variables ||
120+
| Pattern matching ||
121+
| Guards in pattern matching ||
122+
| Option (Some/None) ||
123+
| List operations (map, filter, fold, etc.) ||
124+
| String operations ||
125+
| Tuple operations ||
126+
| Try/Catch/Finally ||
127+
| Throw ||
128+
| For comprehensions | ⚠️ Partial |
129+
| Classes ||
130+
| Traits ||
131+
| Imports | N/A (compile-time) |
132+
133+
## Building
134+
135+
### Prerequisites
136+
- SBT 1.10+
137+
- Scala 3.7.0+
138+
- Node.js (for Scala.js testing)
139+
140+
### Build Commands
141+
142+
```bash
143+
# Compile everything
144+
sbt compile
145+
146+
# Build JVM tools
147+
sbt browserInterpreterJvm/compile
148+
149+
# Build Scala.js module
150+
sbt browserInterpreterJs/fastLinkJS
151+
152+
# Run TASTy converter
153+
sbt "browserInterpreterJvm/run path/to/file.tasty"
154+
```
155+
156+
## Architecture
157+
158+
```
159+
┌─────────────────────────────────────────────────────────────┐
160+
│ Compilation Pipeline │
161+
├─────────────────────────────────────────────────────────────┤
162+
│ │
163+
│ Scala Source → scalac → TASTy → TastyToJson → JSON AST │
164+
│ │
165+
├─────────────────────────────────────────────────────────────┤
166+
│ Browser Execution │
167+
├─────────────────────────────────────────────────────────────┤
168+
│ │
169+
│ JSON AST → BrowserInterpreter.interpret() → Output │
170+
│ │
171+
└─────────────────────────────────────────────────────────────┘
172+
```
173+
174+
## Roadmap
175+
176+
- [x] Basic interpreter with control flow
177+
- [x] Pattern matching support
178+
- [x] Exception handling
179+
- [x] Collection operations
180+
- [x] TASTy-to-JSON converter
181+
- [ ] Cross-compile full interpreter to Scala.js
182+
- [ ] Bundle stdlib TASTy for type-checking
183+
- [ ] Full browser-based compilation
184+
185+
## License
186+
187+
Apache 2.0 (same as Scala 3)
188+

browser-interpreter/build.sbt

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import sbt.Keys._
2+
3+
val scala3Version = "3.7.0"
4+
5+
// Shared settings
6+
lazy val commonSettings = Seq(
7+
version := "0.1.0-SNAPSHOT",
8+
scalaVersion := scala3Version,
9+
scalacOptions ++= Seq("-deprecation", "-feature")
10+
)
11+
12+
// Shared code (cross-compiled to JVM and JS)
13+
lazy val shared = crossProject(JVMPlatform, JSPlatform)
14+
.crossType(CrossType.Pure)
15+
.in(file("shared"))
16+
.settings(commonSettings)
17+
.settings(
18+
name := "browser-interpreter-shared"
19+
)
20+
21+
lazy val sharedJVM = shared.jvm
22+
lazy val sharedJS = shared.js
23+
24+
// JVM project for TASTy-to-JSON conversion tools
25+
lazy val jvm = project
26+
.in(file("jvm"))
27+
.dependsOn(sharedJVM)
28+
.settings(commonSettings)
29+
.settings(
30+
name := "browser-interpreter-jvm",
31+
libraryDependencies ++= Seq(
32+
"org.scala-lang" %% "scala3-tasty-inspector" % scala3Version
33+
),
34+
Compile / mainClass := Some("browser.TastyToJsonConverter")
35+
)
36+
37+
// Scala.js project for browser execution
38+
lazy val js = project
39+
.in(file("js"))
40+
.enablePlugins(ScalaJSPlugin)
41+
.dependsOn(sharedJS)
42+
.settings(commonSettings)
43+
.settings(
44+
name := "browser-interpreter-js",
45+
scalaJSUseMainModuleInitializer := false,
46+
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) },
47+
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.8.0"
48+
)
49+
50+
// Root project
51+
lazy val root = project
52+
.in(file("."))
53+
.aggregate(sharedJVM, sharedJS, jvm, js)
54+
.settings(commonSettings)
55+
.settings(
56+
name := "browser-interpreter",
57+
publish / skip := true
58+
)

0 commit comments

Comments
 (0)