Skip to content
This repository was archived by the owner on Jan 11, 2026. It is now read-only.

Commit 15ccc8d

Browse files
committed
source-only
0 parents  commit 15ccc8d

File tree

165 files changed

+25964
-0
lines changed

Some content is hidden

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

165 files changed

+25964
-0
lines changed

.dockerignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.git
2+
.gitignore
3+
.DS_Store
4+
**/bin
5+
**/obj
6+
compiler/bin
7+
compiler/obj
8+
frontend/node_modules
9+
frontend/.next
10+
frontend/out
11+
frontend/.turbo
12+
frontend/tsconfig.tsbuildinfo

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- "**"
7+
pull_request:
8+
branches:
9+
- "**"
10+
11+
jobs:
12+
build-and-test:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
19+
- name: Install dependencies
20+
run: make install
21+
shell: bash
22+
23+
- name: Add PNPM to PATH
24+
run: echo "$HOME/.pnpm/bin" >> "$GITHUB_PATH"
25+
shell: bash
26+
27+
- name: Run tests
28+
run: make test
29+
shell: bash
30+
31+
- name: Build project
32+
run: make build
33+
shell: bash

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.DS_Store

LICENSE

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

Makefile

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
SHELL := /bin/bash
2+
3+
ESC := \033
4+
RESET := $(ESC)[0m
5+
BOLD := $(ESC)[1m
6+
DIM := $(ESC)[2m
7+
GREEN := $(ESC)[32m
8+
YELLOW := $(ESC)[33m
9+
RED := $(ESC)[31m
10+
CYAN := $(ESC)[36m
11+
12+
.PHONY: build cli test dev start install
13+
14+
CLI_ARGS := $(filter-out build cli test dev start install,$(MAKECMDGOALS))
15+
16+
$(CLI_ARGS):
17+
@:
18+
19+
cli: $(CLI_ARGS)
20+
@test -n "$(CLI_ARGS)" || (printf "$(RED)Usage: make cli path/to/file.toy$(RESET)\n" && exit 2)
21+
@scripts/cli.sh $(CLI_ARGS)
22+
23+
test:
24+
@$(MAKE) -C compiler build $(MAKEOVERRIDES)
25+
@COMPILER_PATH=$$(dirname $(abspath $(lastword $(MAKEFILE_LIST))))/compiler/bin/Release/net9.0/CompilersApp \
26+
scripts/test.sh
27+
28+
build:
29+
@$(MAKE) -C compiler build $(MAKEOVERRIDES)
30+
COMPILER_PATH=$$(dirname $(abspath $(lastword $(MAKEFILE_LIST))))/compiler/bin/Release/net9.0/CompilersApp \
31+
$(MAKE) -C frontend build $(MAKEOVERRIDES)
32+
33+
dev:
34+
@scripts/dev.sh $(ARGS)
35+
36+
start:
37+
COMPILER_PATH=$$(dirname $(abspath $(lastword $(MAKEFILE_LIST))))/compiler/bin/Release/net9.0/CompilersApp \
38+
$(MAKE) -C frontend start $(MAKEOVERRIDES) -- $(ARGS)
39+
40+
install:
41+
@scripts/install.sh

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<img src="https://github.com/user-attachments/assets/00c605c9-73cd-4fd7-9aff-fcb9650d251c" width="200" />
2+
3+
![academic project](https://img.shields.io/badge/academic%20project-3DB420)
4+
![source only](https://img.shields.io/badge/source%20only-545454)
5+
6+
> WASM compiler for the invented object-oriented language, built from scratch in C#.
7+
8+
<img width="4014" height="1445" alt="image" src="https://github.com/user-attachments/assets/5af48260-eec7-43e6-8dd3-5924d2edf931" />
9+
10+
## Prerequisites
11+
12+
* .NET 9 SDK
13+
* Node 18+ + Pnpm
14+
* wasi-sdk/wasm-tools
15+
16+
Make sure `dotnet`, `npm`, `pnpm`, `nodemon` and `wasm-validate` are on your `$PATH`.
17+
18+
## Auto-tests
19+
20+
```bash
21+
make install
22+
make test
23+
```
24+
25+
## Manual tests
26+
27+
```bash
28+
make install
29+
make cli compiler/tests/donut.toy
30+
make cli compiler/tests/os.toy
31+
# ...
32+
```
33+
34+
## Starting the web UI
35+
36+
```bash
37+
make install
38+
make build
39+
make start
40+
```
41+
42+
Open http://localhost:3000.
43+
44+
## Style
45+
46+
* C#: `dotnet format` with the default style.
47+
* TS/JS: prettier via `npx prettier . --fix`.

compiler/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bin/
2+
obj/

compiler/CompilersApp.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
</Project>

compiler/Makefile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.DEFAULT_GOAL := build
2+
3+
ARGS ?=
4+
5+
build:
6+
@scripts/build.sh
7+
8+
run: build
9+
dotnet run -- $(ARGS)
10+
11+
clean:
12+
dotnet clean
13+
14+
.PHONY: build run clean

compiler/Program.cs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
using System.Text;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using ToyLang.Semantic;
5+
using ToyLang.Syntax;
6+
using ToyLang.Wasm;
7+
8+
public class Program
9+
{
10+
public static void Main(string[] args)
11+
{
12+
string? inputFile = null;
13+
string? outputFile = null;
14+
15+
for (int i = 0; i < args.Length; i++)
16+
{
17+
switch (args[i])
18+
{
19+
case "-i":
20+
if (i + 1 < args.Length)
21+
{
22+
inputFile = args[++i];
23+
}
24+
else
25+
{
26+
Console.WriteLine("Error: Missing file name for -i option.");
27+
return;
28+
}
29+
break;
30+
case "-o":
31+
if (i + 1 < args.Length)
32+
{
33+
outputFile = args[++i];
34+
}
35+
else
36+
{
37+
Console.WriteLine("Error: Missing file name for -o option.");
38+
return;
39+
}
40+
break;
41+
default:
42+
if (inputFile == null && !args[i].StartsWith("-"))
43+
{
44+
inputFile = args[i];
45+
}
46+
else
47+
{
48+
Console.WriteLine($"Error: Unknown option or multiple input files specified: {args[i]}");
49+
return;
50+
}
51+
break;
52+
}
53+
}
54+
55+
string sourceCode = "";
56+
57+
if (inputFile != null)
58+
{
59+
try
60+
{
61+
sourceCode = File.ReadAllText(inputFile);
62+
}
63+
catch (Exception e)
64+
{
65+
Console.WriteLine($"Error reading file '{inputFile}': {e.Message}");
66+
return;
67+
}
68+
}
69+
else
70+
{
71+
Console.WriteLine("Enter source code (press Ctrl+D or Ctrl+Z then Enter to finish):");
72+
sourceCode = Console.In.ReadToEnd();
73+
}
74+
75+
if (string.IsNullOrEmpty(sourceCode))
76+
{
77+
Console.WriteLine("No source code provided.");
78+
return;
79+
}
80+
81+
var lineStarts = SourceMapping.ComputeLineStarts(sourceCode);
82+
83+
TextWriter writer = outputFile != null
84+
? new StreamWriter(outputFile, false, new UTF8Encoding(false))
85+
: Console.Out;
86+
87+
try
88+
{
89+
var options = new JsonSerializerOptions
90+
{
91+
Converters = { new JsonStringEnumConverter() },
92+
MaxDepth = 2048,
93+
WriteIndented = false
94+
};
95+
96+
IReadOnlyList<Token> tokens;
97+
IReadOnlyDictionary<int, List<Token>> tokensByLine;
98+
try
99+
{
100+
tokens = new Lexer(sourceCode).ScanTokens();
101+
tokensByLine = SourceMapping.BuildTokenLineMap(tokens);
102+
}
103+
catch (SyntaxError le)
104+
{
105+
var startOffset = SourceMapping.LineColumnToOffset(le.Line, le.Column, lineStarts, sourceCode);
106+
var endOffset = startOffset.HasValue ? Math.Min(startOffset.Value + 1, sourceCode.Length) : (int?)null;
107+
var lexErr = new Diagnostic(Stage.Lex, le.Line, le.Message, Severity.Error, startOffset, endOffset);
108+
var output = new PipelineOutput(Array.Empty<Token>(), null, new SemanticReport(Array.Empty<Diagnostic>(), Array.Empty<Diagnostic>()), null, lexErr, null, null, null);
109+
writer.WriteLine(JsonSerializer.Serialize(output, options));
110+
return;
111+
}
112+
113+
114+
115+
ProgramAst? ast = null;
116+
try
117+
{
118+
var analyzer = new Analyzer();
119+
ast = analyzer.analyze(tokens);
120+
}
121+
catch (SyntaxError pe)
122+
{
123+
var startOffset = SourceMapping.LineColumnToOffset(pe.Line, pe.Column, lineStarts, sourceCode);
124+
var endOffset = startOffset.HasValue ? Math.Min(startOffset.Value + 1, sourceCode.Length) : (int?)null;
125+
var parseErr = new Diagnostic(Stage.Parse, pe.Line, pe.Message, Severity.Error, startOffset, endOffset);
126+
var output = new PipelineOutput(tokens, null, new SemanticReport(Array.Empty<Diagnostic>(), Array.Empty<Diagnostic>()), null, parseErr, null, null, null);
127+
writer.WriteLine(JsonSerializer.Serialize(output, options));
128+
return;
129+
}
130+
131+
var sema = new SemanticAnalyzer();
132+
var report = sema.Analyze(ast, sourceCode, tokens);
133+
134+
ProgramAst? optimized = null;
135+
List<Optimizer.OptimizationStep>? steps = null;
136+
string? optimizedSource = null;
137+
string? wasmModuleBase64 = null;
138+
Diagnostic? stageError = null;
139+
var res = Optimizer.OptimizeWithReport(ast);
140+
optimized = res.Program;
141+
steps = res.Steps;
142+
optimizedSource = optimized != null ? CodePrinter.PrintProgram(optimized) : null;
143+
if (steps != null)
144+
{
145+
steps = steps.Select(step =>
146+
{
147+
var hint = step.Hint ?? ExtractHint(step.Before);
148+
var resolvedLine = SourceMapping.ResolveLine(step.Line, hint, tokensByLine) ?? step.Line;
149+
var (startOffset, endOffset) = SourceMapping.ResolveSpan(resolvedLine, hint, sourceCode, lineStarts, tokensByLine);
150+
return step with { Line = resolvedLine, Start = startOffset, End = endOffset };
151+
}).ToList();
152+
}
153+
if (optimized != null)
154+
{
155+
try
156+
{
157+
var wasmBytes = WasmCompiler.Compile(optimized);
158+
wasmModuleBase64 = Convert.ToBase64String(wasmBytes);
159+
}
160+
catch (Exception ex)
161+
{
162+
wasmModuleBase64 = null;
163+
stageError = new Diagnostic(Stage.Optimize, 0, ex.Message, Severity.Error);
164+
}
165+
}
166+
167+
var result = new PipelineOutput(tokens, ast, report, optimized, stageError, steps, optimizedSource, wasmModuleBase64);
168+
writer.WriteLine(JsonSerializer.Serialize(result, options));
169+
}
170+
catch (Exception e)
171+
{
172+
Console.Error.WriteLine(e.Message);
173+
Environment.ExitCode = 1;
174+
}
175+
finally
176+
{
177+
if (!ReferenceEquals(writer, Console.Out))
178+
writer.Dispose();
179+
}
180+
}
181+
182+
private static string? ExtractHint(string? before)
183+
{
184+
if (string.IsNullOrWhiteSpace(before))
185+
return null;
186+
187+
var trimmed = before.TrimStart();
188+
var newlineIndex = trimmed.IndexOfAny(new[] { '\r', '\n' });
189+
if (newlineIndex >= 0)
190+
trimmed = trimmed[..newlineIndex];
191+
192+
if (trimmed.Length == 0)
193+
return null;
194+
195+
var start = 0;
196+
while (start < trimmed.Length && !char.IsLetter(trimmed[start]) && trimmed[start] != '_')
197+
start++;
198+
199+
if (start >= trimmed.Length)
200+
return null;
201+
202+
var end = start;
203+
while (end < trimmed.Length && (char.IsLetterOrDigit(trimmed[end]) || trimmed[end] == '_'))
204+
end++;
205+
206+
if (end == start)
207+
return null;
208+
209+
return trimmed[start..end];
210+
}
211+
}

0 commit comments

Comments
 (0)