GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - modernc-org/ogo: Read-only mirror of https://gitlab.com/cznic/ogo

BranchesTags
Open more actions menu

Latest commit

 

History

2,051 Commits
2,051 Commits

Folders and files

NameName
Last commit message
Last commit date
.github
.github
 
 
_examples
_examples
 
 
doc
doc
 
 
internal
internal
 
 
loadp2
loadp2
 
 
scripts
scripts
 
 
sponsors
sponsors
 
 
web
web
 
 
.gitignore
.gitignore
 
 
AUTHORS
AUTHORS
 
 
CHANGELOG.md
CHANGELOG.md
 
 
CLAUDE.md
CLAUDE.md
 
 
DIFFERENCES.md
DIFFERENCES.md
 
 
LICENSE
LICENSE
 
 
Makefile
Makefile
 
 
README.md
README.md
 
 
THIRDPARTY_LICENSES.md
THIRDPARTY_LICENSES.md
 
 
all_test.go
all_test.go
 
 
etc.go
etc.go
 
 
go.mod
go.mod
 
 
go.sum
go.sum
 
 
logo.png
logo.png
 
 
main.go
main.go
 
 
specs.go
specs.go
 
 

Repository files navigation

logo_png

Mascot based on the Go gopher by Renée French, licensed CC BY 4.0.

Go Reference

A modern, zero-allocation, Go-like programming language built specifically for the Parallax Propeller 2 (P2) multi-core microcontroller.

OctoGo brings the elegance of Go's concurrency model to embedded hardware. It compiles an LL(1) subset of a Go-like language into C99/C11, which is then compiled to native P2 assembly using the industry-standard flexprop toolchain.

There is no software scheduler and no garbage collector. Goroutines map 1:1 to physical silicon.

The repository you are currently viewing might be a mirror. Please review the guidelines below based on where you are viewing this:

Platform Role Contributing Guidelines
GitLab Primary Source This is the canonical repository (cznic/ogo), where development happens.
GitHub Mirror This is a mirror (modernc-org/ogo). We do accept Issues and Pull Requests here for your convenience!
Note: PRs submitted here will be manually merged into the GitLab source, so please allow extra time for processing.

The Hook: Concurrent Blinky

Forget manual state machines and timer interrupts. In OctoGo, spawning a parallel hardware process is as simple as calling go myfunc(). Channel communication uses the familiar <- operator, allowing you to synchronize hardware locks seamlessly.

import "p2"

// blinkWorker runs independently on its own dedicated Cog
func blinkWorker(pin int, rateChan chan int) {
	delay := 500
	for {
		// Non-blocking hardware poll via select
		select {
		case delay = <-rateChan:
		default:
		}

		p2.PinHigh(pin)
		p2.WaitMs(delay)
		p2.PinLow(pin)
		p2.WaitMs(delay)
	}
}

func main() {
	// A channel is made as Go makes one. Its rendezvous cell is a static or a
	// frame's, not a heap's, and main's outlives every cog.
	rateChan := make(chan int)

	// Spawns directly to a new hardware Cog!
	go blinkWorker(56, rateChan)

	// Update the blink rate from the main Cog via hardware-locked channel
	rateChan <- 100
}

Installation

go install modernc.org/ogo@latest

Building ogo needs Go 1.26 or newer. It is built and tested on linux/amd64, linux/arm64, windows/amd64, darwin/arm64 and darwin/amd64 — if you need another platform, please say so, it helps decide which to do first. Note that the two halves come apart: ogo build emits an ordinary P2 .binary, so you can compile on one machine and flash with whatever loader you already have on another.

On Windows, run ogo from cmd.exe or PowerShell — not a Unix-emulation shell (git-bash, MSYS2, Cygwin). The board-facing commands (run, loadp2) drive the serial port through the native Windows console and are unreliable under those shells: the P2 handshake times out intermittently and the terminal's exit key can stop responding (use Ctrl-C to escape). Building (build, fmt) is unaffected and works in any shell.

