///|
struct ProjectBenchmarkFailure {
phase : String
path : String
detail : String
} derive(Debug)
///|
struct ProjectBenchmarkCorpus {
name : String
root : String
description : String
optional : Bool
} derive(Debug)
///|
struct ProjectBenchmarkCorpusRun {
corpus : ProjectBenchmarkCorpus
result : ProjectBenchmarkResult
} derive(Debug)
///|
struct ProjectBenchmarkResult {
root : String
limit : Int
mut truncated : Bool
mut total_files : Int
mut module_source_files : Int
mut declaration_files : Int
mut module_parse_ok : Int
mut module_parse_failed : Int
mut decl_parse_ok : Int
mut decl_parse_failed : Int
mut decl_emit_ok : Int
mut decl_emit_failed : Int
mut decl_js_ffi_ok : Int
mut decl_js_ffi_failed : Int
started_at_ms : UInt64
mut finished_at_ms : UInt64
failures : Array[ProjectBenchmarkFailure]
} derive(Debug)
///|
fn benchmark_project_default_limit() -> Int {
200
}
///|
fn known_project_benchmark_corpora() -> Array[ProjectBenchmarkCorpus] {
[
{
name: "fixture-types",
root: "fixtures/resolver/project/types",
description: "repo-local declaration and resolver fixtures",
optional: false,
},
{
name: "fixture-jsx-tsx",
root: "fixtures/corpus/jsx-tsx",
description: "repo-local JSX/TSX real-world snippets",
optional: false,
},
{
name: "mbts-js",
root: "/Users/mz/ghq/github.com/mizchi/mbts/js",
description: "optional local mbts JS/TS sources",
optional: true,
},
{
name: "hono-jsx",
root: "/Users/mz/ghq/github.com/honojs/hono/src/jsx",
description: "optional local Hono JSX corpus",
optional: true,
},
{
name: "hono-jsx-benchmarks",
root: "/Users/mz/ghq/github.com/honojs/hono/benchmarks/jsx/src",
description: "optional local Hono JSX benchmark pages",
optional: true,
},
{
name: "ink-components",
root: "/Users/mz/ghq/github.com/vadimdemedes/ink/src/components",
description: "optional local Ink TSX component corpus",
optional: true,
},
{
name: "vscode-copilot-tsx",
root: "/tmp/vscode-benchmark/extensions/copilot/test/simulation/workbench/components",
description: "optional local VSCode Copilot TSX corpus",
optional: true,
},
{
name: "vscode-copilot-workbench",
root: "/tmp/vscode-benchmark/extensions/copilot/test/simulation/workbench",
description: "optional local VSCode Copilot workbench corpus",
optional: true,
},
{
name: "vscode-copilot-tools",
root: "/tmp/vscode-benchmark/extensions/copilot/src/extension/tools",
description: "optional local VSCode Copilot tools corpus",
optional: true,
},
{
name: "vscode-chat-browser",
root: "/tmp/vscode-benchmark/src/vs/workbench/contrib/chat/browser",
description: "optional local VSCode chat browser corpus",
optional: true,
},
{
name: "vscode-base-browser-ui",
root: "/tmp/vscode-benchmark/src/vs/base/browser/ui",
description: "optional local VSCode base browser UI corpus",
optional: true,
},
]
}
///|
async fn project_benchmark_root_exists(root : String) -> Bool {
let kind = @fs.kind(root, follow_symlink=false) catch { _ => return false }
match kind {
@fs.FileKind::Unknown => false
_ => true
}
}
///|
fn project_benchmark_is_declaration_file(path : String) -> Bool {
path.has_suffix(".d.ts") ||
path.has_suffix(".d.mts") ||
path.has_suffix(".d.cts")
}
///|
fn project_benchmark_is_ts_family_file(path : String) -> Bool {
project_benchmark_is_declaration_file(path) ||
path.has_suffix(".ts") ||
path.has_suffix(".tsx") ||
path.has_suffix(".mts") ||
path.has_suffix(".cts")
}
///|
fn project_benchmark_should_skip_dir(name : String) -> Bool {
name == ".git" ||
name == ".moon" ||
name == ".mooncakes" ||
name == "_build" ||
name == "target" ||
name == "node_modules" ||
name == "dist" ||
name == "out"
}
///|
async fn collect_project_benchmark_files(
path : String,
out : Array[String],
limit : Int,
) -> Unit {
if limit > 0 && out.length() >= limit {
return
}
let kind = @fs.kind(path, follow_symlink=false) catch {
_ => @fs.FileKind::Unknown
}
match kind {
@fs.FileKind::Directory => {
let entries = @fs.readdir(
path,
include_hidden=false,
include_special=false,
sort=true,
) catch {
_ => []
}
for name in entries {
if project_benchmark_should_skip_dir(name) {
continue
}
let child = join_path(path, name)
collect_project_benchmark_files(child, out, limit)
if limit > 0 && out.length() >= limit {
return
}
}
}
@fs.FileKind::Regular =>
if project_benchmark_is_ts_family_file(path) {
out.push(path)
}
_ => ()
}
}
///|
fn benchmark_result_new(
root : String,
limit : Int,
started_at_ms : UInt64,
) -> ProjectBenchmarkResult {
{
root,
limit,
truncated: false,
total_files: 0,
module_source_files: 0,
declaration_files: 0,
module_parse_ok: 0,
module_parse_failed: 0,
decl_parse_ok: 0,
decl_parse_failed: 0,
decl_emit_ok: 0,
decl_emit_failed: 0,
decl_js_ffi_ok: 0,
decl_js_ffi_failed: 0,
started_at_ms,
finished_at_ms: started_at_ms,
failures: [],
}
}
///|
fn benchmark_push_failure(
result : ProjectBenchmarkResult,
phase : String,
path : String,
detail : String,
) -> Unit {
result.failures.push({ phase, path, detail })
}
///|
fn benchmark_source_line_col(source : String, pos : Int) -> (Int, Int) {
let limit = if pos < 0 {
0
} else if pos > source.length() {
source.length()
} else {
pos
}
let mut line = 1
let mut col = 1
for i in 0.. String {
let (line, col) = benchmark_source_line_col(source, pos)
"\{message} @\{line}:\{col}"
}
///|
async fn benchmark_read_file(path : String) -> String? {
Some(@fs.read_file(path).text() catch { _ => return None })
}
///|
fn benchmark_ffi_module_spec(path : String, root : String) -> String {
let relative = if root != "" && path.has_prefix(root) {
let offset = root.length()
if path.length() > offset && path[offset].unsafe_to_char() == '/' {
path[offset + 1:].to_string()
} else {
path[offset:].to_string()
}
} else {
path
}
let mut spec = "./" + relative
if spec.has_suffix(".d.ts") {
spec = spec.replace(old=".d.ts", new=".js")
} else if spec.has_suffix(".d.mts") {
spec = spec.replace(old=".d.mts", new=".mjs")
} else if spec.has_suffix(".d.cts") {
spec = spec.replace(old=".d.cts", new=".cjs")
}
spec
}
///|
async fn benchmark_declaration_file(
path : String,
root : String,
result : ProjectBenchmarkResult,
) -> Unit {
result.declaration_files += 1
let source = benchmark_read_file(path)
match source {
Some(source) => {
let parser = @parser.Parser::from_source(source)
let parsed = parser.parse_module() catch {
@parser.ParseError(msg) => {
result.decl_parse_failed += 1
let error_pos = if parser.pos < parser.tokens.length() {
parser.tokens[parser.pos].pos
} else {
source.length()
}
benchmark_push_failure(
result,
"parse_module",
path,
benchmark_format_parse_detail(source, msg, error_pos),
)
return
}
}
let _ = parsed
result.decl_parse_ok += 1
let _ = @bridge.emit_moonbit_decl_from_entry_path(path) catch {
@bridge.ModuleGraphError::ReadError(msg)
| @bridge.ModuleGraphError::ParseError(msg)
| @bridge.ModuleGraphError::ResolveError(msg) => {
result.decl_emit_failed += 1
benchmark_push_failure(result, "emit_moonbit_decl", path, msg)
return
}
}
result.decl_emit_ok += 1
let module_spec = benchmark_ffi_module_spec(path, root)
let _ = @bridge.emit_moonbit_js_ffi_bundle_from_entry_path(
path, module_spec,
) catch {
@bridge.ModuleGraphError::ReadError(msg)
| @bridge.ModuleGraphError::ParseError(msg)
| @bridge.ModuleGraphError::ResolveError(msg) => {
result.decl_js_ffi_failed += 1
benchmark_push_failure(result, "emit_moonbit_js_ffi", path, msg)
return
}
}
result.decl_js_ffi_ok += 1
}
None => {
result.decl_parse_failed += 1
benchmark_push_failure(result, "read_file", path, "Failed to read file")
}
}
}
///|
async fn benchmark_module_source_file(
path : String,
result : ProjectBenchmarkResult,
) -> Unit {
result.module_source_files += 1
let source = benchmark_read_file(path)
match source {
Some(source) => {
let allow_jsx = path.has_suffix(".tsx")
let parser = @parser.Parser::from_source_with_jsx(source, allow_jsx)
let _ = parser.parse_module_block() catch {
@parser.ParseError(msg) => {
result.module_parse_failed += 1
let error_pos = if parser.pos < parser.tokens.length() {
parser.tokens[parser.pos].pos
} else {
source.length()
}
benchmark_push_failure(
result,
"parse_module_block",
path,
benchmark_format_parse_detail(source, msg, error_pos),
)
return
}
}
result.module_parse_ok += 1
}
None => {
result.module_parse_failed += 1
benchmark_push_failure(result, "read_file", path, "Failed to read file")
}
}
}
///|
async fn benchmark_ts_project(
root : String,
limit? : Int = benchmark_project_default_limit(),
) -> ProjectBenchmarkResult {
let started_at_ms = @env.now()
let result = benchmark_result_new(root, limit, started_at_ms)
let files : Array[String] = []
collect_project_benchmark_files(root, files, limit)
result.total_files = files.length()
result.truncated = limit > 0 && files.length() >= limit
if files.length() == 0 {
let kind = @fs.kind(root, follow_symlink=false) catch {
_ => @fs.FileKind::Unknown
}
if kind is @fs.FileKind::Unknown {
benchmark_push_failure(
result, "scan", root, "Path not found or not readable",
)
}
result.finished_at_ms = @env.now()
return result
}
for path in files {
if project_benchmark_is_declaration_file(path) {
benchmark_declaration_file(path, root, result)
} else {
benchmark_module_source_file(path, result)
}
}
result.finished_at_ms = @env.now()
result
}
///|
async fn benchmark_project_corpora(
corpora : Array[ProjectBenchmarkCorpus],
limit? : Int = benchmark_project_default_limit(),
) -> Array[ProjectBenchmarkCorpusRun] {
let runs : Array[ProjectBenchmarkCorpusRun] = []
for corpus in corpora {
if corpus.optional && !project_benchmark_root_exists(corpus.root) {
continue
}
let result = benchmark_ts_project(corpus.root, limit~)
runs.push({ corpus, result })
}
runs
}
///|
async fn benchmark_known_corpora(
limit? : Int = benchmark_project_default_limit(),
) -> Array[ProjectBenchmarkCorpusRun] {
benchmark_project_corpora(known_project_benchmark_corpora(), limit~)
}
///|
fn render_project_benchmark_report(result : ProjectBenchmarkResult) -> String {
let sb = StringBuilder::new()
let elapsed_ms = result.finished_at_ms - result.started_at_ms
sb.write_string("=== Project Benchmark ===\n")
sb.write_string("Root: ")
sb.write_string(result.root)
sb.write_string("\n")
sb.write_string("Files scanned: ")
sb.write_string(result.total_files.to_string())
if result.limit > 0 {
sb.write_string(" (limit ")
sb.write_string(result.limit.to_string())
if result.truncated {
sb.write_string(", truncated")
}
sb.write_string(")")
}
sb.write_string("\n")
sb.write_string("Module source files: ")
sb.write_string(result.module_source_files.to_string())
sb.write_string("\n")
sb.write_string("Declaration files: ")
sb.write_string(result.declaration_files.to_string())
sb.write_string("\n")
sb.write_string("parse_module_block (.ts): ")
sb.write_string(result.module_parse_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(result.module_parse_failed.to_string())
sb.write_string(" failed\n")
sb.write_string("parse_module (.d.ts): ")
sb.write_string(result.decl_parse_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(result.decl_parse_failed.to_string())
sb.write_string(" failed\n")
sb.write_string("emit_moonbit_decl (.d.ts): ")
sb.write_string(result.decl_emit_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(result.decl_emit_failed.to_string())
sb.write_string(" failed\n")
sb.write_string("emit_moonbit_js_ffi (.d.ts): ")
sb.write_string(result.decl_js_ffi_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(result.decl_js_ffi_failed.to_string())
sb.write_string(" failed\n")
sb.write_string("Elapsed: ")
sb.write_string(elapsed_ms.to_string())
sb.write_string(" ms\n")
if result.failures.length() == 0 {
sb.write_string("Failures: none\n")
return sb.to_string()
}
sb.write_string("Failures:\n")
let mut shown = 0
for failure in result.failures {
if shown >= 10 {
break
}
sb.write_string("- [")
sb.write_string(failure.phase)
sb.write_string("] ")
sb.write_string(failure.path)
sb.write_string(": ")
sb.write_string(failure.detail)
sb.write_string("\n")
shown += 1
}
if result.failures.length() > shown {
sb.write_string("- ... ")
sb.write_string((result.failures.length() - shown).to_string())
sb.write_string(" more failures\n")
}
sb.to_string()
}
///|
fn render_project_benchmark_corpora_report(
runs : Array[ProjectBenchmarkCorpusRun],
) -> String {
let sb = StringBuilder::new()
sb.write_string("=== Benchmark Corpora ===\n")
if runs.length() == 0 {
sb.write_string("Corpora: none\n")
return sb.to_string()
}
let mut total_module_ok = 0
let mut total_module_failed = 0
let mut total_decl_ok = 0
let mut total_decl_failed = 0
let mut total_emit_ok = 0
let mut total_emit_failed = 0
let mut total_ffi_ok = 0
let mut total_ffi_failed = 0
let mut total_elapsed_ms : UInt64 = 0
sb.write_string("Corpora:\n")
for run in runs {
let elapsed_ms = run.result.finished_at_ms - run.result.started_at_ms
total_module_ok += run.result.module_parse_ok
total_module_failed += run.result.module_parse_failed
total_decl_ok += run.result.decl_parse_ok
total_decl_failed += run.result.decl_parse_failed
total_emit_ok += run.result.decl_emit_ok
total_emit_failed += run.result.decl_emit_failed
total_ffi_ok += run.result.decl_js_ffi_ok
total_ffi_failed += run.result.decl_js_ffi_failed
total_elapsed_ms += elapsed_ms
sb.write_string("- ")
sb.write_string(run.corpus.name)
sb.write_string(": ")
sb.write_string(run.corpus.root)
sb.write_string("\n")
sb.write_string(" parse_module_block (.ts): ")
sb.write_string(run.result.module_parse_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(run.result.module_parse_failed.to_string())
sb.write_string(" failed\n")
sb.write_string(" parse_module (.d.ts): ")
sb.write_string(run.result.decl_parse_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(run.result.decl_parse_failed.to_string())
sb.write_string(" failed\n")
sb.write_string(" emit_moonbit_decl (.d.ts): ")
sb.write_string(run.result.decl_emit_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(run.result.decl_emit_failed.to_string())
sb.write_string(" failed\n")
sb.write_string(" emit_moonbit_js_ffi (.d.ts): ")
sb.write_string(run.result.decl_js_ffi_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(run.result.decl_js_ffi_failed.to_string())
sb.write_string(" failed\n")
sb.write_string(" elapsed: ")
sb.write_string(elapsed_ms.to_string())
sb.write_string(" ms\n")
}
sb.write_string("Summary:\n")
sb.write_string("- parse_module_block (.ts): ")
sb.write_string(total_module_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(total_module_failed.to_string())
sb.write_string(" failed\n")
sb.write_string("- parse_module (.d.ts): ")
sb.write_string(total_decl_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(total_decl_failed.to_string())
sb.write_string(" failed\n")
sb.write_string("- emit_moonbit_decl (.d.ts): ")
sb.write_string(total_emit_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(total_emit_failed.to_string())
sb.write_string(" failed\n")
sb.write_string("- emit_moonbit_js_ffi (.d.ts): ")
sb.write_string(total_ffi_ok.to_string())
sb.write_string(" ok / ")
sb.write_string(total_ffi_failed.to_string())
sb.write_string(" failed\n")
sb.write_string("- elapsed: ")
sb.write_string(total_elapsed_ms.to_string())
sb.write_string(" ms\n")
sb.to_string()
}
///|
fn parse_project_benchmark_limit(raw : String) -> Int? {
let parsed = @string.parse_int(raw) catch { _ => return None }
if parsed < 0 {
None
} else {
Some(parsed)
}
}
///|
async fn run_project_benchmark(root : String, limit : Int) -> Unit {
let result = benchmark_ts_project(root, limit~)
println(render_project_benchmark_report(result))
}
///|
async fn run_benchmark_corpora(limit : Int) -> Unit {
let runs = benchmark_known_corpora(limit~)
println(render_project_benchmark_corpora_report(runs))
}