Ovid .md

Language

This is what ovid help language prints, recorded from the ovid this site was built with. It describes v0, the language as it is today. What it lists as absent, such as strings, slices, struct values, methods, and generics, is not there yet, not ruled out.

Ovid language reference (v0).

File:
  package app/util          // must equal the directory path
  import ovid/io            // one import per line, the full path
  const Limit i64 = 64      // consts are i64
  type Pair struct {        // fields are i64, bool, or *T; one per line
    a i64
    next *Pair
  }
  func Sum(p *Pair, n i64) i64 {
    var t i64 = 0           // every local is declared with a type; var t i64 is 0
    while n > 0 {
      t = t + p.a
      p = p.next
      n = n - 1
    }
    return t
  }

Types: i64, bool, *T (T a struct in this package or path.T from an import).
No struct values, slices, arrays, strings, generics, methods, globals, or
closures. At most 6 params; exactly one result type.

Statements: var x T = e | var x T (zero: 0, false, or a null pointer) | x = e | p.f = e | if c { } else if c { } else { }
| while c { } | return e | store8(addr, v) | store64(addr, v) | call(...).
Every path through a func must return.

Expressions: integers (decimal, 0x hex), true/false, names, calls f(a),
other packages' funcs and consts by import path: ovid/mem.Copy(d, s, n),
ovid/io.O_RDONLY (a one-segment import may also be written util.F()),
field reads p.f, casts e as *T (i64 address to pointer and back),
load8/load32/load64(addr), strptr("lit") / strlen("lit"), sizeof(T).
Binary operators, Go precedence: || && == != < <= > >= + - | ^ * / % << >> &
(>> is arithmetic). Unary: ! (bool), - and ^ (i64). Comparisons give bool;
if/while conditions must be bool. && and || short-circuit and are ordinary
values: var sp bool = c == 32 || c == 9 || c == 10.

Strings: there is no string type. strptr("hi\n") is the address of an
interned NUL-terminated literal and strlen("hi\n") is its length (3),
computed by the compiler, so never count bytes by hand. Literals are
read-only: a store into one kills the program (SIGSEGV), so to change the
bytes, copy them first: var b i64 = ovid/io.Alloc(io, n) then
ovid/mem.Copy(b, strptr("..."), n). Literals are NUL-terminated, so
printing one needs only its address:
  ovid/io.Print(strptr("total: "))     // Eprint writes to stderr
  ovid/io.PrintInt(io, n)              // a number in decimal
  ovid/io.Stdout(p, n)                 // n bytes at p, for non-literals

