ava/docs/revision2.md
2024-07-31 10:09:47 -05:00

10 KiB

Ava R2

This document specifies the second revision of the Ava programming language, started 2024-07-18.

Table of Contents

Names

Names in Ava source code are user-defined strings that provide some form of identifier to some component that may be named. All names in Ava follow the same rules.

Name Rules

  • Names are UTF-8 strings.
  • Names may not be a reserved keyword.
  • Names may not be a reserved symbol/operator.
  • Names may not include whitespace characters.
  • Names may not include .
  • Names may not include "
  • Names may not include '
  • Names may not include ,
  • Names may not include :
  • Names may not include \
  • Names may not include (
  • Names may not include )
  • Names may not begin with a number.
  • Two definitions may not share the same name in scope.
    • This implies that names may not reuse the name of any standard type that is always considered in scope, such as Int32 or Boolean.

Name Targets

The following items may be named in Ava:

Name Examples

  • foo
  • foo-bar
  • foo_bar
  • <=
  • >
  • ==
  • %
  • f1
  • _foo
  • &&
  • ||

Reserved Keywords

namespace

Used to specify the Namespace of a file.

import

Used to declare an Import within a file.

private

Used to mark a Definition as Private.

internal

Used to mark a Definition as Internal.

let

Declares an immutable Variable binding.

mut

Declares a mutable Variable binding.

const

Declares a Constant definition.

given

Used to define type constructors within the scope of some definition.

fn

Indicates a function definition. Each function may or may not be implemented.

class

Indicates a type class definition.

instance

Indicates the definition of a specific type class instance.

match

Keyword that identifies a match expression.

λ

Alias for the case keyword.

case

Defines a pattern matching case within some pattern matching context (either a function implementation or a match expression).

end

Used to indicate the end of some block.

type

Declare a type definition.

opaque

Declare an opaque type.

record

Define a record (immutable data structure).

enum

Define a new enumeration (sum type).

as

Used to rename a specific import.

if

Denotes an if/then/else expression.

then

Required after the predicate of an if/then/else expression.

else

Required after the first block of an if/then/else expression.

true

The Boolean value true.

false

The Boolean value false.

object

Define a singleton object.

MISSING SOME VALUES

TODO: Things like do/return, infix, const

TODO: Consider abstract to be used for abstract type class functions.

Reserved Symbols and Operators

. (Access Operator)

= (Assignment Operator)

: (Type Assignment Operator)

:: (Type Class Membership Operator)

( (Open Parenthesis)

) (Close Parenthesis)

, (Comma)

=> (Case Implementation Operator)

-> (Type Function Operator)

_ (Anonymous Value Binding)

| (Type Union Operator)

-- (Comment Prefix)

Namespaces

The primary organizational concept in Ava is the namespace. Namespaces must be explicitly declared in every Ava file.

Rules

  • Every Ava source file must begin with a namespace declaration.
  • The first non-comment code in any Ava source file must be a namespace declaration.
  • Namespaces must contain at least one name.
  • Namespaces may concatenate names using the . character.
  • Multiple files may share the same namespace.
  • Namespaces are public by default. This means that if any definition is not marked private or internal, all definitions will be exported.

Syntax Specification

namespace <name>[.<name>]*

Syntax Examples

namespace foo
namespace foo.bar
namespace foo.bar.baz_buzz
namespace foo.bar.v0

Imports

An import can be used to pull other namespaces, or definitions from other namespaces, into the current scope.

Rules

  • Imports must follow the namespace declaration.
  • Imports may not be placed after any definition.
  • Glob imports may not use as to rename (since they have no specific target to rename).

Syntax Specification

import <name>[.<name>]*[.\* | as <name>]

Syntax Examples

-- import a namespace, which can be referenced by name
import foo.bar

-- import everything within some namespace
import foo.bar.baz.*

-- import a specific definition from within a namespace
import foo.bar.SomeDef

-- import and rename a namespace
import baz.buzz as bb

-- import and rename a specific definition from within a namespace
import baz.buzz.FooBar as Foo

Definitions

A "definition" is something that might be defined at the top level of any Ava source file. These include:

Definition Accessibility

All definitions are public by default. All definitions may be modified to be either internal or private.

Internal Accessibility

internal definitions are only accessible within their namespace. This means that they are shared across files that share a namespace.

internal fn foo: Int32 -> Int32
    λ x => x + 1
end fn

Unused internal definitions are considered errors.

Private Accessibility

private definitions are only accessible within their file. Even if another file shares a namespace, private definitions are invisible to that file.

private fn foo: Int32 -> Int32
    λ x => x + 1
end fn

Unused private definitions are considered errors.

Comments

Comments are sections of code that are not parsed as code. They are used to add in-code documentation.

Rules

  • All comments begin with --. This is a prefix, -- tells the parser to ignore any following content on sight.
  • Multi-line comments are not supported.
  • Names may not include the comment designator.

Standard Comment Syntax

Comments start with --. If that string is detected, the comment initializer to the end of the line will be completely ignored.

Docstring Comment Syntax

Docstrings start with --- and must precede a definition at the top level of some source file. Docstrings may be parsed in a separate process to produce a documentation artifact.

Examples

--- This is a docstring for the function definition.
fn foo: String -> String
    -- This is a regular comment.
    --- This is a regular comment, because it is not at the definition level.
    λ x => x + x
end fn

Constants

Constants are top-level definitions in Ava. They are named, concrete values with a concrete type.

Rules

  • Constants must be defined at the top level of Ava source files.
  • Constants must have an explicit type (they may not infer).
  • Constants must have a concrete type.
  • Constants must have a literal value.
  • Constants may reference the value of another constant.
  • Constants may be a record, itself defined in terms of literal values or constant values.

Note that these rules exist to permit constant definitions that are not limited to literal values.

Syntax

Constants are defined using the const keyword.

const <name>: <type> = <value>

Examples

const foo: String = "foo"
const bar: Int32 = 12

record Foo
    x: String,
    y: Int32,
end record

const baz: Foo = Foo(foo, bar)

Functions

Functions are top-level definitions in Ava, values in Ava, and are the heart of programming in Ava. In general, functions have type A -> B, where input of type A produces a result of type B.

Functions Without Arguments

Technically, there are no functions without arguments in Ava. All functions are pure, and thus a function without arguments is just a value. The Unit type can be used as a "nothing" argument, as it's a singleton object.

given A
record IO
    thunk: Unit -> A
end record

given A
fn io_new: (Unit -> A) -> IO A
    λ thunk => IO(thunk = thunk)
end fn

fn demonstrate: Unit -> IO Int32
    λ () =>
        block
            let foo: Int32 = 10
            let bar: IO Int32 = io_new λ () => foo
            bar
        end block
end fn

Functions With Multiple Arguments

Arguments should be chained with -> in Ava:

import ava.string

fn example: String -> Int32 -> Int32
    λ x y => (string.length x) + y
end fn

Technically, this represents a curried function with left associativity:

A -> B -> C -> D == ((A -> B) -> C) -> D

This means that Ava naturally supports curried functions with partial application:

fn example: Int32 -> Int32 -> Int32 -> Int32
    λ x y z => x + y + z
end fn

let add1: Int32 -> Int32 -> Int32 = example 1

-- Result: 6
let result = add1 2 3

In this case, example 1 invokes example with an input of literal value 1, which results in a partial application of example that looks like:

fn partial: Int32 -> Int32 -> Int32
    λ y z => 1 + y + z
end fn