10 KiB
Ava R2
This document specifies the second revision of the Ava programming language, started 2024-07-18.
Table of Contents
- Names
- Reserved Keywords
- Reserved Symbols and Operators
- Namespaces
- Imports
- Definitions
- Comments
- Constants
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
orBoolean
.
- This implies that names may not reuse the name of any standard type
that is always considered in scope, such as
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:
- Constants
- Functions
- Infix Functions
- Type Classes
- Type Class Instances
- Enumerations
- Records
- Type Definitions
- Opaque Types
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