Fork me on GitHub

R, embedded in Go.
Checked against GNU R 4.6.2, never against guesses.

go-embedded-r is in development: v0.5.0 (2026-11-05) completes milestones M0 (the oracle harness, against GNU R 4.6.2) and M1 (the parser and deparse, package syntax), identical to R's tree, tokens and deparse output on 842 of 842 files of R's base packages, and slices 1 to 3 of M2: the evaluator (package eval) has 102 of R's 210 primitives and 94 base closures, with replacement, subsetting, attributes, S3 dispatch, conditions, print, environments as values, the apply family, string functions and regular expressions, and 1 035 of 1 035 goldens match R 4.6.2. Regular expressions are an approximation on Go's regexp, not TRE (lazy quantifiers, capture priority in repeated groups and \</\> differ from R; back-references are refused); a faithful TRE port is in progress for v0.6.0. Since v0.4.0 a session is bounded by default (a breaking change): 30 000 000 steps, 16 GiB of cumulative allocation and 10 s per evaluation, with eval.Unbounded to opt out. Base, stats, regex, graphics, the notebook bridge, the playground and CRAN ports (M3 to M8) are planned. R cells are live in go-notebook v0.8.0 and playground v0.9.0: parsed and checked, not run yet. It will be a pure-Go, embeddable R interpreter for the r cells of go-notebook and its WebAssembly playground, written clean-room under the BSD-3-Clause licence.

v0.5.0: M0, M1 doneM2 slices 1–3 doneM3 – M8 plannedpure Go
status
release   v0.5.0 (2026-11-05)
done      M0 oracle harness (R 4.6.2)
done      M1 parser + deparse: 842/842
done      M2 slices 1-3: 102/210 prims
          + 94 closures, 1035/1035 goldens
caveat    regex: not TRE (Go regexp)
planned   M3 - M8 base ... CRAN
goal      r cells in go-notebook
oracle    GNU R 4.6.2, a black box
licence   BSD-3-Clause, clean room
plan      github.com/go-scikit/roadmap
2 repositories — released v0.5.0: the oracle harness, the parser and deparse (M0 and M1 done) and slices 1 to 3 of M2, the evaluator; M3 to M8 are planned
What it is

R's semantics, written in Go.

What exists is a parser, an oracle harness and the first three slices of the evaluator. The rest is the plan of the roadmap's bibliography, not a result.

An R interpreter in pure Go

github.com/go-embedded-r/r is the start of an R implementation written in Go (CGO_ENABLED=0), meant to be embedded in Go programs and to run in the browser through WebAssembly.

For go-notebook

It will back the r cells of go-notebook, the way julia backs its Julia cells and rbgo its Ruby cells.

R is the oracle

Every stage is checked against the reference implementation, the pinned GNU R 4.6.2 (CRAN tarball, SVN revision 90784), never against expectations written by hand. R runs as a black box and what it prints is recorded.

R cells in go-notebook, parsed not run

R cells are live in go-notebook v0.8.0 and playground v0.9.0: they are parsed and checked, not run yet. Running them needs the evaluator, which is planned.

A parser and deparse, today

Package syntax: lexer, parser, an AST with a source map, the tokens of getParseData(), R's own parse errors byte for byte, Deparse, and TopLevelNames, which names what a cell assigns and reads.

Regular expressions: not TRE

R's engine, TRE, is not ported. grepl, sub, gsub, regexpr, gregexpr and regmatches run on Go's regexp, an approximation: lazy quantifiers, capture priority in repeated groups and \</\> differ from R, and back-references and perl = TRUE are refused. A faithful TRE port is in progress for v0.6.0, not released.

An evaluator, in three slices

Package eval (v0.5.0): 102 of R's 210 primitives and 94 base closures, with replacement, subsetting, attributes, S3 dispatch, conditions, print, environments as values, the apply family, string functions and regular expressions, judged by the oracle (1 035 of 1 035 goldens). Its API, eval.NewSession and Eval, is bounded by default since v0.4.0 (30 000 000 steps, 16 GiB of cumulative allocation, 10 s; eval.Unbounded opts out), a breaking change.

The serialization format

rds reads and writes R's serialization format (XDR, versions 2 and 3, gzip or bzip2): a decoded value encodes back to the same bytes. The decoder is built for untrusted input and fuzzed.

Clean room, BSD-3-Clause

R's sources are GPL-2-or-later. R is an oracle, never a source: no R source file, test file, help page or transcript is copied here, and base R's R code is rewritten from the documented behaviour, not copied.

The plan

From an oracle to a playground.

M0 and M1 are done, and M2 has slices 1 to 3 done. M3 to M8 are planned and not started.

M0 · Oracle harness

done

internal/oracle starts one Rscript --vanilla process (the pinned release; any other version is refused) and talks to it in R's own serialization; rds reads and writes that format; cmd/roracle regenerates the golden files. The tests replay the goldens without R, and with R they check that R reads Go's bytes back unchanged.

