nandi/code-transpiler-rust-frontend-findingspublic Fork 0
5375d17
Commits
Clone
git clone https://git.rickub.com/nandi/code-transpiler-rust-frontend-findings.git
git clone ssh://git@rickub.com/nandi/code-transpiler-rust-frontend-findings.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Reproducible bug report for the Rust frontend in Code-Transpiler

Rust is listed as a supported source language, but cannot be used as one,
and the CLI does not report this: the default -runtime fallback writes an
empty target file and exits 0.

Four defects, each with a minimal case:

  0. internal/backend/parser.go recognises # comments only, so a Rust //
     becomes two division operators
  1. internal/matrixir/lexical.go:261 treats ' as a string opener, so
     lifetimes desync delimiter pairing
  2. frontend_fact_parser.go:387 requires if ( ... ), so Rust if-blocks
     cannot parse; the emitted type contract is R regardless of input
  3. fn is never recognised as a declaration -- fn f(a: i32) parses as a
     call to f, leaving no function nodes for the emitter

repro.sh clones upstream, builds the CLI, and runs every case through both
the CLI and a UAST node-kind prober. EXPECTED.md records the output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-18T18:35:40-07:00 Browse files
5375d17
added .gitignore +1 -0
new file mode 100644
@@ -0,0 +1 @@
1+.work/
new file mode 100644
@@ -0,0 +1 @@
1+.work/
added EXPECTED.md +32 -0
new file mode 100644
@@ -0,0 +1,32 @@
1+# Expected output of ./repro.sh
2+
3+Recorded against upstream `main` as of 2026-09-18, Go 1.27.1, linux/amd64.
4+
5+```
6+=== CLI: rust -> nim (exit code, then stderr tail) ===
7+00-comment-fails.rs exit=0 <no error> emitted:
8+00-comment.rs exit=0 <no error> emitted:
9+01-trivial.rs exit=0 <no error> emitted:
10+02-if-block.rs exit=0 <no error> emitted:
11+03-macro.rs exit=0 <no error> emitted:
12+04-lifetime.rs exit=1 r2many: unmatched delimiter ")" at 47
13+
14+=== Frontend prober: UAST node kinds actually produced ===
15+00-comment-fails.rs err: expected expression near "/"
16+00-comment.rs node kinds: map[CALL:1 CONTROL:1 DISPATCH:1 EFFECT:3 MEMORY:1 SYMBOL:2 block:4 call:2 expression:4 identifier:4 lvalue:1 program:1 temporary:1 unknown:9]
17+01-trivial.rs node kinds: map[CALL:1 CONTROL:2 DISPATCH:2 EFFECT:4 MEMORY:1 SYMBOL:7 assign:4 binary:2 block:4 builtin:1 call:2 expression:8 identifier:16 lvalue:1 program:1 temporary:1 unknown:20]
18+02-if-block.rs err: expected "" near "s"
19+03-macro.rs err: expected "" near ","
20+04-lifetime.rs err: expected expression near ")"
21+05-equivalent.R node kinds: map[CLOSURE:1 EFFECT:3 FUNCTION:1 MEMORY:1 OWNERSHIP:1 STORAGE:1 SYMBOL:2 assign:4 block:4 expression:2 function:5 identifier:4 lvalue:1 parameter:2 program:1 unknown:11]
22+```
23+
24+## Reading this
25+
26+- `emitted:` blank means the Nim file is **one byte: a newline**. A complete,
27+ empty program, written on exit 0.
28+- `00-comment.rs` vs `00-comment-fails.rs` differ only by one `//` line.
29+- `01-trivial.rs` reports **no error** and produces `call:2 CALL:1` with no
30+ function node: `fn f(...)` was read as a call to `f`.
31+- The last two lines are the same program in Rust and in R. Only R yields
32+ `FUNCTION:1 CLOSURE:1 function:5 parameter:2`.
new file mode 100644
@@ -0,0 +1,32 @@
1+# Expected output of ./repro.sh
2+
3+Recorded against upstream `main` as of 2026-09-18, Go 1.27.1, linux/amd64.
4+
5+```
6+=== CLI: rust -> nim (exit code, then stderr tail) ===
7+00-comment-fails.rs exit=0 <no error> emitted:
8+00-comment.rs exit=0 <no error> emitted:
9+01-trivial.rs exit=0 <no error> emitted:
10+02-if-block.rs exit=0 <no error> emitted:
11+03-macro.rs exit=0 <no error> emitted:
12+04-lifetime.rs exit=1 r2many: unmatched delimiter ")" at 47
13+
14+=== Frontend prober: UAST node kinds actually produced ===
15+00-comment-fails.rs err: expected expression near "/"
16+00-comment.rs node kinds: map[CALL:1 CONTROL:1 DISPATCH:1 EFFECT:3 MEMORY:1 SYMBOL:2 block:4 call:2 expression:4 identifier:4 lvalue:1 program:1 temporary:1 unknown:9]
17+01-trivial.rs node kinds: map[CALL:1 CONTROL:2 DISPATCH:2 EFFECT:4 MEMORY:1 SYMBOL:7 assign:4 binary:2 block:4 builtin:1 call:2 expression:8 identifier:16 lvalue:1 program:1 temporary:1 unknown:20]
18+02-if-block.rs err: expected "" near "s"
19+03-macro.rs err: expected "" near ","
20+04-lifetime.rs err: expected expression near ")"
21+05-equivalent.R node kinds: map[CLOSURE:1 EFFECT:3 FUNCTION:1 MEMORY:1 OWNERSHIP:1 STORAGE:1 SYMBOL:2 assign:4 block:4 expression:2 function:5 identifier:4 lvalue:1 parameter:2 program:1 unknown:11]
22+```
23+
24+## Reading this
25+
26+- `emitted:` blank means the Nim file is **one byte: a newline**. A complete,
27+ empty program, written on exit 0.
28+- `00-comment.rs` vs `00-comment-fails.rs` differ only by one `//` line.
29+- `01-trivial.rs` reports **no error** and produces `call:2 CALL:1` with no
30+ function node: `fn f(...)` was read as a call to `f`.
31+- The last two lines are the same program in Rust and in R. Only R yields
32+ `FUNCTION:1 CLOSURE:1 function:5 parameter:2`.
added LICENSE +19 -0
new file mode 100644
@@ -0,0 +1,19 @@
1+MIT License
2+
3+Permission is hereby granted, free of charge, to any person obtaining a copy
4+of this software and associated documentation files (the "Software"), to deal
5+in the Software without restriction, including without limitation the rights
6+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+copies of the Software, and to permit persons to whom the Software is
8+furnished to do so, subject to the following conditions:
9+
10+The above copyright notice and this permission notice shall be included in all
11+copies or substantial portions of the Software.
12+
13+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+SOFTWARE.
new file mode 100644
@@ -0,0 +1,19 @@
1+MIT License
2+
3+Permission is hereby granted, free of charge, to any person obtaining a copy
4+of this software and associated documentation files (the "Software"), to deal
5+in the Software without restriction, including without limitation the rights
6+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+copies of the Software, and to permit persons to whom the Software is
8+furnished to do so, subject to the following conditions:
9+
10+The above copyright notice and this permission notice shall be included in all
11+copies or substantial portions of the Software.
12+
13+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+SOFTWARE.
added README.md +151 -0
new file mode 100644
@@ -0,0 +1,151 @@
1+# Rust → Nim in Code-Transpiler: what actually happens
2+
3+A reproducible bug report for [tarekwasfy01/Code-Transpiler](https://github.com/tarekwasfy01/Code-Transpiler),
4+which lists `rust` as a supported source language.
5+
6+**Summary: Rust cannot be used as a source language, and the CLI does not say so.**
7+Most real Rust fails in the lexer. The rest parses into a tree containing no
8+functions, and the CLI writes an empty-but-valid target file and exits `0`.
9+
10+Nothing here is a criticism of the project's R-derived core, which works. The
11+issue is specifically the Rust frontend, and the silent failure that hides it.
12+
13+## Reproduce
14+
15+```bash
16+./repro.sh
17+```
18+
19+Clones upstream, builds `cmd/r2many`, runs the minimal cases through the CLI,
20+then re-runs them through a prober that reports which UAST node kinds the
21+frontend actually produced. Needs Go 1.26+ and network access. Recorded output:
22+[`EXPECTED.md`](EXPECTED.md).
23+
24+## What was tried first
25+
26+A random real crate: `base16ct` 1.0.0 — 613 lines, pure Rust, `no_std`, no
27+dependencies. Five of its six source files failed to parse. The sixth, which
28+contains three functions, transpiled to this complete Nim program:
29+
30+```nim
31+{
32+}
33+```
34+
35+For the minimal cases in `cases/`, the emitted Nim file is even smaller: one
36+byte, a newline. Exit code 0 in both situations.
37+
38+Full transcript: [`outputs/base16ct-1.0.0-transcript.txt`](outputs/base16ct-1.0.0-transcript.txt).
39+
40+## The four defects
41+
42+### 0. `//` line comments are not supported
43+
44+[`internal/backend/parser.go:157`](https://github.com/tarekwasfy01/Code-Transpiler/blob/main/internal/backend/parser.go#L157)
45+
46+This tokenizer recognises `#` comments only, alongside R operators (`<<-`,
47+`%in%`, `[[`). A Rust `//` becomes two division operators:
48+
49+```
50+expected expression near "/"
51+```
52+
53+[`cases/00-comment.rs`](cases/00-comment.rs) and
54+[`cases/00-comment-fails.rs`](cases/00-comment-fails.rs) differ by exactly one
55+comment line, and only the second fails. Every file in a real crate has
56+comments, `base16ct` included — doc comments alone account for much of why it
57+never reaches the later stages.
58+
59+### 1. `'` is always a string opener, so lifetimes break the lexer
60+
61+[`internal/matrixir/lexical.go:261`](https://github.com/tarekwasfy01/Code-Transpiler/blob/main/internal/matrixir/lexical.go#L261)
62+
63+```go
64+if c == '"' || c == '\'' || c == '`' {
65+```
66+
67+`<'a>` and `Formatter<'_>` are consumed as unterminated char literals. The
68+delimiter pairing in `AnalyzeTokenStructure` then desyncs, which is why the
69+reported error is a bracket complaint at an offset that always turns out to be
70+a lifetime. Case: [`cases/04-lifetime.rs`](cases/04-lifetime.rs).
71+
72+This one looks genuinely small to fix: for `source == "rust"`, treat `'` as a
73+literal only when it matches `'\?.'`, and emit a lifetime token otherwise.
74+
75+### 2. The Rust parser is the R parser
76+
77+[`internal/backend/frontend_fact_parser.go:387`](https://github.com/tarekwasfy01/Code-Transpiler/blob/main/internal/backend/frontend_fact_parser.go#L387)
78+
79+```go
80+func (p *factParser) parseIf() (ParsedNode, error) {
81+ p.next()
82+ if _, e := p.expect(tokLParen, ""); e != nil { // requires if ( ... )
83+```
84+
85+This is a second, separate tokenizer from the one in defect 1 — the two stages
86+disagree — but both are R's. Parenthesised conditions are mandatory, so Rust's `if cond { }` can never
87+parse — it fails with `expected "" near "<first token of cond>"`. Case:
88+[`cases/02-if-block.rs`](cases/02-if-block.rs). Macro invocations are likewise
89+unhandled ([`cases/03-macro.rs`](cases/03-macro.rs)).
90+
91+The emitted semantic contract is hardcoded R for a file declared as Rust:
92+
93+```json
94+"language_profile": "rust",
95+"value_model": "tagged_dynamic_binary64",
96+"index_base": 1,
97+"type_contract": { "truth": "r_compatible", "ownership": "unknown" }
98+```
99+
100+### 3. `fn` is never recognised as a declaration
101+
102+This is the one that yields empty output. Node kinds produced for two
103+equivalent programs, via [`prober/main.go`](prober/main.go):
104+
105+| input | function nodes |
106+|---|---|
107+| [`cases/05-equivalent.R`](cases/05-equivalent.R) — `f <- function(a) {…}` | `FUNCTION:1, CLOSURE:1, function:5, parameter:2` |
108+| [`cases/01-trivial.rs`](cases/01-trivial.rs) — `fn f(a: i32) -> i32 {…}` | **none**`call:2, CALL:1` |
109+
110+`fn f(a: i32)` is parsed as a *call* to `f`, with `fn` left as a stray
111+identifier. The tree holds no functions, so the Nim emitter correctly emits
112+nothing. Note that case 01 reports **no parse error** — it is the silent path.
113+
114+## The silent-failure path
115+
116+`-runtime` defaults to `true`. The fallback catches the parse failure, writes a
117+syntactically valid empty program, and exits `0`. The machinery to report this
118+already exists and is correct when asked:
119+
120+```
121+-native strict native frontend for "rust" is not implemented
122+-no-runtime 1/1 translations failed: [{nim DIRECT_NATIVE_UNAVAILABLE (stage=direct)}]
123+(default) exit 0, empty file
124+```
125+
126+Refusing to emit a program with zero functions when the input declared some
127+would turn a silent wrong answer into an honest error, without touching the
128+frontend at all.
129+
130+## Scope of a real fix
131+
132+Defects 0 and 1 are patches. Defects 2 and 3 are not: there is no Rust frontend to
133+repair. Supporting Rust means writing one — items, generics, lifetimes,
134+`impl`/traits, `match`, macros, `?` — and lowering it into a semantic model
135+that isn't R-shaped, since the type contract is R throughout. That is
136+comparable in size to the existing ~1,400-line R frontend, and probably larger.
137+
138+## Contents
139+
140+```
141+cases/ minimal inputs, one per defect, plus the R control
142+prober/ drop-in cmd/dbg that prints UAST node-kind counts
143+outputs/ the base16ct run: transcript and the one emitted .nim
144+repro.sh clone, build, run everything
145+```
146+
147+`prober/main.go` imports `internal/backend`, so it only compiles from inside
148+the upstream tree; `repro.sh` copies it to `cmd/dbg` for you.
149+
150+Upstream is MIT. No upstream or crate source is vendored here — `repro.sh`
151+fetches what it needs.
new file mode 100644
@@ -0,0 +1,151 @@
1+# Rust → Nim in Code-Transpiler: what actually happens
2+
3+A reproducible bug report for [tarekwasfy01/Code-Transpiler](https://github.com/tarekwasfy01/Code-Transpiler),
4+which lists `rust` as a supported source language.
5+
6+**Summary: Rust cannot be used as a source language, and the CLI does not say so.**
7+Most real Rust fails in the lexer. The rest parses into a tree containing no
8+functions, and the CLI writes an empty-but-valid target file and exits `0`.
9+
10+Nothing here is a criticism of the project's R-derived core, which works. The
11+issue is specifically the Rust frontend, and the silent failure that hides it.
12+
13+## Reproduce
14+
15+```bash
16+./repro.sh
17+```
18+
19+Clones upstream, builds `cmd/r2many`, runs the minimal cases through the CLI,
20+then re-runs them through a prober that reports which UAST node kinds the
21+frontend actually produced. Needs Go 1.26+ and network access. Recorded output:
22+[`EXPECTED.md`](EXPECTED.md).
23+
24+## What was tried first
25+
26+A random real crate: `base16ct` 1.0.0 — 613 lines, pure Rust, `no_std`, no
27+dependencies. Five of its six source files failed to parse. The sixth, which
28+contains three functions, transpiled to this complete Nim program:
29+
30+```nim
31+{
32+}
33+```
34+
35+For the minimal cases in `cases/`, the emitted Nim file is even smaller: one
36+byte, a newline. Exit code 0 in both situations.
37+
38+Full transcript: [`outputs/base16ct-1.0.0-transcript.txt`](outputs/base16ct-1.0.0-transcript.txt).
39+
40+## The four defects
41+
42+### 0. `//` line comments are not supported
43+
44+[`internal/backend/parser.go:157`](https://github.com/tarekwasfy01/Code-Transpiler/blob/main/internal/backend/parser.go#L157)
45+
46+This tokenizer recognises `#` comments only, alongside R operators (`<<-`,
47+`%in%`, `[[`). A Rust `//` becomes two division operators:
48+
49+```
50+expected expression near "/"
51+```
52+
53+[`cases/00-comment.rs`](cases/00-comment.rs) and
54+[`cases/00-comment-fails.rs`](cases/00-comment-fails.rs) differ by exactly one
55+comment line, and only the second fails. Every file in a real crate has
56+comments, `base16ct` included — doc comments alone account for much of why it
57+never reaches the later stages.
58+
59+### 1. `'` is always a string opener, so lifetimes break the lexer
60+
61+[`internal/matrixir/lexical.go:261`](https://github.com/tarekwasfy01/Code-Transpiler/blob/main/internal/matrixir/lexical.go#L261)
62+
63+```go
64+if c == '"' || c == '\'' || c == '`' {
65+```
66+
67+`<'a>` and `Formatter<'_>` are consumed as unterminated char literals. The
68+delimiter pairing in `AnalyzeTokenStructure` then desyncs, which is why the
69+reported error is a bracket complaint at an offset that always turns out to be
70+a lifetime. Case: [`cases/04-lifetime.rs`](cases/04-lifetime.rs).
71+
72+This one looks genuinely small to fix: for `source == "rust"`, treat `'` as a
73+literal only when it matches `'\?.'`, and emit a lifetime token otherwise.
74+
75+### 2. The Rust parser is the R parser
76+
77+[`internal/backend/frontend_fact_parser.go:387`](https://github.com/tarekwasfy01/Code-Transpiler/blob/main/internal/backend/frontend_fact_parser.go#L387)
78+
79+```go
80+func (p *factParser) parseIf() (ParsedNode, error) {
81+ p.next()
82+ if _, e := p.expect(tokLParen, ""); e != nil { // requires if ( ... )
83+```
84+
85+This is a second, separate tokenizer from the one in defect 1 — the two stages
86+disagree — but both are R's. Parenthesised conditions are mandatory, so Rust's `if cond { }` can never
87+parse — it fails with `expected "" near "<first token of cond>"`. Case:
88+[`cases/02-if-block.rs`](cases/02-if-block.rs). Macro invocations are likewise
89+unhandled ([`cases/03-macro.rs`](cases/03-macro.rs)).
90+
91+The emitted semantic contract is hardcoded R for a file declared as Rust:
92+
93+```json
94+"language_profile": "rust",
95+"value_model": "tagged_dynamic_binary64",
96+"index_base": 1,
97+"type_contract": { "truth": "r_compatible", "ownership": "unknown" }
98+```
99+
100+### 3. `fn` is never recognised as a declaration
101+
102+This is the one that yields empty output. Node kinds produced for two
103+equivalent programs, via [`prober/main.go`](prober/main.go):
104+
105+| input | function nodes |
106+|---|---|
107+| [`cases/05-equivalent.R`](cases/05-equivalent.R) — `f <- function(a) {…}` | `FUNCTION:1, CLOSURE:1, function:5, parameter:2` |
108+| [`cases/01-trivial.rs`](cases/01-trivial.rs) — `fn f(a: i32) -> i32 {…}` | **none**`call:2, CALL:1` |
109+
110+`fn f(a: i32)` is parsed as a *call* to `f`, with `fn` left as a stray
111+identifier. The tree holds no functions, so the Nim emitter correctly emits
112+nothing. Note that case 01 reports **no parse error** — it is the silent path.
113+
114+## The silent-failure path
115+
116+`-runtime` defaults to `true`. The fallback catches the parse failure, writes a
117+syntactically valid empty program, and exits `0`. The machinery to report this
118+already exists and is correct when asked:
119+
120+```
121+-native strict native frontend for "rust" is not implemented
122+-no-runtime 1/1 translations failed: [{nim DIRECT_NATIVE_UNAVAILABLE (stage=direct)}]
123+(default) exit 0, empty file
124+```
125+
126+Refusing to emit a program with zero functions when the input declared some
127+would turn a silent wrong answer into an honest error, without touching the
128+frontend at all.
129+
130+## Scope of a real fix
131+
132+Defects 0 and 1 are patches. Defects 2 and 3 are not: there is no Rust frontend to
133+repair. Supporting Rust means writing one — items, generics, lifetimes,
134+`impl`/traits, `match`, macros, `?` — and lowering it into a semantic model
135+that isn't R-shaped, since the type contract is R throughout. That is
136+comparable in size to the existing ~1,400-line R frontend, and probably larger.
137+
138+## Contents
139+
140+```
141+cases/ minimal inputs, one per defect, plus the R control
142+prober/ drop-in cmd/dbg that prints UAST node-kind counts
143+outputs/ the base16ct run: transcript and the one emitted .nim
144+repro.sh clone, build, run everything
145+```
146+
147+`prober/main.go` imports `internal/backend`, so it only compiles from inside
148+the upstream tree; `repro.sh` copies it to `cmd/dbg` for you.
149+
150+Upstream is MIT. No upstream or crate source is vendored here — `repro.sh`
151+fetches what it needs.
added cases/00-comment-fails.rs +2 -0
new file mode 100644
@@ -0,0 +1,2 @@
1+// A plain Rust line comment.
2+fn f() { }
new file mode 100644
@@ -0,0 +1,2 @@
1+// A plain Rust line comment.
2+fn f() { }
added cases/00-comment.rs +1 -0
new file mode 100644
@@ -0,0 +1 @@
1+fn f() { }
new file mode 100644
@@ -0,0 +1 @@
1+fn f() { }
added cases/01-trivial.rs +4 -0
new file mode 100644
@@ -0,0 +1,4 @@
1+fn f(a: i32) -> i32 {
2+ let mut s = a;
3+ s
4+}
new file mode 100644
@@ -0,0 +1,4 @@
1+fn f(a: i32) -> i32 {
2+ let mut s = a;
3+ s
4+}
added cases/02-if-block.rs +7 -0
new file mode 100644
@@ -0,0 +1,7 @@
1+fn f(a: i32) -> i32 {
2+ let mut s = a;
3+ if s < 0 {
4+ s = -s;
5+ }
6+ s
7+}
new file mode 100644
@@ -0,0 +1,7 @@
1+fn f(a: i32) -> i32 {
2+ let mut s = a;
3+ if s < 0 {
4+ s = -s;
5+ }
6+ s
7+}
added cases/03-macro.rs +3 -0
new file mode 100644
@@ -0,0 +1,3 @@
1+fn main() {
2+ println!("{}", 1);
3+}
new file mode 100644
@@ -0,0 +1,3 @@
1+fn main() {
2+ println!("{}", 1);
3+}
added cases/04-lifetime.rs +3 -0
new file mode 100644
@@ -0,0 +1,3 @@
1+pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> &'a [u8] {
2+ dst
3+}
new file mode 100644
@@ -0,0 +1,3 @@
1+pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> &'a [u8] {
2+ dst
3+}
added cases/05-equivalent.R +4 -0
new file mode 100644
@@ -0,0 +1,4 @@
1+f <- function(a) {
2+ s <- a
3+ s
4+}
new file mode 100644
@@ -0,0 +1,4 @@
1+f <- function(a) {
2+ s <- a
3+ s
4+}
added outputs/base16ct-1.0.0-transcript.txt +28 -0
new file mode 100644
@@ -0,0 +1,28 @@
1+Crate: base16ct 1.0.0 (crates.io), 613 LOC, pure Rust, no_std
2+Command: r2many transpile -from rust -to nim <file>.rs -o <file>.nim
3+Build: CGO_ENABLED=0 go build ./cmd/r2many (succeeds)
4+
5+src/lib.rs unmatched delimiter ")" at 3346 -> no file written
6+src/error.rs unterminated string at 411 -> no file written
7+src/display.rs unmatched delimiter ")" at 174 -> no file written
8+src/lower.rs unmatched delimiter ")" at 815 -> no file written
9+src/upper.rs unmatched delimiter "(" at 817 -> no file written
10+src/mixed.rs <no error, exit 0> -> mixed.nim (see below)
11+
12+Every failing offset lands on a lifetime:
13+ error.rs:411 fmt::Formatter<'_>
14+ display.rs:174 pub struct HexDisplay<'a>(pub &'a [u8]);
15+ lower.rs:815 pub fn encode<'a>(src: &[u8], dst: &'a mut [u8])
16+
17+mixed.rs contains three functions (decode, decode_vec, decode_nibble).
18+The complete emitted Nim program was:
19+
20+ {
21+ }
22+
23+Flag behaviour on the same input:
24+ -native -> strict native frontend for "rust" is not implemented
25+ -no-runtime -> 1/1 translations failed: [{nim DIRECT_NATIVE_UNAVAILABLE (stage=direct)}]
26+ (default) -> exit 0, empty program written
27+
28+Control: R -> Nim on the project's original source language does emit real code.
new file mode 100644
@@ -0,0 +1,28 @@
1+Crate: base16ct 1.0.0 (crates.io), 613 LOC, pure Rust, no_std
2+Command: r2many transpile -from rust -to nim <file>.rs -o <file>.nim
3+Build: CGO_ENABLED=0 go build ./cmd/r2many (succeeds)
4+
5+src/lib.rs unmatched delimiter ")" at 3346 -> no file written
6+src/error.rs unterminated string at 411 -> no file written
7+src/display.rs unmatched delimiter ")" at 174 -> no file written
8+src/lower.rs unmatched delimiter ")" at 815 -> no file written
9+src/upper.rs unmatched delimiter "(" at 817 -> no file written
10+src/mixed.rs <no error, exit 0> -> mixed.nim (see below)
11+
12+Every failing offset lands on a lifetime:
13+ error.rs:411 fmt::Formatter<'_>
14+ display.rs:174 pub struct HexDisplay<'a>(pub &'a [u8]);
15+ lower.rs:815 pub fn encode<'a>(src: &[u8], dst: &'a mut [u8])
16+
17+mixed.rs contains three functions (decode, decode_vec, decode_nibble).
18+The complete emitted Nim program was:
19+
20+ {
21+ }
22+
23+Flag behaviour on the same input:
24+ -native -> strict native frontend for "rust" is not implemented
25+ -no-runtime -> 1/1 translations failed: [{nim DIRECT_NATIVE_UNAVAILABLE (stage=direct)}]
26+ (default) -> exit 0, empty program written
27+
28+Control: R -> Nim on the project's original source language does emit real code.
added outputs/mixed.nim +3 -0
new file mode 100644
@@ -0,0 +1,3 @@
1+
2+{
3+}
new file mode 100644
@@ -0,0 +1,3 @@
1+
2+{
3+}
added prober/main.go +26 -0
new file mode 100644
@@ -0,0 +1,26 @@
1+package main
2+
3+import (
4+ "encoding/json"
5+ "fmt"
6+ "os"
7+ "regexp"
8+
9+ "github.com/tarekwasfy01/Code-Transpiler/v2/internal/backend"
10+)
11+
12+func main() {
13+ code, _ := os.ReadFile(os.Args[2])
14+ p, err := backend.ParseSemantic(os.Args[1], string(code))
15+ fmt.Println("err:", err)
16+ if p == nil || p.UniversalAST == nil {
17+ return
18+ }
19+ b, _ := json.Marshal(p.UniversalAST)
20+ re := regexp.MustCompile(`"kind":"([A-Za-z]+)"`)
21+ counts := map[string]int{}
22+ for _, m := range re.FindAllStringSubmatch(string(b), -1) {
23+ counts[m[1]]++
24+ }
25+ fmt.Println("node kinds:", counts)
26+}
new file mode 100644
@@ -0,0 +1,26 @@
1+package main
2+
3+import (
4+ "encoding/json"
5+ "fmt"
6+ "os"
7+ "regexp"
8+
9+ "github.com/tarekwasfy01/Code-Transpiler/v2/internal/backend"
10+)
11+
12+func main() {
13+ code, _ := os.ReadFile(os.Args[2])
14+ p, err := backend.ParseSemantic(os.Args[1], string(code))
15+ fmt.Println("err:", err)
16+ if p == nil || p.UniversalAST == nil {
17+ return
18+ }
19+ b, _ := json.Marshal(p.UniversalAST)
20+ re := regexp.MustCompile(`"kind":"([A-Za-z]+)"`)
21+ counts := map[string]int{}
22+ for _, m := range re.FindAllStringSubmatch(string(b), -1) {
23+ counts[m[1]]++
24+ }
25+ fmt.Println("node kinds:", counts)
26+}
added repro.sh +35 -0
new file mode 100755
@@ -0,0 +1,35 @@
1+#!/usr/bin/env bash
2+# Reproduce every finding. Requires Go 1.26+ and network access.
3+set -u
4+here=$(cd "$(dirname "$0")" && pwd)
5+work=${1:-$here/.work}
6+mkdir -p "$work" && cd "$work"
7+
8+[ -d Code-Transpiler ] || git clone --depth 1 https://github.com/tarekwasfy01/Code-Transpiler
9+cd Code-Transpiler
10+export TMPDIR=$PWD/.tmp && mkdir -p .tmp
11+
12+echo "=== Building CLI ==="
13+CGO_ENABLED=0 go build -o "$work/r2many" ./cmd/r2many || exit 1
14+
15+echo
16+echo "=== CLI: rust -> nim (exit code, then stderr tail) ==="
17+for f in "$here"/cases/*.rs; do
18+ out=$work/$(basename "$f" .rs).nim
19+ rm -f "$out"
20+ msg=$("$work/r2many" transpile -from rust -to nim "$f" -o "$out" 2>&1)
21+ code=$?
22+ printf '%-24s exit=%s %s' "$(basename "$f")" "$code" "$(printf '%s' "$msg" | tail -1)"
23+ [ -n "$msg" ] || printf '<no error>'
24+ if [ -f "$out" ]; then printf ' emitted: %s' "$(tr -s '\n' ' ' < "$out")"; fi
25+ printf '\n'
26+done
27+
28+echo
29+echo "=== Frontend prober: UAST node kinds actually produced ==="
30+mkdir -p cmd/dbg && cp "$here/prober/main.go" cmd/dbg/main.go
31+for f in "$here"/cases/*.rs; do
32+ printf '%-24s ' "$(basename "$f")"; go run ./cmd/dbg rust "$f" 2>&1 | tail -1
33+done
34+printf '%-24s ' "05-equivalent.R"
35+go run ./cmd/dbg r "$here/cases/05-equivalent.R" 2>&1 | tail -1
new file mode 100755
@@ -0,0 +1,35 @@
1+#!/usr/bin/env bash
2+# Reproduce every finding. Requires Go 1.26+ and network access.
3+set -u
4+here=$(cd "$(dirname "$0")" && pwd)
5+work=${1:-$here/.work}
6+mkdir -p "$work" && cd "$work"
7+
8+[ -d Code-Transpiler ] || git clone --depth 1 https://github.com/tarekwasfy01/Code-Transpiler
9+cd Code-Transpiler
10+export TMPDIR=$PWD/.tmp && mkdir -p .tmp
11+
12+echo "=== Building CLI ==="
13+CGO_ENABLED=0 go build -o "$work/r2many" ./cmd/r2many || exit 1
14+
15+echo
16+echo "=== CLI: rust -> nim (exit code, then stderr tail) ==="
17+for f in "$here"/cases/*.rs; do
18+ out=$work/$(basename "$f" .rs).nim
19+ rm -f "$out"
20+ msg=$("$work/r2many" transpile -from rust -to nim "$f" -o "$out" 2>&1)
21+ code=$?
22+ printf '%-24s exit=%s %s' "$(basename "$f")" "$code" "$(printf '%s' "$msg" | tail -1)"
23+ [ -n "$msg" ] || printf '<no error>'
24+ if [ -f "$out" ]; then printf ' emitted: %s' "$(tr -s '\n' ' ' < "$out")"; fi
25+ printf '\n'
26+done
27+
28+echo
29+echo "=== Frontend prober: UAST node kinds actually produced ==="
30+mkdir -p cmd/dbg && cp "$here/prober/main.go" cmd/dbg/main.go
31+for f in "$here"/cases/*.rs; do
32+ printf '%-24s ' "$(basename "$f")"; go run ./cmd/dbg rust "$f" 2>&1 | tail -1
33+done
34+printf '%-24s ' "05-equivalent.R"
35+go run ./cmd/dbg r "$here/cases/05-equivalent.R" 2>&1 | tail -1