Frontends
Go Frontend
Go Frontend
The Go frontend is a BraceFrontendBase subclass with dedicated Lexer and
Parser helpers for Go-specific syntax: short variable declarations :=,
channels <-, select, go and defer statements, composite literals,
and for-range. The MVP lowers channels, goroutines, and select to
synchronous semantics: every channel op completes immediately, every
go runs inline, every defer runs at the call site.
| File | Lines | Purpose |
|---|---|---|
frontends/go/GoFrontend.h |
1,409 | Frontend class with all method bodies inline |
frontends/go/Lexer.h / .cpp |
~12 / 280 | Go tokenizer |
frontends/go/Parser.h / .cpp |
84 / 690 | Short-var decl, type declarations, channel send, for-range, statement kinds |
frontends/go/GoStdLib.h |
83 | findStdLibMapping for fmt.*, math.*, os.*, strings.* |
Language registration
// GoFrontend.h:12-13
std::string languageName() const override { return "go"; }
std::vector<std::string> fileExtensions() const override { return {".go"}; }
Only .go is recognised.
Preprocessing
GoFrontend::parse (line 709) runs joinGoStatements(source) (line 740)
before BraceFrontendBase::parse. The joiner keeps go f(), defer f(), and select { … } on a single logical line so that the base
class's automatic semicolon insertion heuristic doesn't break them up.
package main is preserved as mod->metadata["go_package"] = "main"
(line 56, isIgnoredLine skips the package line so it isn't parsed as
code).
Print patterns
// GoFrontend.h:35-43
return {
{"fmt.Println", true, true},
{"fmt.Print", true, false},
{"fmt.Printf", true, false},
{"print", true, false},
{"println", true, true}
};
Features (file:line)
Short variable declarations :=
tryParseVarDecl (line 60) first tries go::Parser::parseShortVarDecl,
which recognises:
x := 42
x, y := 1, 2
x int := f() // with explicit type
ch := make(chan int)
The result is a VariableDeclNode with metadata["decl_origin"]=":=",
plus metadata["go_type"]="<typeExpr>" when an explicit type is given.
var / const declarations
Recognised by go::Parser::parseVariableDeclaration (line 104). const
sets isConst=true. Type metadata is preserved.
Channels: make, send, receive
make(chan T) / make(chan T, n)
tryBuildChannelInitializer (line 795) detects channel constructors in
short-var and var initializers. The lowered IR is an ObjectLiteralNode
with a __chan_value__ member holding a queue:
ch := make(chan int) // ch is an mpl_object with __chan_value__: []
ch <- 42 // AssignmentNode: ch.__chan_value__ = 42
<-ch // FunctionCallNode("__go_chan_recv__", [ch])
Channel send ch <- value
tryParseLanguageSpecificStatement (line 287-302) detects ch <- value
before parseStatement so it isn't routed through parseExpr. Lowered
to:
AssignmentNode(target=MemberAccessNode(ch, "__chan_value__"), value=parseExpr(value), op="=")
Channel receive x, ok := <-ch; <-ch; x := <-ch
Recognised by the expression parser; emits
FunctionCallNode("__go_chan_recv__", [ch]) or with optional ok flag.
select { case … default: … }
parseSelectBlock (line 1054) handles the MVP synchronous semantics: each
case's body is emitted in declaration order, unconditionally.
select {
case v := <-ch:
fmt.Println(v)
case ch <- 42:
fmt.Println("sent")
default:
fmt.Println("nothing")
}
Lowers to a BlockNode with metadata["go_select"]="true" containing
the case bodies in order, with a __go_select_choose__ runtime hook
that the backend can expand into a real select on platforms that
support it.
go f() and defer f()
| Statement | Where | Lowered to |
|---|---|---|
go f(args) |
line 361 | FunctionCallNode("f", args) (inline; MVP is synchronous) |
defer f(args) |
line 347 | FunctionCallNode("f", args) (inline; MVP runs at defer site, not at function return) |
The MVP semantics mean there is no actual concurrency and no LIFO ordering of deferred calls. Real semantics would require async lowering to a coroutine or thread-pool backend extension (see limitations.md).
Composite literals
tryBuildCompositeLiteralInitializer (line 819) detects:
[]int{1, 2, 3}
[3]string{"a", "b", "c"}
map[string]int{"a": 1, "b": 2}
Point{X: 1, Y: 2}
Lowers to ObjectLiteralNode (for maps and structs) or ArrayLiteralNode
(for slices/arrays).
for / for-range
Three forms supported by tryParseGoForRange (line 1208):
| Form | Behaviour |
|---|---|
for i := 0; i < n; i++ { … } |
ForNode(init, condition, increment, body) — standard C-style |
for cond { … } |
WhileNode(cond, body) |
for [k [, v]] := range X { … } |
BlockNode with metadata["go_for_range"]="true", key+value bindings declared inside, body wrapped in a 1-iteration ForNode with BreakNode (MVP semantics — body executes once) |
for range X { … } |
Same as above without bindings |
The for-range MVP wraps the body in a single iteration. The C++ backend
on platforms that support iteration semantics can lower this further.
Type declarations
| Form | Where | IR |
|---|---|---|
type Name struct { … } |
line 224 | ClassDeclNode(name=Name, isStruct=true, members=…) |
type Name interface { … } |
line 271 | InterfaceDeclNode(name=Name) |
type Name = Underlying |
line 277 | TypeAliasDeclNode(name=Name, typeExpr=Underlying) |
type Name Underlying |
line 277 | TypeAliasDeclNode(name=Name, typeExpr=Underlying, isExported=…) |
go::Parser::parseTypeDeclaration is the helper.
Function declarations
tryParseFunctionDeclHeader (line 481) recognises:
func Name(params) returnType { … }
func (r *Type) Name(params) returnType { … } // method
func Name(params) (resultA, resultB) { … } // multi-return
Multi-return functions set metadata["multi_return"]="true". Receiver
methods set metadata["go_receiver"]="<receiver>".
The main() function gets metadata["cpp.inline_entrypoint"]="true" so
the C++ backend inlines its body at the program entry point (line 507).
Struct fields
tryParseClassFieldDecl (line 513) parses Go struct fields:
type Point struct {
X, Y int // multiple names sharing a type
Name string
Inner // embedded (anonymous)
*Other // pointer to embedded
}
Embedded fields get metadata["go_embed"]="true" and metadata["go_type"]
set. Multi-name fields return only the first name (the C++ backend
synthesises the rest from the struct tag).
Imports
import "fmt"
import alias "fmt"
import (
"fmt"
"os"
)
Lowered to a VariableDeclNode (alias = variable name) with
metadata["decl_origin"]="import" and a FunctionCallNode("__go_import__", [module, alias]) (line 207-219).
panic / recover
| Built-in | Where | IR |
|---|---|---|
panic(expr) |
line 422 | ThrowNode(value=parseExpr(expr)) |
recover() |
line 436 | FunctionCallNode("__go_recover__") |
Other Go built-ins
tryParseLanguageSpecificStatement (line 444-476) maps:
| Built-in | IR |
|---|---|
make(...) |
FunctionCallNode("__go_make__", args) |
new(T) |
FunctionCallNode("__go_new__", args) |
len(x) |
FunctionCallNode("__go_len__", [x]) |
cap(x) |
FunctionCallNode("__go_cap__", [x]) |
append(s, x) |
FunctionCallNode("__go_append__", [s, x]) |
copy(dst, src) |
FunctionCallNode("__go_copy__", [dst, src]) |
delete(m, k) |
FunctionCallNode("__go_delete__", [m, k]) |
close(ch) |
FunctionCallNode("__go_close__", [ch]) |
complex(r, i) |
FunctionCallNode("__go_complex__", [r, i]) |
imag(z) |
FunctionCallNode("__go_imag__", [z]) |
real(z) |
FunctionCallNode("__go_real__", [z]) |
fallthrough, goto, break, continue, return
| Statement | Where | IR |
|---|---|---|
fallthrough |
line 376 | FunctionCallNode("__go_fallthrough__") |
goto label |
line 381 | FunctionCallNode("__go_goto__", [label]) |
break |
line 390 | BreakNode |
break label |
line 390 | BreakNode(metadata["go_label"]=label) |
continue |
line 398 | ContinueNode |
continue label |
line 398 | ContinueNode(metadata["go_label"]=label) |
return expr |
line 406 | ReturnNode(value=parseExpr(expr)) |
Goroutine lambdas: func() { … }()
tryParseGoLambdaCall (line 982) detects inline goroutine-style lambdas:
go func() {
fmt.Println("async")
}()
Lowers to FunctionCallNode of a synthesized lambda.
Standard library mapping (GoStdLib.h)
The complete table at frontends/go/GoStdLib.h:40-72:
| Go | MPL module.function |
|---|---|
fmt.Println(...) |
Console.WriteLine(...) |
fmt.Printf(...) |
Console.Write(...) |
fmt.Print(...) |
Console.Write(...) |
fmt.Sprintf(...) |
String.Format(...) |
math.Abs(x) |
Math.Abs(x) |
math.Floor(x) |
Math.Floor(x) |
math.Ceil(x) |
Math.Ceil(x) |
math.Round(x) |
Math.Round(x) |
math.Sqrt(x) |
Math.Sqrt(x) |
math.Pow(x, y) |
Math.Pow(x, y) |
math.Max(a, b) |
Math.Max(a, b) |
math.Min(a, b) |
Math.Min(a, b) |
math.Log(x) |
Math.Log(x) |
math.Log2(x) |
Math.Log2(x) |
math.Log10(x) |
Math.Log10(x) |
math.Sin(x) |
Math.Sin(x) |
math.Cos(x) |
Math.Cos(x) |
math.Tan(x) |
Math.Tan(x) |
os.Exit(code) |
Process.Exit(code) |
os.Getwd() |
Environment.GetCurrentDirectory() |
os.Getenv(name) |
Environment.GetVariable(name) |
os.Setenv(name, val) |
Environment.SetVariable(name, val) |
strings.ToLower(s) |
String.ToLower(s) |
strings.ToUpper(s) |
String.ToUpper(s) |
strings.TrimSpace(s) |
String.Trim(s) |
strings.HasPrefix(s, p) |
String.StartsWith(s, p) |
strings.HasSuffix(s, p) |
String.EndsWith(s, p) |
strings.Split(s, sep) |
String.Split(s, sep) |
IR examples
Example 1 — channel and goroutine
ch := make(chan int)
go func() {
ch <- 42
}()
v := <-ch
Lowers to (MVP synchronous semantics):
VariableDeclNode(name="ch",
initializer=ObjectLiteralNode(metadata=["go_chan_type"="int"]))
FunctionCallNode(name="<lambda1>",
arguments=[],
metadata=["go_goroutine"="true"])
body:
AssignmentNode(target=MemberAccessNode(ch, "__chan_value__"),
value=LiteralNode(42))
VariableDeclNode(name="v",
initializer=FunctionCallNode("__go_chan_recv__", [IdentifierNode("ch")]))
Example 2 — type declaration with methods
type Counter struct {
n int
}
func (c *Counter) Inc() { c.n++ }
func (c *Counter) Value() int { return c.n }
Lowers to:
ClassDeclNode(name="Counter", isStruct=true)
members:
VariableDeclNode(name="n", class_field="true", metadata=[go_type="int"])
methods:
FunctionDeclNode(name="Inc", metadata={ go_receiver="c *Counter" })
body: AssignmentNode(target=MemberAccessNode(c, "n"),
value=BinaryOpNode("+", MemberAccessNode(c, "n"), 1), op="=")
FunctionDeclNode(name="Value", metadata={ go_receiver="c *Counter" }, returnType="int")
body: ReturnNode(value=MemberAccessNode(c, "n"))
Example 3 — select statement
select {
case v := <-ch:
fmt.Println("got", v)
case ch <- 1:
fmt.Println("sent")
default:
fmt.Println("default")
}
Lowers to a BlockNode(metadata["go_select"]="true") containing the
three case bodies in order with __go_select_choose__ markers.
What is not supported (Go-specific gaps)
- Real concurrency:
gois synchronous, no thread pool. - LIFO defer: deferred calls run at the defer site.
- Real
select: cases execute in order, no readiness check. - Generic type parameters (
func F[T any](x T)) — passed through as raw identifier. - Embedding with promoted methods: only the embedded field is emitted; method promotion is a backend concern.
- Channel direction (
chan<- T/<-chan T) — direction is dropped. - iota in const blocks.
type switch— emitted as a regularswitch.deferwith closure capture — runs synchronously, may not capture the right value if used inside a loop.