M1 · Parser and deparse

done

Package syntax: the parser in v0.1.0, deparse in v0.2.0. syntax.ParseAll(src), syntax.TopLevelNames(cell) and syntax.Deparse(expr, nil). Tree, tokens, error text and deparse output are compared with R 4.6.2 (see the parity table below). Bounded in bytes, depth and nodes. Known gaps: the effect of #line directives on later error lines, parse(file=) encoding options, and the platform divergences of numeric constants in deparse (exp10 on macOS, 80-bit long double on x86-64).

M2 · Evaluator core

slices 1 to 3 done

Package eval, slice 3 released in v0.5.0 (limits bounded by default since v0.4.0). Slice 1: values, environments, promises, closures with R's argument matching, control flow, arithmetic. Slice 2: replacement with copy-on-modify, subsetting, attributes, S3 dispatch, conditions, print, and the Eval API with limits. Slice 3: environments as values, the apply family, string functions and regular expressions. 102 of R's 210 primitives and 94 base closures. Judged by the oracle: 1 035 of 1 035 goldens, 189 998 of 190 000 arithmetic fuzz expressions, 35 963 of 36 000 subsetting, 17 993 of 18 000 regex. Known divergences: regex is an approximation on Go's regexp, not TRE (a TRE port is in progress for v0.6.0), format of a factor is missing, sort and ls order by code point, not locale, function printing, some [[<- messages. The design is docs/m2-design.md of go-embedded-r/r.

M3 – M8 · Base, stats, regex, graphics, notebook bridge, wasm playground, CRAN ports

planned

Planned, not started: base, stats, regex, graphics, the notebook bridge, the WebAssembly playground and CRAN ports. The plan is bib/r.md of go-scikit/roadmap.

M1 parity with R 4.6.2

The parser and deparse, judged by the oracle.

The same text is parsed by R and by Go, and the terminal tokens, the tree and the error text are compared (go run ./cmd/rparity). R's own sources are read where they are unpacked and never committed.

R's base packages: 842 files

Every .R file of src/library (7.8 MB): 844 inputs, 842 parse and 2 are rejected. Tree 842 / 842, tokens 842 / 842, deparse 842 / 842, error text byte for byte 2 / 2. On R's tests/ (90 files that parse): tree, tokens and deparse 90 / 90. Deparse differs from R in the last digit for 5 of 100 000 random literals beyond exponent 200 (exp10 on macOS), and, on x86-64 Linux, in 6 of the 90 files of tests/ (80-bit long double).

R's tests: 94 files

95 files, 1 is not UTF-8 and is skipped: 94 inputs, 90 parse and 4 are rejected. Tree 90 / 90, tokens 90 / 90, error text 4 / 4.

Our own corpora

42 snippets: tree and tokens 42 / 42. 52 error cases: 51 / 51 identical error texts. 93 edge cases (Unicode, raw strings, |>, \(x), ->>, hex, L and i numbers): 90 / 90, errors 3 / 3. 117 odd inputs (CRLF, tabs, bidi controls, invalid UTF-8): 81 / 81, errors 36 / 36.

190 000 differential fuzz inputs

Random edits of lines of R's sources (seeds 1, 2 and 11), about two thirds rejected: tree, tokens and error text all identical.

100 000 decimal literals

Random, up to 20 digits, exponents -330 to 330: 100 000 identical doubles. They match R on macOS arm64 (fused multiply-add, 64-bit long double); R on x86-64 Linux accumulates in 80-bit precision and can differ in the last place. Those literals are compared within 16 ulp and kept out of the goldens.

Speed: 0.80 of R

One core each, interleaved rounds, median of 11, 1-minute load 7.8 on an Apple M-series laptop: the 842 files of src/library (7.8 MB) take 0.214 s in Go (syntax.ParseAll, which also builds the source map and the token list) and 0.269 s in R's parse(text =, keep.source = FALSE). Go/R = 0.80.

R is an oracle, never a source.

The sources of R are GPL-2-or-later and this project is BSD-3-Clause. No R source file, test file, help page, .Rout.save transcript or text of R's manuals is copied, and none is downloaded at run time. The golden files are the answers of R 4.6.2 to inputs written here. Contributors work from specifications: the R Language Definition, the R Internals manual, the help pages and the oracle's behaviour.

The plan, the parser and the licence →
Repositories

What is here.

The organization holds the interpreter's repository, r, and its landing and documentation.

r

source

The interpreter, github.com/go-embedded-r/r, released as v0.5.0 (2026-11-05): M0, the oracle harness against GNU R 4.6.2 (internal/oracle, cmd/roracle, the rds serialization reader and writer, golden files), and M1, the parser and deparse (package syntax). M2, the evaluator (package eval): slices 1 to 3 are done (its regular expressions are an approximation on Go's regexp, not TRE). Everything after is planned.

docs

site

The documentation (Hugo + Hextra): the plan and its milestones, what the repository holds today, the parser's parity with R 4.6.2, and the licence rule. Source →