From c7aa5e19a9ffd6b3a6b8b8d69695c07b1b16b7a7 Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Wed, 24 Jan 2024 22:52:03 -0600 Subject: [PATCH] First dump of madness. --- .pre-commit-config.yaml | 12 +++++ README.md | 13 ++++++ constants.md | 30 ++++++++++++ definitions.md | 29 ++++++++++++ effects.md | 8 ++++ enumerations.md | 26 +++++++++++ expressions.md | 45 ++++++++++++++++++ functions.md | 34 ++++++++++++++ general-syntax.md | 58 +++++++++++++++++++++++ holes.md | 4 ++ memory-management.md | 1 + names.md | 23 +++++++++ namespaces.md | 76 ++++++++++++++++++++++++++++++ pattern-matching.md | 29 ++++++++++++ records.md | 101 ++++++++++++++++++++++++++++++++++++++++ recursion.md | 3 ++ standard-library.md | 5 ++ strings.md | 1 + table-of-contents.md | 27 +++++++++++ tuples.md | 61 ++++++++++++++++++++++++ type-aliases.md | 1 + type-classes.md | 1 + type-unions.md | 8 ++++ types.md | 69 +++++++++++++++++++++++++++ values.md | 1 + variables.md | 8 ++++ 26 files changed, 674 insertions(+) create mode 100644 .pre-commit-config.yaml create mode 100644 README.md create mode 100644 constants.md create mode 100644 definitions.md create mode 100644 effects.md create mode 100644 enumerations.md create mode 100644 expressions.md create mode 100644 functions.md create mode 100644 general-syntax.md create mode 100644 holes.md create mode 100644 memory-management.md create mode 100644 names.md create mode 100644 namespaces.md create mode 100644 pattern-matching.md create mode 100644 records.md create mode 100644 recursion.md create mode 100644 standard-library.md create mode 100644 strings.md create mode 100644 table-of-contents.md create mode 100644 tuples.md create mode 100644 type-aliases.md create mode 100644 type-classes.md create mode 100644 type-unions.md create mode 100644 types.md create mode 100644 values.md create mode 100644 variables.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..516c4a0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: fix-byte-order-marker + - id: mixed-line-ending + args: ['--fix=lf'] + description: Enforces using only 'LF' line endings. + - id: trailing-whitespace diff --git a/README.md b/README.md new file mode 100644 index 0000000..a93cbad --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# The Ava Programming Language + +This repository is currently a research project. The project is to explore ideas +in programming language design by specifying _Ava_. + +To start reading, please use the [Table of Contents](./table-of-contents.md) + +## What is Ava? + +Ava is a programming language research project that is in the design and +specification phase. There is no grammar, parser, compiler, or way to use any +Ava code. Ava is a way to get ideas out of my head, challenged, and further +explored. diff --git a/constants.md b/constants.md new file mode 100644 index 0000000..6193789 --- /dev/null +++ b/constants.md @@ -0,0 +1,30 @@ +# Constants + +_Constants_ are definitions which provide a name and explicit type for some +[value](values.md). The `const` keyword (definition type) is used to define a +constant. + +``` +const weak_pi: Float64 is 3.14 +const my_name: String is "Pat" +``` + +In general: + +``` +const : is +``` + +Since a constant is categorized as a [definition](general-syntax.md#definitions) +is uses the `is` keyword rather than the `:=` operator. + +## Restrictions + +Constants are subject to the following restrictions: + +- Constants MUST refer to a literal value. +- Constants MUST have an explicit type. +- Constants MAY refer to a record instance. +- Constants CANNOT require a type constructor. +- Constants CANNOT refer to functions. +- Constants CANNOT refer to expressions. diff --git a/definitions.md b/definitions.md new file mode 100644 index 0000000..e83008b --- /dev/null +++ b/definitions.md @@ -0,0 +1,29 @@ +# Definitions + +_Definitions_ are Ava constructs that may live at the top level within some +namespace. [Functions](functions.md) in particular may _also_ live within type +class (and instance) definitions. + +## Syntax + +All definitions adhere to the following syntax: + +``` +[export] is +``` + +Each definition type ultimately controls the description and body. All +definition names adhere to standard [Name](names.md) rules. + +## Supported Definition Types + +TODO: This whole document... can go away? Need inventory of keywords. + +- [`const`](constants.md) +- [`record`](records.md) +- [`type`](types.md) +- [`alias`](type-aliases.md) +- [`class`](type-classes.md) +- [`instance`](type-classes.md#instances) +- [`enum`](enumerations.md) +- [`fn`](functions.md) diff --git a/effects.md b/effects.md new file mode 100644 index 0000000..1222ff6 --- /dev/null +++ b/effects.md @@ -0,0 +1,8 @@ +# Effects + +## The Effect Type + +The type `IO[E, A]` represents an effect which will produce `A` when executed, +and may fail with an error of type `E`. There exists a type +`type Task[A] = IO[Nothing, A]` that represents effects that cannot fail with an +error. diff --git a/enumerations.md b/enumerations.md new file mode 100644 index 0000000..4311d19 --- /dev/null +++ b/enumerations.md @@ -0,0 +1,26 @@ +# Enumerations + +Enumerations are sum types. + +## Example: The Option Type + +``` +enum Option[A] is + record Some[A] is (value: A) + object None + +fn some[A]: (a: A) => Some[A] is a => Some(a) +``` + +## Example: The Either Type + +TODO: Describe import behavior! Does importing Either import Left/Right? + +``` +enum Either[L, R] is + record Left[L] is { value: L } + record Right[R] is { value: R } + +fn left[L]: (left: L) => Either[L, Nothing] is l => Left { l } +fn right[R]: (right: R) => Either[Nothing, R] is r => Right { r } +``` diff --git a/expressions.md b/expressions.md new file mode 100644 index 0000000..181fef4 --- /dev/null +++ b/expressions.md @@ -0,0 +1,45 @@ +# Expressions + +_Expressions_ are the basis of programming in Ava. An expression is some code +that will produce a _value_ when evaluated. + +## Syntax + +Most non-[Definition](definitions.md) code is considered an expression. +Expressions include: + +- Literals +- Variables +- Function Calls +- Control Expressions + +In general, using a Literal or a Variable for an expression wraps that value in +a `pure` function that returns the value when evaluated. + +## Control Expressions + +### If Then + +The `if/then` expression expects an expression that returns a _Boolean Value_. +That expression is evaluated. If it evaluates to `true`, the `then` expression +is evaluated. Otherwise the `else` expression is evaluated. + +``` +if then else +``` + +### Do Yield + +TODO: This is incomplete + +The `do/yield` expression allows for imperative composition of monadic +expressions. + +``` +do + x <- foo() + y <- bar() + _ <- baz() +yield + x + y +``` diff --git a/functions.md b/functions.md new file mode 100644 index 0000000..a4fd549 --- /dev/null +++ b/functions.md @@ -0,0 +1,34 @@ +# Functions + +TODO: Scala syntax? Haskell syntax? Implications? How do we specify names for +parameters? Do we want to do that? How do function arguments, tuples, and +records all relate to one another? Are they all just the same? How do we talk +about curried functions? Is that supported? Is there some other mechanism? + +TODO: This is all 100% up in the air right now. + +``` +fn example: () => Int32 is 42 +example +example() +``` + +``` +fn example: String => Int32 is str => length(str) +example "foo" +example("foo") +``` + +``` +fn example: { x: Int32, y: Int32 } => Int32 is (x, y) => x + y +example 1 2 +example(1, 2) +example { x: 1, y: 2 } +``` + +``` +fn map[F[*], A, B]: (F[A]) => (A => B) => F[B] is + (fa) (f) => fa f + +map(list)(x => x + 1) +``` diff --git a/general-syntax.md b/general-syntax.md new file mode 100644 index 0000000..151205a --- /dev/null +++ b/general-syntax.md @@ -0,0 +1,58 @@ +# General Syntax + +## Type Binding + +Some name may be bound to some type by using the `:` (colon) syntax. + +``` +name: Type +``` + +Type bindings are used for all such occurrences. This includes: + +- [Defining Variables](variables.md) +- [Defining Records](records.md) +- [Defining Functions](functions.md) + +## Value Binding + +Some name may be bound to some value by using the `:=` syntax. + +``` +name := value +name: Type := value +``` + +Value bindings are used for all such occurrences. This includes: + +- [Defining Variables](variables.md) +- [Instantiating Records](records.md) + +## Comments + +Comments are lines where the first non-whitespace characters are `--`. This was +selected for ease of typing paired with low visual noise. + +``` +-- this is a comment +-- this is another comment +let x := "foo" -- this will not compile, comments cannot be mixed with code +``` + +Ava does not support multi-line comments. + +## Code Documentation + +Code documentation is written using comments that both directly-precede certain +definitions and have 3 `-` characters: + +``` +--- This function does some foo as well as some bar. +--- @param x Documentation for the `x` parameter. +fn foo_bar: (x: String) => Int32 +``` + +- Code documentation respects Markdown. +- TODO: Link syntax. +- TODO: Supported definitions (example for each) +- TODO: Parameter documentation diff --git a/holes.md b/holes.md new file mode 100644 index 0000000..f0e935e --- /dev/null +++ b/holes.md @@ -0,0 +1,4 @@ +# Holes + +`???` is a hole that resolves to any type, used for development, cannot be used +at runtime at all (it blows up). diff --git a/memory-management.md b/memory-management.md new file mode 100644 index 0000000..7327319 --- /dev/null +++ b/memory-management.md @@ -0,0 +1 @@ +# Memory Management diff --git a/names.md b/names.md new file mode 100644 index 0000000..bfffa65 --- /dev/null +++ b/names.md @@ -0,0 +1,23 @@ +# Names + +_Names_ are user-defined strings that name defined items. All names (variable +names, function names, etc.) follow the same rules. + +Names are UTF-8 strings. The following values are _excluded_: + +- Any reserved keyword +- Any whitespace character +- `.` +- `:` +- `=` +- `"` or `'` +- `[` or `]` +- `{` or `}` +- `(` or `)` + +## Convention + +- Variables use `lower_snake_case` +- Functions use `lower_snake_case` +- Generic Types use `UpperCamelCase` but prefer single letters such as `A`. +- Data Types, aliases, etc. use `UpperCamelCase` diff --git a/namespaces.md b/namespaces.md new file mode 100644 index 0000000..5ec4d42 --- /dev/null +++ b/namespaces.md @@ -0,0 +1,76 @@ +# Namespaces + +_Namespaces_ are the primary organizational unit of Ava. Every Ava file _must_ +define a namespace. The namespace is ALWAYS the first line of the file. + +## Syntax + +Each namespace is an ordered, delimited, list of [names](names.md). Each name is +separated by the `.` character. + +``` +[.]* +``` + +- At least one name MUST be specified. +- CANNOT contain more than one `.` character in a row. +- CANNOT begin with `.` character. +- CANNOT end with `.` character. +- CANNOT use a [Reserved Name](#reserved-names). + +### Examples + +- `foo` +- `foo.bar.baz` +- `language.v0` +- `collections.v0` + +## Reserved Names + +Any namespaces provided with a particular Ava distribution are considered +_reserved_. This means that, for example, the following namespaces cannot be +used: + +- `language` +- `collections` + +## Language Namespace + +The `language` namespace is automatically imported into every Ava file. + +## Exports + +Each namespace MAY _export_ defintions using the `export` keyword. If some +definition is exported, it may be [imported](#imports) into another namespace. +Definitions are NOT exported by default -- it is an opt-in process. As noted in +the [Definitions](definitions.md) documentation, the `export` keyword may be +used at the beginning of _any_ definition. + +## Imports + +Imports immediately follow the namespace definition in each file. Each file may +have zero or more imports. Imports bring definitions from another namespace into +scope. + +### Syntax + +Each import is a single, fully-qualified name. Imported names MAY be mapped to +some alternative name. An import may refer to a specific namespace or it may +refer to some specific definition within a namespace. + +TODO: Account for type classes. How can we easily import instances? One option +is to NOT do this. Ever. Just resolve EVERY possible `instance` during +compilation and make them available within the scope of the program. Global. +Very strict one-instance-per-thing side-effect, but could be useful. + +``` +import [.] [as ] +``` + +### Examples + +``` +import collections.NonEmptyList as NEL +import collections.Queue +import collections +``` diff --git a/pattern-matching.md b/pattern-matching.md new file mode 100644 index 0000000..c0eddac --- /dev/null +++ b/pattern-matching.md @@ -0,0 +1,29 @@ +# Pattern Matching + +TODO: This needs just more description and examples and pressure testing. + +Ava supports pattern matching and value destructuring. + +## The Match Expression + +``` +match + case => + case => + ... +``` + +## The Default Case + +The _default case_, `_`, is used to catch all unmatched cases. This can be used +to only match a partial set of possible values: + +TODO: WIP gotta figure out functions. + +``` +fn example: String => Int32 is + str => match str + case "foo" => 1 + case "bar" => 2 + case _ => 3 +``` diff --git a/records.md b/records.md new file mode 100644 index 0000000..714bbae --- /dev/null +++ b/records.md @@ -0,0 +1,101 @@ +# Records + +Records may be defined. Each record contains one or more named fields. Note that +records are just [tuples](tuples.md) with named fields. In many ways, the two +can be interchanged. + +``` +record Foo is (x: String, y: Int32) +``` + +Record fields are both _ordered_ and _named_. + +- [Anonymous Records](#anonymous-records) +- [Generic Records](#generic-records) +- [Instantiating Records](#instantiating-records) +- [Copying Data](#copying-data) +- [Accessing Record Data](#accessing-record-data) +- [Tuple Interactions](#tuple-interactions) +- [Destructuring Records](#destructuring-records) + +## Anonymous Records + +Records do _not_ need to be named. + +``` +(x: String, y: String, z: String) +``` + +## Generic Records + +Records may have generically typed data, and accept a type constructor: + +``` +record Foo[A, B] is (x: A, y: B) +``` + +## Instantiating Records + +``` +record Foo is (x: String, y: Int32) + +let foo1 := Foo("foo", 1) +let foo2 := Foo(x := "foo", y := 1) +``` + +## Copying Data + +Copy syntax allows any record to be duplicated, with any fields explicitly +overridden by some value: + +``` +record Foo is (x: String, y: Int32) + +let foo := Foo("foo", 1) +let bar := copy(foo) +let baz := copy(foo, ("y": 2)) +``` + +## Accessing Record Data + +The `.` operator is used to access individual fields on a record. + +``` +record Foo is (x: String, y: Int32) + +let foo := Foo("foo", 1) +let bar := foo.x +``` + +Note that because records are just tuples, tuple syntax continues to work: + +``` +let baz: Int32 := foo._2 +``` + +## Tuple Interactions + +Use the following record definition for this section: + +``` +record Foo is (x: String, y: Int32) +``` + +Tuples can be assigned from records. + +``` +let foo := Foo("foo", 1) +let some_tuple: (String, Int32) := foo +``` + +Records can be assigned from tuples. + +``` +let some_tuple := ("foo", 1) +let foo: Foo := some_tuple +``` + +## Destructuring Records + +Records can be _destructured_ via [pattern matching](pattern-matching.md) +capabilities. This can take two possible forms. diff --git a/recursion.md b/recursion.md new file mode 100644 index 0000000..b46763a --- /dev/null +++ b/recursion.md @@ -0,0 +1,3 @@ +# Recursion + +TCO is supported. diff --git a/standard-library.md b/standard-library.md new file mode 100644 index 0000000..2e92ad6 --- /dev/null +++ b/standard-library.md @@ -0,0 +1,5 @@ +# Standard Library + +Things like supporting different standard functions, type class list, etc. + +What about collections? diff --git a/strings.md b/strings.md new file mode 100644 index 0000000..ed218ce --- /dev/null +++ b/strings.md @@ -0,0 +1 @@ +# Strings diff --git a/table-of-contents.md b/table-of-contents.md new file mode 100644 index 0000000..252caa7 --- /dev/null +++ b/table-of-contents.md @@ -0,0 +1,27 @@ +# Table of Contents + +- [Names](names.md) +- [Namespaces](namespaces.md) + - [Exports](namespaces.md#exports) + - [Imports](namespaces.md#imports) +- [Definitions](definitions.md) +- [General Syntax](general-syntax.md) +- [Expressions](expressions.md) +- [Values](values.md) +- [Constants](constants.md) +- [Variables](variables.md) +- [Effects](effects.md) +- [Records](records.md) +- [Types](types.md) +- [Type Aliases](type-aliases.md) +- [Type Classes](type-classes.md) +- [Enumerations](enumerations.md) +- [Type Unions](type-unions.md) +- [Functions](functions.md) +- [Strings](strings.md) +- [Tuples](tuples.md) +- [Memory Management](memory-management.md) +- [Holes](holes.md) +- [Pattern Matching](pattern-matching.md) +- [Recursion](recursion.md) +- [Standard Library](standard-library.md) diff --git a/tuples.md b/tuples.md new file mode 100644 index 0000000..8bab6a9 --- /dev/null +++ b/tuples.md @@ -0,0 +1,61 @@ +# Tuples + +Tuples are heterogeneous, fixed-size, collections of values. Tuples contain 0 to +`N` values, where `N` is unbounded. Tuples are similar to [Records](records.md), +but records have named parameters. + +## Syntax + +``` +let x := () +let y := ("foo") +let z := ("foo", 1) +let w := ("foo", 1, true, 3.14) +``` + +## Type of a Standard Tuple + +Consider the tuple `("foo", 1, true, 3.14)`. It has type +`(String, Int32, Boolean, Float64)`. + +## The Empty Tuple + +The type `()` has a single possible value, `()`. This is the empty tuple. It is +typically used as a token to indicate side-effects with no other useful output. + +## Accessing Tuple Members + +Each tuple member is _indexed_ and can be directly accessed via a property of +that index: + +``` +let w := ("foo", 1, true) +let x := w._1 +let y := w._2 +let z := w._3 +``` + +## Destructuring Tuples + +Tuples can be _destructured_ via [pattern matching](pattern-matching.md) +capabilities. This can take two possible forms. + +### Destructured Binding + +Tuples may be destructured at the point of binding. + +``` +let w := ("foo", 1, true) +let (x, y, z) := w +``` + +### Destructured Match Case + +``` +let w := ("foo", 1, true) + +let z := + match w + case ("foo", _, x) => x + case _ => false +``` diff --git a/type-aliases.md b/type-aliases.md new file mode 100644 index 0000000..d9dd816 --- /dev/null +++ b/type-aliases.md @@ -0,0 +1 @@ +# Type Aliases diff --git a/type-classes.md b/type-classes.md new file mode 100644 index 0000000..b15218f --- /dev/null +++ b/type-classes.md @@ -0,0 +1 @@ +# Type Classes diff --git a/type-unions.md b/type-unions.md new file mode 100644 index 0000000..60804e2 --- /dev/null +++ b/type-unions.md @@ -0,0 +1,8 @@ +# Type Unions + +TODO: Explore. What syntax is better? `|` seems better. + +``` +type Foo = String | Int32 +type Bar = Boolean or String or Int32 +``` diff --git a/types.md b/types.md new file mode 100644 index 0000000..f7c66cf --- /dev/null +++ b/types.md @@ -0,0 +1,69 @@ +# Types + +- [The Ava Type System](#the-ava-type-system) +- [The Nothing Type](#the-nothing-type) +- [Type Definitions](#type-definitions) + - [Type Constructors](#type-constructors) +- [Higher Kinded Types](#higher-kinded-types) + +## The Ava Type System + +Ava is based on a static type system with support for higher-kinded types. Ava +does not support inheritence. Ava _does_ support sum types +([Enumerations](enumerations.md)) and _does_ support +[type classes](type-classes.md). + +## The Nothing Type + +The special type, `Nothing`, cannot be instantiated. It can be used to describe +cases that cannot be expressed. For example, `Either[Nothing, String]` cannot +ever be a `Left`. `Nothing` can be used to satisfy any type requirement. For +example, `Either[Nothing, String]` satisfies `Either[Int32, String]`. + +## Type Definitions + +Types may directly, at the top level, be defined in terms of some +[type constructor](#type-constructors): + +``` +type TupleList[A, B] is List[(A, B)] +``` + +Or in general terms: + +``` +type is +``` + +### Type Constructors + +Type constructors essentially allow for types to be defined in terms of some set +of type inputs. Consider the prior example: + +``` +type TupleList[A, B] is List[(A, B)] +``` + +The `TupleList` type requires two type arguments, `A` and `B`, to be constructed +as a concrete type. For example, `TupleList[String, Int32]` is a concrete type. + +## Higher Kinded Types + +Type constructors in Ava may be higher order (e.g. not order 1) functions: + +``` +// Assume this exists +type List[A] is ??? +fn map[A, B]: (List[A], (A) => B) => List[B] + +type class Functor[F[*]] is + fn map[A, B]: (F[A])((A) => B) => F[B] + +instance Functor[List] is + fn map[A, B]: (List[A])((A) => B) => List[B] is + (list)(f) => map(list, f) +``` + +In this example, `Functor[F[*]]` is a type constructor which accepts a single +type argument. That type argument is a type constructor that accepts a single +concrete (order 0) type argument. diff --git a/values.md b/values.md new file mode 100644 index 0000000..89a9695 --- /dev/null +++ b/values.md @@ -0,0 +1 @@ +# Values diff --git a/variables.md b/variables.md new file mode 100644 index 0000000..bd062b0 --- /dev/null +++ b/variables.md @@ -0,0 +1,8 @@ +# Variables + +- [Immutable Values](#immutable-values) +- [Mutable Values](#mutable-values) + +## Variable Names + +Please refer to [Names](names.md).