That is the whole toolchain. ogo embeds the C backend (flexspin's compiler, transpiled to Go) and the P2 loader, so there is no flexprop installation, no separate loader and no SDK path to configure.

Usage

ogo <command> [arguments]

The commands are:

Command
build compile packages and dependencies
fmt reformat source files
help show help for a command
loadp2 load a program onto a Propeller 2 board (loadp2 passthrough)
run compile and run a program on a connected board
smith output a random program for compiler testing
test test packages
version print the ogo version

Run ogo help for this list at the terminal, or ogo help <command> — for example ogo help build — for one command's flags and detail. A typical session:

  1. Write your .ogo code.
  2. Run ogo build blinky/ to compile and generate your P2 binary.
  3. Run ogo run blinky/ to compile it, load it onto a connected board and open a terminal.
  4. Run ogo test blinky/ to run that package's _test.ogo tests, on the board.

Tests run on the board and nowhere else. A test is func TestSomething(t *testing.T), and testing is imported by name with nothing on disk:

import "testing"

func TestPop(t *testing.T) {
	if got, ok := pop(); !ok || got != 3 {
		t.Errorf("pop: got %d, %v; want 3, true", got, ok)
	}
}

t.Log, Logf, Error, Errorf, Fatal, Fatalf, Skip, Skipf, Fail, FailNow, SkipNow, Name and Helper are Go's, and the output reads as go test -v's does, each message after its file and line. A format is a constant string, as the builtin printf's is, since nothing is formatted into memory. FailNow and its kin stop the test where they are called, however deep, but run no deferred call on the way out, as a panic runs none here; and a message is placed on the line it is written on, Helper or not. A t.Log can't be deferred or started with go directly yet; wrap it in a function literal. Nor is it a value yet, f := t.Logf, and no interface holds a *testing.T or a *testing.B yet, any among them: a helper takes a *testing.T parameter. ogo test ./... tests every package under a root, one board run each; ogo test -run Push runs only the tests whose name matches, and compiles only those, which is what makes iterating on one of them worth the board's ten seconds. ogo test -c builds the tests without running them, for a machine with no board attached.

A benchmark, func BenchmarkSum(b *testing.B), runs where ogo test -bench . asks, after the tests, as go test runs one: b.N grown until a run takes -benchtime, a second by default or a count written 100x, and the result printed as go test -v -bench prints it, its time measured by the P2's 64-bit system counter at the clock the cpu: label names. b.ResetTimer, StopTimer, StartTimer, SetBytes, ReportAllocs (no allocation is ever reported) and ReportMetric are Go's, and so are b.Loop, the loop Go 1.24's benchmarks are written with, and sub-benchmarks, b.Run; RunParallel is not provided yet. A function literal capturing a variable is refused here as everywhere, so a table-driven benchmark hands each case to its sub-benchmark through a package variable.

ogo build --report says what a binary takes of the P2 once it is built: Hub RAM by its parts and what main's stack needs beside them, each goroutine's stack and the go statement it is for, the hardware locks the runtime takes, the program's data by package variable, each with what its value is made of (a slice's backing, a channel's cell and buffer), and its largest functions by their Go names. Every size is the C backend's own, read off its listing and its binary. For p2-11:

Hub RAM: 357544 of 524288 bytes in the binary; main's stack needs 2572 more, 164176 to spare
  header              1028  the loader's and the clock's
  cog runtime         1364  the code each cog loads into its registers
  code               79872  219 functions as the backend laid them out, the largest below
  ...
Goroutines: 4 go statements, each run once, on a slot of its own
   longs  needs  go statement
     512    509  disk.ogo:113 turn
     160    156  main.ogo:137 receive
  ...
Data: the largest of the program's 269260 bytes
   253952  memory [126976]uint16
     4816  machine pdp11.Machine
     4100  the goroutine slots: stacks, arguments, bookkeeping
  ...
Code: the largest of 219 functions
     3992  (*fat.Volume).Mount
     2924  (*rk11.Controller).begin
  ...

Seeing garbled serial output? ogo run reads at 230400 baud, which is what a compiled program writes at, so println output is readable out of the box. The raw ogo loadp2 passthrough uses loadp2's own defaults instead, and it reads at 115200 — a baud mismatch, so the output arrives as rubbish bytes rather than text. Pass the matching baud and nothing else is needed:

ogo loadp2 -b 230400 -t prog.binary

The -f frequency is not part of the fix for a program built here: such a program sets its own clock as it starts, so -f decides neither what it runs at nor what its output looks like. The clock is a build-time choice — ogo build --clock 200MHz — and 160 MHz is the default.

A machine controller

_examples/vacuum is a vacuum system's PLC, after the one Rayman wrote in Spin2 for a P2 and posted on the Parallax forum: a scan loop on the main cog that reads the inputs, drives the pumps and valves and runs the front panel through two I2C expanders, a Modbus RTU server on a cog of its own, the vacuum gauges' serial lines parsed on another, interlocks, a safe state when the panel stops answering, and the status screen of a 1.8" LCD. The hardware is modelled, so it imports nothing and is held to Go's output on the host and on a P2-EDGE, as life is. _examples/spin2drivers calls the two Spin2 drivers that PLC runs on, Jon McPhalen's jm_i2c and the eight-port serial driver, as they are, from two OctoGo packages of bodyless functions.

ogo run _examples/vacuum
ogo run _examples/spin2drivers

Drawing on an oscilloscope

_examples/gopher puts the Go gopher on a scope in X/Y mode: two smart pins run as DACs, one X and one Y, and the beam walks the outline while persistence does the rest. There is no framebuffer and no display controller — the figure is the two voltages, redrawn about a thousand times a second. Eight frames make it dance.

ogo run _examples/gopher     # then probe the two pins it names, scope in X/Y mode

The same program, in Go and on a P2

_examples/life runs Conway's Game of Life on the board and imports nothing at all — no pin, no cog, no intrinsic, just arrays, structs, methods and loops. Give the source a package clause and spell printf fmt.Printf and it compiles as Go, which is not a claim it is left to make: the test suite performs exactly those two substitutions, runs the twin under go run, runs this one on a real P2-EDGE, and requires the two to print the same bytes.

ogo run _examples/life

That is a stronger check than a test corpus gets. Pinning behaviour against what somebody wrote down as expected is only as good as the person who wrote it; pinning it against the language OctoGo is modelled on, running the same source, is not.

Trying a program as Go first, in Visual Studio Code

An OctoGo program is written as Go is written, so Go's own tools are a good place to learn the language and to try a program before it goes to the board. Visual Studio Code with the Go extension gives completion, errors as you type, go to definition, formatting and a debugger. On Windows, roughly (elsewhere only the installer differs):

  1. Install Go from https://go.dev/dl/ (on Windows, the .msi).
  2. Install VS Code, and from its Extensions view the Go extension by the Go Team at Google. When it offers to install its tools (gopls, dlv), let it.
  3. Make a folder, open it in VS Code, and in its terminal (View > Terminal) run go mod init hello once. Go builds a program as a module, and without the go.mod this writes, go run . and F5 stop with "go.mod file not found in current directory or any parent directory". go env -w GO111MODULE=auto makes them work too, by turning on the GOPATH mode that predates modules; go mod init is the current way, and go env -u GO111MODULE undoes the setting.
  4. Create main.go with package main and a func main, and run it with go run . or debug it with F5.

An example that imports no p2 runs this way too, which is how _examples/vacuum can be stepped through in the debugger: copy its main.ogo into such a folder as main.go, put package main and import "fmt" at its top and var printf = fmt.Printf at its end, and Go prints what the P2 prints — the test suite holds the two to the same bytes. Then ogo run the original on the board. An example that imports p2 drives pins and cogs and runs only on a P2. For the language itself, the Tour of Go runs in the browser.

Language Specification

The language is specified in the package documentation: OctoGo Language Specification. It is a draft and is reconciled with the implementation as the compiler moves; the Status section below is the shorter answer to what works today, and CHANGELOG.md records what changed in each release — including the cases where a new release rejects a program the last one accepted. DIFFERENCES.md lists what builds both as OctoGo and as Go and runs differently — a panic and its deferred calls, a recursion at the end of its stack, among others — each with a program and what each compiler makes of it, run as a test on a P2 board and under Go.

Architecture & Design

OctoGo is designed to be a zero-cost abstraction over the Propeller 2's unique 8-cog architecture.

  • Native Hardware Concurrency: The go keyword transpiles to a scoped block that claims a pooled slot holding the goroutine's stack and its arguments, then invokes _cogstart_C. The pool has one slot per available Cog, so the P2's 8-cog limit is enforced by construction; exceeding it is a runtime panic.

  • Hardware-Backed Channels: Channels (chan) are not software queues. Each is a rendezvous cell in Hub RAM guarded by one of the P2's native hardware locks (0-15), giving atomic, lock-step transfer. A channel is made as Go makes one, make(chan T), its cell static at package level and the frame's in a function, with no allocator behind it, and a buffered one, make(chan T, n), has its n slots beside the cell, n a constant. Past the sixteenth channel the locks are shared rather than exhausted, which costs contention and nothing else — twenty-four channels each completing a rendezvous run correctly on a P2-EDGE.

  • Zero-Allocation & No GC: OctoGo operates without a Garbage Collector. Memory scoping is strict (Hub RAM vs. Cog RAM), and slices are implemented as non-escaping views over fixed arrays.

  • Select Statements: The select statement is transpiled into an efficient polling loop, utilizing flexprop's _waitx yield instructions to prevent bus starvation during non-blocking hardware polling.

  • Implicit Namespaces: To keep the grammar clean and strictly LL(1), there is no package keyword. A package's namespace is implicitly inferred from its directory name, mapping cleanly to a single C translation unit.

The Compiler Pipeline

OctoGo is a source-to-source compiler (transpiler) written in Go.

  1. Frontend: Lexical analysis and parsing are generated via modernc.org/egg using a LL(1) grammar. The AST is represented as a zero-pointer, cache-local flat []int32 slice.

  2. Semantic Pass: Uses Go 1.23+ iterators (func(yield func(node) bool)) to traverse the AST, calculate memory offsets, and resolve scope.

  3. Transpilation: Emits standard C. The runtime it needs -- bounds and divide-by-zero traps, slice and channel helpers, the goroutine pool -- is emitted into the same file, per program, so only what is used is paid for.

  4. Backend Generation: The ogo build command automatically feeds the emitted C into flexprop, delegating register allocation, instruction scheduling, and P2 binary generation to a proven, hardware-aware backend.

Status: early preview

OctoGo compiles and runs real programs on real Propeller 2 hardware. Every change goes through a suite that builds each test program with the real backend, and before each release every one is also loaded onto a P2 and its serial output checked; there is no public CI, a board being part of the test, so each release's notes say what passed. It is also unfinished. This section is the honest inventory — please read it before deciding the compiler is broken.

Works (all of it exercised on hardware):

  • Integers (sized and unsized, including 64-bit), byte, rune, bool, string, structs, fixed arrays including multi-dimensional, slices, named types, channels. Structs may refer to themselves, to each other, and to a type declared later, so linked lists, trees and graphs build. A named type behaves as what it is defined over — type Name string indexes, slices, ranges and compares as a string, type List []int as a slice, type Named Point has Point's fields and takes Point's literals — and carries its own methods. That holds for every kind a type can be defined over: a pointer dereferences, a function is called. A literal may name such a type, Row{1, 2, 3} for type Row [3]int.
  • A slice literal stands wherever a value may — sum([]int{1, 2, 3}) — its backing array a local of the function that wrote it, with the lifetime that implies.
  • An array may be a slice's or a channel's element, of any rank: [][2]int, chan [3]int, chan [2][3]int. C cannot spell one inline where a slice header's pointer or a rendezvous payload goes, so the element gets a generated typedef and everything that would copy it by value copies it through a pointer instead. Such a slice is made the same way as any other, by slicing a backing array — m[:] over a [4][2]int is a [][2]int over that storage, as is d[1][:] over one rank further in.
  • A pointer to an array, *[3]int, which passes one by reference without a slice header. It abbreviates the dereference as Go does — p[i] is (*p)[i], and so are len(p), range p and p[lo:hi] — while *p copies the array and copying the pointer aliases it. No other pointer is indexable, which C would not say for itself. The dereference may also be written out and carry a suffix, (*p).x and (*p)[i]; for a pointer to a slice or a string that is the only spelling there is, an index on the pointer itself being no operation in either language.
  • Slicing, including the capacity bound: pool[0:0:64] hands out a region of a package-level buffer that appending cannot grow past, which is how you sub-divide storage with no heap to allocate from.
  • Composite literals: structs positionally (P{1, 2}, P{}, nested) or by field name (P{y: 2}, any subset in any order, the rest zeroed), and arrays and slices positionally, by index or mixed ([4]int{10, 20}, []int{1, 2, 3}, [5]int{0: 1, 4: 9}), including multi-dimensional ([2][3]int{{1, 2, 3}, {4, 5, 6}}) and at package scope, which is how you write a lookup table. One place a literal may not appear bare is the top level of an if, for or switch header, where { is the block — parenthesize there, as in Go: if p == (P{}) {. That is only for a literal whose type is a bare name; for _, v := range []int{1, 2, 3} needs nothing, a [ not being able to begin a block.
  • var (including several names and a value list), const with iota, type, functions and methods with value or pointer receivers. A package may declare several init functions; they run in the order they are written, before main.
  • Named and unnamed parameters and results, multiple return values, naked returns. Several values may be destructured out of a plain call, a method call or an imported package's call alike, b, ok := r.pop().
  • A declared function used as a value: a variable, parameter, result, array element or struct field of a function type holds one and a call through it is a call, chosen = add; chosen(1, 2), including one with several results. go starts one too, go g(21). It becomes a C function pointer, so it costs nothing and allocates nothing, and one is always safe to send to another cog — it names code, not the frame it was made in.
  • if/else and switch including an init statement (if v := f(); v > 0, switch v := f(); v), which may declare several names from one call (if v, ok := m.get(k); ok), all for forms including range, switch with or without a guard, fallthrough, break and continue (including labeled), defer (including in nested blocks, capturing its arguments).
  • The full operator set, compound assignment, multiple assignment, and equality on strings, structs and arrays. A multiple assignment writes to anything a single one can — an element, a field, a pointee — so xs[i], xs[j] = xs[j], xs[i] swaps.
  • A call's result may be used directly: a field read off it (mk().y), a method called on it, or an index into it (mk()[1], mk().d[1]) — an array result included, which may also be ranged, assigned and passed on whole.
  • len, cap, append, copy, clear, min, max, make for a fixed-capacity slice, panic, print/println.
  • printf, the built-in fmt.Printf would be if there were a fmt package: %d %x %X %o %b %q %U %s %t %f %e %E %g %G %c %v %T %%, over a format that must be a constant — which is what lets a wrong verb be refused where you wrote it instead of printing nonsense on the board. %T costs nothing for a static type and one pointer read for an interface, whose vtable carries its type's name.
  • A predeclared Builder for assembling a string at run time without a heap: NewBuilder(back[:]) puts a write cursor over a byte array you own, and WriteString, WriteByte, WriteRune, Write, String, Len and Reset behave as strings.Builder's do, results included, except that a write past the backing writes what fits and returns a non-nil error, "short write", rather than growing it — you chose the size — and that String is a view of the backing, overwritten by a Reset and the writes after it, since there is no heap to copy it into. No interface holds one yet, any among them; a parameter of type *Builder takes it. It is meant to move into the strings package as strings.Builder.
  • go, chan and select, mapped to cogs and hardware locks. A method may be launched too, go w.run(ch), its receiver evaluated and copied where the go stands — including one reached through fields and indexes, go ws[i].run(ch), which is a worker per cog — as may an imported package's function, go driver.Poll(ch). A select multiplexes receives and one send, case ch <- v:, and a receive clause may take the comma-ok form, case v, ok := <-ch:, whose ok is false once the channel is closed. A channel is made with make(chan T), at package level or in a function, and a channel a function makes stays in it -- but for main's, which goroutines may share. The P2's locks are reachable directly through the p2 package.
  • Runtime traps for out-of-range indexing and slicing, division and remainder by zero, a shift by a negative count, appending past a slice's capacity, and cog exhaustion, and a nil pointer dereference. Each prints panic: <what> and stops every cog, as Go ends the whole program; --release reboots the board instead, and --unchecked omits the checks.
  • Small functions are inlined where they are called, a call being dear on this target: an accessor of one line costs the check it makes and not the call as well. --no-inline leaves it to the C backend.
  • A package is a directory: ogo build compiles every .ogo file in it together, and a program may span several packages. A value of an imported package's struct type is written the way you would expect, geo.Point{1, 2}.

Does not work yet, in rough order of how likely you are to hit it:

  • The standard library is seven packages. strings is the allocation-free part of Go's — the functions that answer a question about a string or return a substring of one, each meaning exactly what Go's of the same name means, checked by running the same program under Go — and bytes is the same part of Go's for a byte slice. math is the whole of Go's on the same terms, its bodies Go's own pure Go source — the roots, powers, exponentials and logarithms, the trigonometric and hyperbolic functions and their inverses, Hypot, FMA, Erf, Gamma, Lgamma, the Bessel functions and the rest, Go's mathematical constants and the limits of the integer and the float types; a program carries only the functions it calls. errors is Go's but for Join (and there is no fmt.Errorf): New makes its error where the call stands, there being no heap, so a sentinel, var ErrTimeout = errors.New("timeout"), is declared at package level and a function returns one of those; errors.New in a function makes an error that function may use and not return, and it is called where its error is wanted, never taken as a value. Is, As and Unwrap walk a tree of wrapped errors as Go's do. p2 wraps thirty-three intrinsics (pin control, smart pins including the ADC, timing and the clock, the serial line in both directions, the hardware locks, the cog's LUT RAM), testing is Go's *testing.T for logging, failing and skipping, subtests as t.Run runs and go test -v prints them, and *testing.B for benchmarks as ogo test -bench runs them, b.Loop and b.Run too (no Cleanup or RunParallel yet; a subtest's name is a constant or lives as long, and a test runs 256 at most, eight deep), -run and -bench selecting subtests and sub-benchmarks as go test's do, and unsafe has unsafe.Pointer, Add, Slice and Sizeof, Alignof and Offsetof, of the P2's layout. That is the whole of it. Your own packages do import and build; there is just little else to import yet.
  • One function's locals live in cog RAM, and there are 480 longs of it for all of them together. A function big enough to exhaust that fails to build with the backend's fit 480 failed: pc is N — the P2's own limit rather than the compiler's — and ogo build names the functions holding the most registers, the largest first. Small functions are inlined into their callers, so a call's arguments and locals count toward the frame of the function that makes it, and an --unchecked build can need more registers than a checked one. Splitting the function into several is the fix, and is what the code wanted anyway.
  • A goroutine's stack is sized by the build, read off the backend's listing of its frames and calls. Where every go statement of the program runs at most once — in no loop and no function literal, in main, an init or a function called once from such a place, as drivers are usually started — each has a slot of its own, sized for its goroutine; otherwise the pool is seven slots, each what the deepest goroutine needs. A program whose goroutine needs more than the largest slot, 8192 longs, is refused, and ogo build --gostack N sets every slot's size outright (64 to 8192 longs; the slots sit in hub RAM for the run). The listing cannot bound a goroutine RECURSING through its own calls, an interface's or a function value's, so a program starting one is refused unless --gostack gives the size. A call through a function value is taken to reach what the value's type may hold: the functions the program made values of that type -- of every type of the signature, where the program has a value of it written out, func(int) int, anywhere. Recursion works — main runs on the cog's own stack, the rest of Hub RAM, and a goroutine on its pool slot's — and a recursion the build sees is checked as it runs: the build puts a check at the entry of each function on it, which panics goroutine stack overflow before the recursion writes past the end of its stack, keeping free what the panic needs, about 600 bytes. Before 2026-10-18 nothing did: main recursing too deep ran on past the top of Hub RAM in silence on a P2-EDGE, and a goroutine in a --gostack slot over the slot after it. A check costs three instructions on main's cog and about ten on a goroutine's, and only a function on a recursion has one. Any other goroutine is fenced too, so one that overruns its slot and still returns ends with the same panic.
  • A method value in two shapes, neither an omission. One with a value receiver: Go copies the receiver where the value is made and there is nowhere to copy to, while binding the address instead would alias the variable, so the program would answer differently the moment anything wrote to it. And one whose receiver is not a package-level variable, whose address does not outlive the value. A pointer-receiver method on a package variable is taken freely; the receiver is bound at compile time, which is what keeps a function value one word (doc/funcval-cost.c prices the alternative). Every other function-valued form works, go through one included.
  • A local interface type, and a second local type of one name in one function — every other type declaration inside a function works, aliases and shadowing included.
  • A range clause written with = accepts a variable or a struct field, not an element: for xs[0], a[0] = range xs is refused.
  • Every import path names a directory below the program's root, whoever writes the import — and an ogo.mod file is what says where that root is. It holds one line, module example.com/proj, and a build looks for the nearest one at or above the package it is given. Import paths are then written as Go writes them, module path included, so cmd/firmware and cmd/calib may both import "example.com/proj/sensor" and share the one copy of it. Neither the importing package's own location nor the working directory takes any part in it. Without an ogo.mod the directory you build is the root and paths are relative to it (import "sensor"), which reaches packages beside the main package and nothing outside it — no cmd/ layout, and no package shared by two programs. There is no versioning and there are no external dependencies: ogo.mod says where the root is, and a line it does not implement, such as require, is refused rather than ignored. Nesting a package inside the one that imports it is not a layout either way — util/geo/ is found as <module>/util/geo, not as geo. A package directory must be lower case (letters, digits, _, -), though the module prefix may carry capitals, being a repository name: foo/ and Foo/ are one directory on macOS and Windows, so a program naming a capital is refused where it is written rather than where it is cloned. The same reasoning refuses the device names Windows reserves — con, prn, aux, nul, com0–com9, lpt0–lpt9 — as a package directory and as a source file name (aux.ogo is aux): nothing on Windows may carry one, so a repository holding con/ cannot even be checked out there.

Floating point (float32/float64) is supported, exponent literals included (1e3, 1.5e-3), as Go defines it: float32 is IEEE single precision and float64 IEEE double precision, 53 bits of significand, every operation rounded to nearest even. The P2 has no floating-point hardware, so both are software: a float32 the C toolchain's, a float64 routines the compiler writes into a program that uses one -- about 6 KB with its printing, and an addition about 860 clocks, a multiplication 1,720 and a division 2,900 at 160 MHz, against a float32's 660, 580 and 560. Printing is EXACT: the compiler renders a float from its own exact decimal value rather than through the target's printf, so %f, %e, %g and %v all produce the digits Go produces -- 21.0 prints 21.000000, and %v gives the shortest form that round-trips to the same float. (The compiler's integer-to-float conversion rounds an exact tie to even, as IEEE 754 and Go do, so float32(16777217) is 16777216 here as it is in Go.) Constant expressions are evaluated exactly, as Go evaluates them -- 0.1+0.2 == 0.3 is true, 7 / 2.0 is 3.5 -- and a constant beside a float32 operand is a float32, so f == 0.3 compares two of them.

Not planned, because the target does not permit them: a garbage collector, a heap, maps, closures that capture their environment (a function value is fine — it is a pointer to code, not to a frame), and runtime string concatenation. Constant string concatenation folds at compile time; to assemble one at run time, write into a Builder over storage you own.

Having no heap has one consequence worth knowing before you meet it: a reference must not outlive what it refers to. Where Go would move the referent to the heap and say nothing, there is nowhere to move it to, so the program is refused instead — returning a local's address or a slice backed by a local, storing either in a package variable, or handing either to another cog as a go argument or through a channel. A struct holding such a reference counts as one — and so does reading the reference back out of it, b.d being the same slice header b carries, and an interface made from one, any(&q) reaching q exactly as &q does — and the requirement follows a parameter back to the call sites that chose the storage. main's outermost block is the exception: it outlives every cog, since the end of main stops them all, so a buffer declared there may go to a goroutine:

func main() {
	var buf [64]byte
	done := make(chan int)
	go fill(buf[:], done)   // main's outermost block outlives every cog
	<-done
}

Anywhere else, declare the buffer at package scope and pass a slice of it, which is what the diagnostics ask for.

Everything else Go has is meant to be here eventually — anything missing above is work not yet done rather than a decision taken. Generics are the one open question: not supported, not planned, and not ruled out either.

Interfaces work: an interface value is a pointer to what it carries beside a pointer to a statically emitted vtable, with type assertions and type switches on top. A pointer is what goes into one -- var s Shape = &q, never = q -- because there is no heap to copy into and a silently aliasing value form would mean something Go does not; any expression already of pointer type goes in as it stands, so a constructor's result does, var s Shape = New(). One may be written where a type is wanted rather than declared with a name, func measure(s interface{ area() int }), and the empty one is spelled any as well; identity is by method set, so any and interface{} are one type, and a type switch's case may be one written out, case interface{ Unwrap() error }:. A conversion names the interface where the position does not, Shape(&q) or any(&q), and builds the same two words the assignment does; a conversion to one written out, interface{ M() }(x), is not supported yet. A method may return several values, Next() (int32, bool). A struct may hold one in a field, or embed one -- struct{ error } wraps a cause and passes as an error itself, the promoted call dispatching through whatever the field holds -- and a call through a nil interface panics, as Go's does. The whole-program-optimization pass that would devirtualize the calls is still an open question, and opinions are welcome.

About

Read-only mirror of https://gitlab.com/cznic/ogo

Resources

Readme
License
Activity
Custom properties

Stars

3 stars

Watchers

2 watching

Forks

0 forks
Report repository

Releases

Sponsor this project

Packages

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.