Memory: no implicit allocation. ovid/io.Alloc(io, nbytes) returns an i64
address of zeroed bytes from the heap, which grows as needed and is never
freed; it does not return 0 (out of memory ends the program, exit 71).
Cast the address: var p *Pair = raw as *Pair.
Each struct field takes 8 bytes, so a struct is 8 * fields bytes; never
count them by hand, write sizeof(T) (T a struct; path.T for another
package's), a compile-time i64:
  var p *Pair = ovid/io.Alloc(io, sizeof(Pair)) as *Pair
There is no address-of (&x): locals live in registers or the stack and
cannot be pointed at. When a callee must write a value back, allocate a
cell and pass its address (the out-param pattern ovid/io.ReadFile uses):
  var pp i64 = ovid/io.Alloc(io, 8)       // receives the data address
  var nn i64 = ovid/io.Alloc(io, 8)       // receives the length
  if ovid/io.ReadFile(io, path, ovid/io.CLen(path), pp, nn) != 0 {
    return 1
  }
  var data i64 = load64(pp)
  var n i64 = load64(nn)
Or return a struct: func Read(...) *Result, with the fields you need.

Control flow, all of it:
  if a < b {
    return 1
  } else if a == b {
    return 0
  } else {
    return -1
  }
  while i < n { i = i + 1 }   // no for, break, or continue: use the condition

Programs: the entry package (ovid.mod "entry") has
  func main(io *ovid/io.Cap) i64    // result is the exit code
io is the capability for argv, heap, and syscalls. syscall(...) is only
allowed inside ovid/io; everyone else calls ovid/io funcs.

Tests: any func TestX(io *ovid/io.Cap) i64 in any module package; 0 passes,
anything else fails (the value is reported as the exit code). build and run
leave out _test.ov files: an error there stops check and test, not them, and
the program cannot call what they declare.

Standard library

ovid/io, ovid/mem, and ovid/test ship inside the toolchain, and a module cannot replace them. This is ovid help std:

Shipped packages. Import them by path; a module cannot have a package of the same path.

package ovid/io
  type Cap struct { argc i64; argv i64; heap i64; used i64; size i64 }
  type Temp struct { fd i64; name i64 }
  const SYS_READ SYS_WRITE SYS_CLOSE SYS_FSTAT SYS_MMAP SYS_EXIT SYS_OPENAT SYS_FSTATAT AT_FDCWD O_RDONLY O_WRONLY O_CREAT O_EXCL O_TRUNC EEXIST O_DIRECTORY SYS_GETDENTS64 SYS_GETPID SYS_FSYNC SYS_RENAME SYS_UNLINK SYS_FCHMOD SYS_MKDIRAT EXIT_OOM HEAPCHUNK
  func Argc(io *Cap) i64  // Argc is the number of command-line arguments, the program name included.
  func Arg(io *Cap, i i64) i64  // Arg is the address of argument i, NUL-terminated; its length is CLen. Arg(io, 0) is the program name. It is 0 for an i that is not an argument: past argv the stack holds the environment, which is not the program's to read through here.
  func Alloc(io *Cap, n i64) i64  // Alloc returns n zeroed bytes (8-aligned) from the heap. Nothing is ever freed. A request that does not fit in what is left of the current region starts a new region, or, when it is more than half a region, gets a mapping of its own and leaves the current region in use. The result is never 0: if the kernel refuses, the program ends with exit code 71 and "out of memory" on standard error.
  func Map(n i64) i64  // Map returns n fresh zeroed bytes from the kernel (mmap, private and anonymous), or ends the program when there is no memory. The mapping is not reserved (MAP_NORESERVE, 0x4000): it is address space until it is touched, so under the kernel's default overcommit heuristic a host with less memory than a region can still grant it. Strict overcommit (vm.overcommit_memory=2) ignores the flag, and other limits still apply.
  func CLen(p i64) i64  // CLen is the length of the NUL-terminated string at p.
  func Write(fd i64, p i64, n i64) i64  // Write writes all n bytes at p to fd. It returns 0, or -1 on error.
  func Stdout(p i64, n i64) i64  // Stdout writes n bytes at p to standard output.
  func Stderr(p i64, n i64) i64  // Stderr writes n bytes at p to standard error.
  func CStr(io *Cap, p i64, n i64) i64  // CStr copies n bytes at p into a new NUL-terminated string.
  func Open(path i64, flags i64, mode i64) i64  // Open opens the NUL-terminated path; it returns an fd, or < 0 on error.
  func Close(fd i64) i64  // Close closes fd.
  func Read(fd i64, p i64, n i64) i64  // Read reads up to n bytes from fd into p; it returns the count, 0 at the end, or < 0 on error.
  func Fstat(fd i64, buf i64) i64  // Fstat fills buf (256 bytes) with struct stat for fd: size at 48.
  func FstatAt(path i64, buf i64) i64  // FstatAt fills buf (256 bytes) with struct stat for the NUL-terminated path.
  func ReadFile(io *Cap, path i64, pathn i64, outp i64, outn i64) i64  // ReadFile reads the whole file named by path (pathn bytes). It returns 0 and stores the data address at outp and its length at outn (allocate two 8-byte cells and load64 them), or -1. The data is NUL-terminated.
  func WriteFile(io *Cap, path i64, pathn i64, data i64, datan i64, mode i64) i64  // WriteFile creates or truncates path with permission mode (493 is 0755, 420 is 0644) and writes datan bytes. It returns 0, or < 0 on error.
  func Fsync(fd i64) i64  // Fsync waits until everything written to fd is on the disk. On a directory's fd it makes the names in it (a create, rename, or unlink) durable. It returns 0, or < 0 on error.
  func Rename(from i64, to i64) i64  // Rename moves the file at the NUL-terminated path from to the path to, replacing what is there in one step: a reader sees the old file or the new one, never a part of either.
  func Unlink(path i64) i64  // Unlink removes the name at the NUL-terminated path.
  func DirLen(path i64, pathn i64) i64  // DirLen is the length of path's directory part, its last "/" included; 0 when path has none.
  func PutInt(dst i64, v i64) i64  // PutInt writes v (v >= 0) in decimal at dst, without a NUL, and returns the address after the last digit.
  func CreateTemp(io *Cap, path i64, pathn i64) *Temp  // CreateTemp makes a new, empty file beside path (pathn bytes), named <dir>/.<base>.<pid>.<n>.ovid-tmp, open for writing. The file is created exclusively and private (0600), so a name someone else put there, a symlink included, is never written through: the next n is tried. Check fd on the result.
  func FillTemp(t *Temp, data i64, datan i64, mode i64, sync bool) i64  // FillTemp writes datan bytes to the temp file t, gives it permission mode (not masked by the umask) once the data is in, syncs it if sync, and closes it. It returns 0, or < 0 on error, and then the file is removed. An error that only close reports (a write the kernel had put off) counts.
  func SyncPath(path i64) i64  // SyncPath syncs the file or directory at the NUL-terminated path (see Fsync), which must be readable. It returns 0, or < 0 on error.
  func WriteFileAtomic(io *Cap, path i64, pathn i64, data i64, datan i64, mode i64) i64  // WriteFileAtomic replaces or creates path (pathn bytes) with datan bytes and permission mode in one step: the data goes to a temp file in the same directory (see CreateTemp), which is renamed over path. Another process sees the old file or the new one, never a part, and a program that is running from path can be replaced. Nothing is synced, so after a power loss the file may be empty: use it for a file that can be made again, WriteFileDurable for one that cannot. It returns 0, or < 0 on error, and then path is untouched.
  func WriteFileDurable(io *Cap, path i64, pathn i64, data i64, datan i64, mode i64) i64  // WriteFileDurable is WriteFileAtomic that also survives a crash: the temp file is synced before the rename and the directory after it, so once it returns 0 the new content is on the disk. The two syncs cost milliseconds on a disk. It returns < 0 on error. If the temp file or the rename failed, path is untouched; but the last step, syncing the directory, comes after the rename, so when that is what failed path already holds the new content and it is only not known to be durable. A caller that must tell the two apart reads path back.
  func Getdents(fd i64, buf i64, n i64) i64  // Getdents reads directory entries from fd (opened with O_DIRECTORY) into buf, at most n bytes, as linux_dirent64 records: d_ino at 0, d_off at 8, d_reclen (u16) at 16, d_type at 18 (4 dir, 8 file, 10 symlink, 0 unknown), NUL-terminated name at 19. Returns the bytes read, 0 at the end, or < 0.
  func Mkdir(path i64) i64  // Mkdir creates the directory at the NUL-terminated path, mode 0755.
  func IsDir(io *Cap, path i64, pathn i64) i64  // IsDir is 1 if path (pathn bytes) is a directory, else 0.
  func Print(p i64) i64  // Print writes the NUL-terminated string at p to standard output, so a literal needs no length: ovid/io.Print(strptr("hi\n")).
  func Eprint(p i64) i64  // Eprint is Print to standard error.
  func PrintInt(io *Cap, v i64) i64  // PrintInt writes v in decimal to standard output. It uses heap scratch and gives it back, so it may be called in a loop.
  func WriteInt(io *Cap, fd i64, v i64) i64  // WriteInt writes v in decimal to fd.

package ovid/mem
  type Buf struct { data i64; len i64; cap i64 }
  func Eq(a i64, an i64, b i64, bn i64) bool  // Eq reports whether the an bytes at a equal the bn bytes at b.
  func EqC(p i64, lit i64, n i64) bool  // EqC compares the NUL-terminated string at p with n bytes at lit: ovid/mem.EqC(arg, strptr("-v"), strlen("-v")).
  func Copy(dst i64, src i64, n i64) i64  // Copy copies n bytes from src to dst and returns n.
  func New(io *ovid/io.Cap, cap i64) *Buf  // New is an empty Buf with room for cap bytes.
  func Grow(io *ovid/io.Cap, b *Buf, need i64) i64  // Grow makes room for need more bytes and returns 0.
  func WByte(io *ovid/io.Cap, b *Buf, c i64) i64  // WByte appends the byte c.
  func WBytes(io *ovid/io.Cap, b *Buf, p i64, n i64) i64  // WBytes appends n bytes at p.
  func FormatI64(dst i64, v i64) i64  // FormatI64 writes v in decimal at dst and returns the length. dst needs 52 bytes: digits are staged at dst+32.
  func HexOut(dst i64, v i64, width i64) i64  // HexOut writes the low width nibbles of v as lowercase hex at dst.

package ovid/test
  func Eq(io *ovid/io.Cap, got i64, want i64) bool  // Eq reports whether got equals want. If not, it writes "got <got>, want <want>" to stderr, which ovid test shows as the failed test's output: if !ovid/test.Eq(io, Sum(s), 10) { return 1 }
  func True(ok bool, msg i64) bool  // True reports ok. If it is false, it writes msg (a NUL-terminated literal: strptr("...")) and a newline to stderr.