Skip to content
Snippets Groups Projects
Unverified Commit bffdb0c1 authored by Yorick Peterse's avatar Yorick Peterse
Browse files

Implement Inko's parser in Inko

The parser is an LL(1) recursive descent parser. While the Ruby parser
was used as a reference, it is not a 1:1 port.

As part of porting the parser to Inko, several syntax changes were made:

1. "where" on methods is not supported by the Inko parser. When we start
   using the parser we will move "where" to traits.

2. The comments "#!" and "##" are replaced with just "#". This makes the
   lexer and parser internals easier, and removes the need for having to
   remember three different comment types.

3. Mutable rest arguments are supported in the Inko parser using
   "mut *NAME", instead of "* mut NAME".

Using the parser is straightforward:

    import std::compiler::parser::Parser

    let parser = Parser.new(input: '10 + 2', file: 'test.inko')

    try! parser.parse

The AST nodes are located in modules under `std::compiler::ast`. For
example, integer literals are in `std::compiler::ast::literals`. For now
there is some minor duplication across some of the AST nodes. For
example, the types of methods (MethodDefinition) and required methods
(RequiredMethodDefinition) are similar. When we start implementing the
compiler we may change this around a bit, or perhaps split the AST nodes
into more separate types.

Fixes https://gitlab.com/inko-lang/inko/issues/172
parent 6338d4ca
No related branches found
No related tags found
No related merge requests found
Showing
with 1991 additions and 772 deletions
Loading
Loading
@@ -182,7 +182,7 @@ module Inkoc
 
loop do
case step.type
when :identifier, :object, :trait
when :identifier, :object, :trait, :return
steps << identifier_from_token(step)
when :constant
symbol = import_symbol_from_token(step)
Loading
Loading
Loading
Loading
@@ -176,6 +176,7 @@ module Inkoc
 
def error_for_undefined_throw(throw_type, block_type, location)
return if block_type.throw_type
return unless throw_type
 
diagnostics.throw_without_throw_defined_error(throw_type, location)
end
Loading
Loading
Loading
Loading
@@ -38,12 +38,12 @@ _INKOC.set_object_name(Block, 'Block')
# building blocks of Inko, such as "Object.new" and the bits necessary to allow
# creating of modules.
impl Object {
## Creates a new instance of `self`, without sending `init` to the instance.
# Creates a new instance of `self`, without sending `init` to the instance.
static def allocate -> Self {
_INKOC.set_object(False, self)
}
 
## Creates a new instance of `self` and sends `init` to the instance.
# Creates a new instance of `self` and sends `init` to the instance.
static def new -> Self {
let obj = allocate
 
Loading
Loading
@@ -55,26 +55,26 @@ impl Object {
def init {}
}
 
## A trait.
##
## A trait can contain both required methods and default implementations of
## methods.
# A trait.
#
# A trait can contain both required methods and default implementations of
# methods.
object Trait {}
 
## An object to use for storing all modules created from source files.
# An object to use for storing all modules created from source files.
object Modules {}
 
## An object containing methods and types defined in a source file.
##
## This object does not define any meaningful methods on its own, as those could
## conflict with the methods defined in a module by the user. Instead, use the
## `std::mirror` module to obtain module information such as its name or file
## path.
# An object containing methods and types defined in a source file.
#
# This object does not define any meaningful methods on its own, as those could
# conflict with the methods defined in a module by the user. Instead, use the
# `std::mirror` module to obtain module information such as its name or file
# path.
object Module {
## The full name of the module.
# The full name of the module.
@name: String
 
## The file path of the module.
# The file path of the module.
@path: String
 
static def new(name: String, path: String) -> Self {
Loading
Loading
#! Generating of ANSI escape sequences.
#!
#! This module provides basic support for generating `String` objects wrapped in
#! [ANSI escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code).
#!
#! # Examples
#!
#! Making a `String` green:
#!
#! import std::ansi
#!
#! ansi.green('hello world') # => "\e[32mhello world\e[0m"
# Generating of ANSI escape sequences.
#
# This module provides basic support for generating `String` objects wrapped in
# [ANSI escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code).
#
# # Examples
#
# Making a `String` green:
#
# import std::ansi
#
# ansi.green('hello world') # => "\e[32mhello world\e[0m"
import std::conversion::ToString
import std::string_buffer::StringBuffer
 
## The start of every ANSI escape sequence.
# The start of every ANSI escape sequence.
let START = "\e["
 
## The sequence to use for terminating an escape sequence.
# The sequence to use for terminating an escape sequence.
let RESET = "\e[0m"
 
## The code for making text bold.
# The code for making text bold.
let BOLD = '1'
 
## The code for the color red.
# The code for the color red.
let RED = '31'
 
## The code for the color green.
# The code for the color green.
let GREEN = '32'
 
## The code for the color cyan.
# The code for the color cyan.
let CYAN = '36'
 
## The code for the color yellow.
# The code for the color yellow.
let YELLOW = '33'
 
## Wraps a `ToString` in an ANSI escape sequence.
# Wraps a `ToString` in an ANSI escape sequence.
def wrap(string: ToString, code: String) -> String {
StringBuffer.new(START, code, 'm', string.to_string, RESET).to_string
}
 
## Makes the `ToString` bold.
# Makes the `ToString` bold.
def bold(string: ToString) -> String {
wrap(string: string, code: BOLD)
}
 
## Applies the color red to the given `ToString`.
# Applies the color red to the given `ToString`.
def red(string: ToString) -> String {
wrap(string: string, code: RED)
}
 
## Applies the color green to the given `ToString`.
# Applies the color green to the given `ToString`.
def green(string: ToString) -> String {
wrap(string: string, code: GREEN)
}
 
## Applies the color cyan to the given `ToString`.
# Applies the color cyan to the given `ToString`.
def cyan(string: ToString) -> String {
wrap(string: string, code: CYAN)
}
 
## Applies the color yellow to the given `ToString`.
# Applies the color yellow to the given `ToString`.
def yellow(string: ToString) -> String {
wrap(string: string, code: YELLOW)
}
#! An ordered, contiguous growable sequence of values.
#!
#! Arrays (sometimes called vectors or lists) can be used to store values of the
#! same type in order. Arrays are mutable and allow you to add new values or
#! remove existing values.
#!
#! # Indexing
#!
#! Like most programming languages Arrays indexes are zero based, with the first
#! value being located at index 0.
#!
#! It's possible to use a negative index (e.g. -2) when accessing or setting
#! indexes, in which case the value will be accessed starting at the back of the
#! Array. This means that -1 will access the last value, -2 the value before
#! that, etc.
# An ordered, contiguous growable sequence of values.
#
# Arrays (sometimes called vectors or lists) can be used to store values of the
# same type in order. Arrays are mutable and allow you to add new values or
# remove existing values.
#
# # Indexing
#
# Like most programming languages Arrays indexes are zero based, with the first
# value being located at index 0.
#
# It's possible to use a negative index (e.g. -2) when accessing or setting
# indexes, in which case the value will be accessed starting at the back of the
# Array. This means that -1 will access the last value, -2 the value before
# that, etc.
 
import std::conversion::ToArray
import std::index::(Index, SetIndex)
Loading
Loading
@@ -20,120 +20,116 @@ import std::length::Length
import std::operators::Equal
 
impl Array!(T) {
## Returns a new Array containing the given values.
##
## # Examples
##
## Creating an empty Array:
##
## Array.new
##
## Creating an Array with values:
##
## Array.new(10, 20, 30)
# Returns a new Array containing the given values.
#
# # Examples
#
# Creating an empty Array:
#
# Array.new
#
# Creating an Array with values:
#
# Array.new(10, 20, 30)
static def new!(V)(*values: V) -> Array!(V) {
values
}
 
## Removes all values of this Array, then returns self.
##
## # Examples
##
## Sending `clear` to an Array will remove all of its elements:
##
## let array = Array.new(10, 20, 30)
##
## array.clear
## array.empty? # => True
##
## Clear will return the Array itself so you can chain message sends:
##
## let array = Array.new(10, 20, 30)
##
## array.clear.length # => 0
# Removes all values of this Array, then returns self.
#
# # Examples
#
# Sending `clear` to an Array will remove all of its elements:
#
# let array = Array.new(10, 20, 30)
#
# array.clear
# array.empty? # => True
#
# Clear will return the Array itself so you can chain message sends:
#
# let array = Array.new(10, 20, 30)
#
# array.clear.length # => 0
def clear -> Self {
_INKOC.array_clear(self)
self
}
 
## Pushes a value to the back of the Array, returning the pushed value.
##
## # Examples
##
## Pushing a value into an Array:
##
## let array = Array.new
##
## array.push(10) # => 10
## array[0] # => 10
# Pushes a value to the back of the Array, returning the pushed value.
#
# # Examples
#
# Pushing a value into an Array:
#
# let array = Array.new
#
# array.push(10) # => 10
# array[0] # => 10
def push(value: T) -> T {
self[length] = value
}
 
## Removes a value from the back of the Array, returning the removed value.
##
## If no value was found, `Nil` is returned instead.
##
## ## Examples
##
## Popping an existing value:
##
## let array = Array.new(10)
##
## array.pop # => 10
## array.empty? # => True
##
## Popping a value when the Array is empty:
##
## let array = Array.new
##
## array.pop # => Nil
# Removes a value from the back of the Array, returning the removed value.
#
# If no value was found, `Nil` is returned instead.
#
# # Examples
#
# Popping an existing value:
#
# let array = Array.new(10)
#
# array.pop # => 10
# array.empty? # => True
#
# Popping a value when the Array is empty:
#
# let array = Array.new
#
# array.pop # => Nil
def pop -> ?T {
(length > 0).if true: {
remove_at(length - 1)
}, false: {
Nil
}
(length > 0).if(true: { remove_at(length - 1) }, false: { Nil })
}
 
## Removes the value at the given index, returning the removed value.
##
## # Examples
##
## Removing an existing value will result in the value being removed from the
## Array and returned:
##
## let array = Array.new(10)
##
## array.remove_at(0) # => 10
## array.empty? # => True
##
## When removing a non-existing value the Array won't be modified, and the
## returned value will be Nil:
##
## let array = Array.new(10)
##
## array.remove_at(1) # => Nil
## array.empty? # => False
# Removes the value at the given index, returning the removed value.
#
# # Examples
#
# Removing an existing value will result in the value being removed from the
# Array and returned:
#
# let array = Array.new(10)
#
# array.remove_at(0) # => 10
# array.empty? # => True
#
# When removing a non-existing value the Array won't be modified, and the
# returned value will be Nil:
#
# let array = Array.new(10)
#
# array.remove_at(1) # => Nil
# array.empty? # => False
def remove_at(index: Integer) -> ?T {
_INKOC.array_remove(self, index)
}
 
## Yields all the values in this `Array` to the supplied `Block`.
##
## While `std::iterator::Iterator` provides an `each` method using this would
## require the allocation of an `Iterator`. Since iterating over an `Array` is
## so common we provide a specialised implementation for those cases.
##
## # Examples
##
## Iterating over all the values in an `Array`:
##
## import std::stdio::stdout
##
## Array.new(10, 20, 30).each do (number) {
## stdout.print(number)
## }
# Yields all the values in this `Array` to the supplied `Block`.
#
# While `std::iterator::Iterator` provides an `each` method using this would
# require the allocation of an `Iterator`. Since iterating over an `Array` is
# so common we provide a specialised implementation for those cases.
#
# # Examples
#
# Iterating over all the values in an `Array`:
#
# import std::stdio::stdout
#
# Array.new(10, 20, 30).each do (number) {
# stdout.print(number)
# }
def each(block: do (T)) {
let mut index = 0
let max = length
Loading
Loading
@@ -147,18 +143,18 @@ impl Array!(T) {
}
}
 
## Yields all the values and their indexes in this `Array` to the supplied
## `Block`.
##
## # Examples
##
## Iterating over the values of an `Array` and their indexes:
##
## import std::stdio::stdout
##
## Array.new(10, 20, 30).each_with_index do (number, index) {
## stdout.print(index) # => 0, 1, 2
## }
# Yields all the values and their indexes in this `Array` to the supplied
# `Block`.
#
# # Examples
#
# Iterating over the values of an `Array` and their indexes:
#
# import std::stdio::stdout
#
# Array.new(10, 20, 30).each_with_index do (number, index) {
# stdout.print(index) # => 0, 1, 2
# }
def each_with_index(block: do (T, Integer)) {
let mut index = 0
 
Loading
Loading
@@ -168,17 +164,17 @@ impl Array!(T) {
}
}
 
## Appends the values of the given `Array` to `self`.
##
## # Examples
##
## Appending one `Array` to another:
##
## let numbers = Array.new(10, 20, 30)
##
## numbers.append(Array.new(40, 50))
##
## numbers.length # => 5
# Appends the values of the given `Array` to `self`.
#
# # Examples
#
# Appending one `Array` to another:
#
# let numbers = Array.new(10, 20, 30)
#
# numbers.append(Array.new(40, 50))
#
# numbers.length # => 5
def append(other: Self) -> Self {
other.each do (value: T) {
push(value)
Loading
Loading
@@ -187,13 +183,13 @@ impl Array!(T) {
self
}
 
## Returns `True` if `self` contains the given value.
##
## # Examples
##
## Checking if an `Array` contains a value:
##
## Array.new(10, 20, 30).contains?(10) # => True
# Returns `True` if `self` contains the given value.
#
# # Examples
#
# Checking if an `Array` contains a value:
#
# Array.new(10, 20, 30).contains?(10) # => True
def contains?(value: T) -> Boolean where T: Equal {
each do (val) {
(val == value).if_true {
Loading
Loading
@@ -206,114 +202,114 @@ impl Array!(T) {
}
 
impl Length for Array!(T) {
## Returns the number of values in this Array.
##
## # Examples
##
## Getting the length of an empty Array:
##
## Array.new.length # => 0
##
## Getting the length of an Array with values:
##
## Array.new(10).length # => 1
# Returns the number of values in this Array.
#
# # Examples
#
# Getting the length of an empty Array:
#
# Array.new.length # => 0
#
# Getting the length of an Array with values:
#
# Array.new(10).length # => 1
def length -> Integer {
_INKOC.array_length(self)
}
}
 
impl Index!(Integer, T) for Array!(T) {
## Returns the value at the given index, or Nil if no value was found.
##
## # Examples
##
## Retrieving a value by its index:
##
## let array = Array.new(10, 20, 30)
##
## array[1] # => 20
##
## We can also use a negative index to access a value from the back of the
## Array:
##
## let array = Array.new(10, 20, 30)
##
## array[-2] # => 20
##
## Accessing an out-of-bounds index will produce a Nil:
##
## let array = Array.new
##
## array[0] # => Nil
# Returns the value at the given index, or Nil if no value was found.
#
# # Examples
#
# Retrieving a value by its index:
#
# let array = Array.new(10, 20, 30)
#
# array[1] # => 20
#
# We can also use a negative index to access a value from the back of the
# Array:
#
# let array = Array.new(10, 20, 30)
#
# array[-2] # => 20
#
# Accessing an out-of-bounds index will produce a Nil:
#
# let array = Array.new
#
# array[0] # => Nil
def [](index: Integer) -> ?T {
_INKOC.array_at(self, index)
}
}
 
impl SetIndex!(Integer, T) for Array!(T) {
## Stores a value at the given index, then returns it.
##
## If the index is out of bounds then all preceding indexes that are not set
## will be filled with Nil values.
##
## This method will return the value that was added to the Array.
##
## # Examples
##
## Setting an index to a value:
##
## let array = Array.new
##
## array[0] = 10 # => 10
## array # => Array.new(10)
##
## Setting an out-of-bounds index:
##
## let array = Array.new
##
## array[2] = 10 # => 10
## array # => Array.new(Nil, Nil, 10)
##
## We can also use negative indexes:
##
## let array = Array.new(10)
##
## array[-1] = 20 # => 20
## array # => Array.new(20)
# Stores a value at the given index, then returns it.
#
# If the index is out of bounds then all preceding indexes that are not set
# will be filled with Nil values.
#
# This method will return the value that was added to the Array.
#
# # Examples
#
# Setting an index to a value:
#
# let array = Array.new
#
# array[0] = 10 # => 10
# array # => Array.new(10)
#
# Setting an out-of-bounds index:
#
# let array = Array.new
#
# array[2] = 10 # => 10
# array # => Array.new(Nil, Nil, 10)
#
# We can also use negative indexes:
#
# let array = Array.new(10)
#
# array[-1] = 20 # => 20
# array # => Array.new(20)
def []=(index: Integer, value: T) -> T {
_INKOC.array_set(self, index, value)
}
}
 
impl ToArray!(T) for Array!(T) {
## Always returns `self`.
##
## # Examples
##
## "Converting" an array to an array:
##
## Array.new(10).to_array # => Array.new(10)
# Always returns `self`.
#
# # Examples
#
# "Converting" an array to an array:
#
# Array.new(10).to_array # => Array.new(10)
def to_array -> Array!(T) {
self
}
}
 
impl Equal for Array!(T) {
## Returns `True` if `self` and the given `Array` are identical.
##
## # Examples
##
## Comparing two identical arrays:
##
## Array.new(10, 20, 30) == Array.new(10, 20, 30) # => True
##
## Comparing two arrays with a different length:
##
## Array.new(10) == Array.new(10, 20) # => False
##
## Comparing two arrays with the same length but with different values:
##
## Array.new(10, 20) == Array.new(20, 10) # => False
# Returns `True` if `self` and the given `Array` are identical.
#
# # Examples
#
# Comparing two identical arrays:
#
# Array.new(10, 20, 30) == Array.new(10, 20, 30) # => True
#
# Comparing two arrays with a different length:
#
# Array.new(10) == Array.new(10, 20) # => False
#
# Comparing two arrays with the same length but with different values:
#
# Array.new(10, 20) == Array.new(20, 10) # => False
def ==(other: Self) -> Boolean where T: Equal {
(length == other.length).if_false {
return False
Loading
Loading
#! Iterator types and methods for the `Array` type.
#!
#! These types and methods are defined separately as otherwise `std::iterator`
#! would depend on `std::array` while `std::array` would depend on
#! `std::iterator`.
# Iterator types and methods for the `Array` type.
#
# These types and methods are defined separately as otherwise `std::iterator`
# would depend on `std::array` while `std::array` would depend on
# `std::iterator`.
import std::iterator::(self, Iterator)
 
impl Array!(T) {
## Returns an `Iterator` that iterates over all values in `self`.
##
## # Examples
##
## Iterating over an `Array`:
##
## let numbers = Array.new(10, 20, 30)
## let iter = numbers.iter
##
## iter.next # => 10
## iter.next # => 20
## iter.next # => 30
## iter.next # => Nil
# Returns an `Iterator` that iterates over all values in `self`.
#
# # Examples
#
# Iterating over an `Array`:
#
# let numbers = Array.new(10, 20, 30)
# let iter = numbers.iter
#
# iter.next # => 10
# iter.next # => 20
# iter.next # => 30
# iter.next # => Nil
def iter -> Iterator!(T) {
iterator.index_enumerator(length) do (index) {
self[index]
Loading
Loading
#! Blocks of code that can be executed.
#!
#! A Block is a collection of instructions to be executed by the VM. A Block may
#! optionally take 1 or more arguments.
#!
#! # Different types of blocks
#!
#! Inko has 3 different kinds of blocks: methods, closures, and lambdas; all
#! using the same type: `Block`. These different blocks have different purposes.
#!
#! Methods are blocks bound to a name and stored in an object, and they can't
#! capture any local variables from the scope the method is defined in.
#!
#! Closures are blocks without a name that can capture local variables defined
#! in the scope the closure was created in.
#!
#! Lambdas in turn are blocks without a name that _can't_ capture any variables,
#! including `self`. This means that in these blocks `self` always refers to the
#! module the lambda was defined in, regardless of what `self` refers to in the
#! lambda's outer scope.
#!
#! # Closures
#!
#! A closure is created using the `do` keyword:
#!
#! do { 10 }
#!
#! You can also leave out the `do` keyword to create a closure that doesn't take
#! any arguments:
#!
#! { 10 }
#!
#! The `do` keyword is required when a closure accepts arguments or specifies
#! the value it may return or throw. For example, we can define a closure with a
#! required argument as follows:
#!
#! do (number) { number }
#!
#! If we leave out the type signature of the argument its type will be inferred
#! based on how the closure is used. We can also specify a type explicitly:
#!
#! do (number: Integer) { number }
#!
#! We can do the same for the return and throw types. The return type is
#! specified using the `->` symbol, while throw types are specified using `!!`.
#! For example, this closure would return an integer:
#!
#! do -> Integer { 10 }
#!
#! This closure in turn will throw an integer:
#!
#! do !! Integer { 10 }
#!
#! Just like argument types the return and throw types are inferred. In most
#! cases it should not be necessary to specify these types explicitly and so we
#! recommend you to leave them unspecified unless necessary.
#!
#! If a closure does not take any arguments it is recommended to omit the `do`
#! keyword.
#!
#! # Lambdas
#!
#! Lambdas are created using the exact same syntax except instead of using `do`
#! you have to use the `lambda` keyword.
#!
#! When passing a closure created using just curly braces (e.g. `{ 10 }`) to a
#! method that accepts a lambda the closure will instead be treated as a lambda.
#! For example:
#!
#! def example(block: lambda) {
#! block.call
#! }
#!
#! example { 10 } # => 10
#!
#! This is valid because the closure is passed directly as an argument, thus the
#! compiler is able to instead treat it as a lambda. This however does _not_
#! work if you first assign the block:
#!
#! def example(block: lambda) {
#! block.call
#! }
#!
#! let block = { 10 }
#!
#! example(block) # => type error!
#!
#! # Methods
#!
#! Methods can be defined using the `def` keyword instead of the `do` keyword,
#! but the rest of the syntax is the same. For example, to define a method
#! `example` that returns `'Hello'` we'd write the following:
#!
#! def example {
#! 'Hello'
#! }
#!
#! Unlike closures the argument, return, and throw types of methods _are not_
#! inferred. Argument and return types default to `Dynamic`, the throw type is
#! not set unless specified explicitly.
#!
#! By defaulting argument and return types to `Dynamic` you can write dynamic
#! methods without having to explicitly annotate types as `Dynamic`. This means
#! that the following code is perfectly valid:
#!
#! def number_or_string(number) {
#! (number > 0).if true: {
#! 'Hello!'
#! }, false: {
#! 42
#! }
#! }
#!
#! number_or_string(10) # => 'Hello'
#! number_or_string(-5) # => 42
#!
#! If we don't want `Dynamic` types we can instead explicitly specify the types
#! we want instead:
#!
#! def only_numbers(number: Integer) -> Integer {
#! (number > 0).if true: {
#! number
#! }, false: {
#! 42
#! }
#! }
#!
#! only_numbers(10) # => 10
#! only_numbers(-5) # => -42
#!
#! Just like closures we can specify the throw type using `!!`:
#!
#! def this_may_throw !! Integer {
#! throw 10
#! }
# Blocks of code that can be executed.
#
# A Block is a collection of instructions to be executed by the VM. A Block may
# optionally take 1 or more arguments.
#
# # Different types of blocks
#
# Inko has 3 different kinds of blocks: methods, closures, and lambdas; all
# using the same type: `Block`. These different blocks have different purposes.
#
# Methods are blocks bound to a name and stored in an object, and they can't
# capture any local variables from the scope the method is defined in.
#
# Closures are blocks without a name that can capture local variables defined
# in the scope the closure was created in.
#
# Lambdas in turn are blocks without a name that _can't_ capture any variables,
# including `self`. This means that in these blocks `self` always refers to the
# module the lambda was defined in, regardless of what `self` refers to in the
# lambda's outer scope.
#
# # Closures
#
# A closure is created using the `do` keyword:
#
# do { 10 }
#
# You can also leave out the `do` keyword to create a closure that doesn't take
# any arguments:
#
# { 10 }
#
# The `do` keyword is required when a closure accepts arguments or specifies
# the value it may return or throw. For example, we can define a closure with a
# required argument as follows:
#
# do (number) { number }
#
# If we leave out the type signature of the argument its type will be inferred
# based on how the closure is used. We can also specify a type explicitly:
#
# do (number: Integer) { number }
#
# We can do the same for the return and throw types. The return type is
# specified using the `->` symbol, while throw types are specified using `!!`.
# For example, this closure would return an integer:
#
# do -> Integer { 10 }
#
# This closure in turn will throw an integer:
#
# do !! Integer { 10 }
#
# Just like argument types the return and throw types are inferred. In most
# cases it should not be necessary to specify these types explicitly and so we
# recommend you to leave them unspecified unless necessary.
#
# If a closure does not take any arguments it is recommended to omit the `do`
# keyword.
#
# # Lambdas
#
# Lambdas are created using the exact same syntax except instead of using `do`
# you have to use the `lambda` keyword.
#
# When passing a closure created using just curly braces (e.g. `{ 10 }`) to a
# method that accepts a lambda the closure will instead be treated as a lambda.
# For example:
#
# def example(block: lambda) {
# block.call
# }
#
# example { 10 } # => 10
#
# This is valid because the closure is passed directly as an argument, thus the
# compiler is able to instead treat it as a lambda. This however does _not_
# work if you first assign the block:
#
# def example(block: lambda) {
# block.call
# }
#
# let block = { 10 }
#
# example(block) # => type error!
#
# # Methods
#
# Methods can be defined using the `def` keyword instead of the `do` keyword,
# but the rest of the syntax is the same. For example, to define a method
# `example` that returns `'Hello'` we'd write the following:
#
# def example {
# 'Hello'
# }
#
# Unlike closures the argument, return, and throw types of methods _are not_
# inferred. Argument and return types default to `Dynamic`, the throw type is
# not set unless specified explicitly.
#
# By defaulting argument and return types to `Dynamic` you can write dynamic
# methods without having to explicitly annotate types as `Dynamic`. This means
# that the following code is perfectly valid:
#
# def number_or_string(number) {
# (number > 0).if(true: { 'Hello!' }, false: { 42 })
# }
#
# number_or_string(10) # => 'Hello'
# number_or_string(-5) # => 42
#
# If we don't want `Dynamic` types we can instead explicitly specify the types
# we want instead:
#
# def only_numbers(number: Integer) -> Integer {
# (number > 0).if(true: { number }, false: { 42 })
# }
#
# only_numbers(10) # => 10
# only_numbers(-5) # => -42
#
# Just like closures we can specify the throw type using `!!`:
#
# def this_may_throw !! Integer {
# throw 10
# }
 
impl Block {
## Executes the current block, passing the given arguments to the block.
##
## The return type of this method is `Dynamic` because it can not be inferred
## when this method gets compiled. To work around this the compiler will
## optimise sending `call` to a `Block` in such a way that it is able to
## figure out what arguments the `Block` takes and what its return type is.
##
## # Examples
##
## Executing a block without arguments:
##
## let block = { 10 }
##
## block.call # => 10
##
## Executing a block with arguments:
##
## let block = do (number) { number }
##
## block.call(10) # => 10
# Executes the current block, passing the given arguments to the block.
#
# The return type of this method is `Dynamic` because it can not be inferred
# when this method gets compiled. To work around this the compiler will
# optimise sending `call` to a `Block` in such a way that it is able to
# figure out what arguments the `Block` takes and what its return type is.
#
# # Examples
#
# Executing a block without arguments:
#
# let block = { 10 }
#
# block.call # => 10
#
# Executing a block with arguments:
#
# let block = do (number) { number }
#
# block.call(10) # => 10
def call {
_INKOC.run_block(self)
}
 
## Executes the given `Block` as long as the receiving `Block` evaluates to
## `False`.
##
## # Examples
##
## Decrementing a number:
##
## let mut number = 10
##
## { number == 0 }.while_false {
## number -= 1
## }
##
## number # => 0
# Executes the given `Block` as long as the receiving `Block` evaluates to
# `False`.
#
# # Examples
#
# Decrementing a number:
#
# let mut number = 10
#
# { number == 0 }.while_false {
# number -= 1
# }
#
# number # => 0
def while_false(block: do) -> Nil {
call.if_true { return }
block.call
while_false(block)
}
 
## Executes the given `Block` as long as the receiving `Block` evaluates to
## `True`.
##
## # Examples
##
## Incrementing a number:
##
## let mut number = 0
##
## { number < 10 }.while_true {
## number += 1
## }
##
## number # => 10
# Executes the given `Block` as long as the receiving `Block` evaluates to
# `True`.
#
# # Examples
#
# Incrementing a number:
#
# let mut number = 0
#
# { number < 10 }.while_true {
# number += 1
# }
#
# number # => 10
def while_true(block: do) -> Nil {
call.if_false { return }
block.call
while_true(block)
}
 
## Repeatedly executes `self`.
##
## This method will never return on its own.
##
## # Examples
##
## Executing a block until we break out of it explicitly:
##
## let mut number = 0
##
## {
## number == 100
## .if_true {
## return
## }
##
## number += 1
## }.loop
# Repeatedly executes `self`.
#
# This method will never return on its own.
#
# # Examples
#
# Executing a block until we break out of it explicitly:
#
# let mut number = 0
#
# {
# number == 100
# .if_true {
# return
# }
#
# number += 1
# }.loop
def loop -> Void {
call
loop
Loading
Loading
#! Boolean true and false.
#!
#! In Inko boolean true is the object `True` and boolean false is the object
#! `False`. Both are regular objects that are instances of the `Boolean` object.
# Boolean true and false.
#
# In Inko boolean true is the object `True` and boolean false is the object
# `False`. Both are regular objects that are instances of the `Boolean` object.
 
import std::conditional::Conditional
import std::conversion::ToString
Loading
Loading
@@ -9,7 +9,7 @@ import std::hash::(Hasher, Hash)
import std::operators::Equal
 
impl Boolean {
## Returns the `Boolean` that is the opposite of `self`.
# Returns the `Boolean` that is the opposite of `self`.
def not -> Boolean {
False
}
Loading
Loading
@@ -38,25 +38,25 @@ impl Conditional for Boolean {
}
 
impl Equal for Boolean {
## Returns `True` if the given `Boolean` equals `self`.
##
## `True` is only equal to `True` itself, and `False` is only equal to `False`
## itself.
##
## # Examples
##
## Checking if two booleans are equal:
##
## True == True # => True
##
## Checking if two booleans are not equal:
##
## True == False # => False
# Returns `True` if the given `Boolean` equals `self`.
#
# `True` is only equal to `True` itself, and `False` is only equal to `False`
# itself.
#
# # Examples
#
# Checking if two booleans are equal:
#
# True == True # => True
#
# Checking if two booleans are not equal:
#
# True == False # => False
def ==(other: Self) -> Boolean {
_INKOC.object_equals(self, other)
}
 
## Returns `True` if the given `Boolean` does not equal `self`.
# Returns `True` if the given `Boolean` does not equal `self`.
def !=(other: Self) -> Boolean {
(self == other).not
}
Loading
Loading
#! Arrays of bytes
#!
#! Byte arrays are arrays specialised for storing individual bytes in the most
#! efficient way possible. Unlike a regular `Array` of `Integer` values, each
#! value only requires a single byte of space, instead of requiring 8 bytes of
#! space.
#!
#! Byte arrays are primarily meant for reading and writing data from/to a
#! stream, such as a file or a socket. If you simply want to store a list of
#! numbers, you're better off using the `Array` object.
# Arrays of bytes
#
# Byte arrays are arrays specialised for storing individual bytes in the most
# efficient way possible. Unlike a regular `Array` of `Integer` values, each
# value only requires a single byte of space, instead of requiring 8 bytes of
# space.
#
# Byte arrays are primarily meant for reading and writing data from/to a
# stream, such as a file or a socket. If you simply want to store a list of
# numbers, you're better off using the `Array` object.
import std::conversion::ToArray
import std::conversion::ToString
import std::format::(self, Formatter, Inspect)
Loading
Loading
@@ -16,147 +16,143 @@ import std::iterator::(self, Iterator)
import std::length::Length
import std::operators::Equal
 
## A mutable sequence of bytes, similar to an `Array`.
# A mutable sequence of bytes, similar to an `Array`.
let ByteArray = _INKOC.get_byte_array_prototype
 
_INKOC.set_object_name(ByteArray, 'ByteArray')
 
## A type that can be converted to a `ByteArray`.
# A type that can be converted to a `ByteArray`.
trait ToByteArray {
## Converts `self` to a `ByteArray`.
# Converts `self` to a `ByteArray`.
def to_byte_array -> ByteArray
}
 
impl ByteArray {
## Creates a new `ByteArray`.
##
## This method will panic if any of the `Integer` values passed to this method
## are not in the range `0..256`.
##
## # Examples
##
## Creating an empty `ByteArray`:
##
## import std::byte_array::ByteArray
##
## ByteArray.new
##
## Creating a `ByteArray` with values:
##
## import std::byte_array::ByteArray
##
## ByteArray.new(10, 20, 30)
# Creates a new `ByteArray`.
#
# This method will panic if any of the `Integer` values passed to this method
# are not in the range `0..256`.
#
# # Examples
#
# Creating an empty `ByteArray`:
#
# import std::byte_array::ByteArray
#
# ByteArray.new
#
# Creating a `ByteArray` with values:
#
# import std::byte_array::ByteArray
#
# ByteArray.new(10, 20, 30)
static def new(*bytes: Integer) -> Self {
_INKOC.byte_array_from_array(bytes)
}
 
## Removes all values from this `ByteArray`.
##
## # Examples
##
## Removing all values:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(10, 20, 30)
##
## bytes.clear
## bytes.length # => 0
# Removes all values from this `ByteArray`.
#
# # Examples
#
# Removing all values:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(10, 20, 30)
#
# bytes.clear
# bytes.length # => 0
def clear -> Self {
_INKOC.byte_array_clear(self)
self
}
 
## Pushes a value to the back of the `ByteArray`, returning the pushed value.
##
## # Examples
##
## Pushing a value into a `ByteArray`:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new
##
## bytes.push(10) # => 10
## bytes.length # => 1
# Pushes a value to the back of the `ByteArray`, returning the pushed value.
#
# # Examples
#
# Pushing a value into a `ByteArray`:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new
#
# bytes.push(10) # => 10
# bytes.length # => 1
def push(value: Integer) -> Integer {
self[length] = value
}
 
## Removes a value from the back of the `ByteArray`, returning the removed
## value.
##
## If no value was found, `Nil` is returned instead.
##
## ## Examples
##
## Popping an existing value:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(10)
##
## bytes.pop # => 10
## bytes.length # => 0
##
## Popping a value when the `ByteArray` is empty:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new
##
## bytes.pop # => Nil
# Removes a value from the back of the `ByteArray`, returning the removed
# value.
#
# If no value was found, `Nil` is returned instead.
#
# # Examples
#
# Popping an existing value:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(10)
#
# bytes.pop # => 10
# bytes.length # => 0
#
# Popping a value when the `ByteArray` is empty:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new
#
# bytes.pop # => Nil
def pop -> ?Integer {
(length > 0).if true: {
remove_at(length - 1)
}, false: {
Nil
}
(length > 0).if(true: { remove_at(length - 1) }, false: { Nil })
}
 
## Removes the value at the given index, returning the removed value.
##
## This method will return `Nil` if no value was removed.
##
## # Examples
##
## Removing an existing value:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(10)
##
## bytes.remove_at(0) # => 10
## bytes.length # => 0
##
## Removing a non-existing value:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new
##
## bytes.remove_at(0) # => Nil
# Removes the value at the given index, returning the removed value.
#
# This method will return `Nil` if no value was removed.
#
# # Examples
#
# Removing an existing value:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(10)
#
# bytes.remove_at(0) # => 10
# bytes.length # => 0
#
# Removing a non-existing value:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new
#
# bytes.remove_at(0) # => Nil
def remove_at(index: Integer) -> ?Integer {
_INKOC.byte_array_remove(self, index)
}
 
## Yields all the bytes in this `ByteArray` to the supplied `Block`.
##
## While `std::iterator::Iterator` provides an `each` method using this would
## require the allocation of an `Iterator`. Since iterating over an
## `ByteArray` is so common we provide a specialised implementation for those
## cases.
##
## # Examples
##
## Iterating over all the values in a `ByteArray`:
##
## import std::stdio::stdout
## import std::byte_array::ByteArray
##
## ByteArray.new(10, 20, 30).each do (byte) {
## stdout.print(byte)
## }
# Yields all the bytes in this `ByteArray` to the supplied `Block`.
#
# While `std::iterator::Iterator` provides an `each` method using this would
# require the allocation of an `Iterator`. Since iterating over an
# `ByteArray` is so common we provide a specialised implementation for those
# cases.
#
# # Examples
#
# Iterating over all the values in a `ByteArray`:
#
# import std::stdio::stdout
# import std::byte_array::ByteArray
#
# ByteArray.new(10, 20, 30).each do (byte) {
# stdout.print(byte)
# }
def each(block: do (Integer)) {
let mut index = 0
let max = length
Loading
Loading
@@ -170,19 +166,19 @@ impl ByteArray {
}
}
 
## Yields all the values and their indexes in this `ByteArray` to the supplied
## `Block`.
##
## # Examples
##
## Iterating over the values of an `ByteArray` and their indexes:
##
## import std::stdio::stdout
## import std::byte_array::ByteArray
##
## ByteArray.new(10, 20, 30).each_with_index do (byte, index) {
## stdout.print(index) # => 0, 1, 2
## }
# Yields all the values and their indexes in this `ByteArray` to the supplied
# `Block`.
#
# # Examples
#
# Iterating over the values of an `ByteArray` and their indexes:
#
# import std::stdio::stdout
# import std::byte_array::ByteArray
#
# ByteArray.new(10, 20, 30).each_with_index do (byte, index) {
# stdout.print(index) # => 0, 1, 2
# }
def each_with_index(block: do (Integer, Integer)) {
let mut index = 0
 
Loading
Loading
@@ -192,51 +188,48 @@ impl ByteArray {
}
}
 
## Returns a new `String` using the bytes in this `ByteArray`, draining it in
## the process.
##
## After this method is finished, `self` is left empty. This allows one to
## convert a temporary `ByteArray` into a `String`, without requiring the list
## of bytes to be allocated twice.
##
## # Examples
##
## Draining a `ByteArray` into a `String`:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(105, 110, 107, 111)
##
## bytes.drain_to_string # => 'inko'
## bytes.empty? # => True
# Returns a new `String` using the bytes in this `ByteArray`, draining it in
# the process.
#
# After this method is finished, `self` is left empty. This allows one to
# convert a temporary `ByteArray` into a `String`, without requiring the list
# of bytes to be allocated twice.
#
# # Examples
#
# Draining a `ByteArray` into a `String`:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(105, 110, 107, 111)
#
# bytes.drain_to_string # => 'inko'
# bytes.empty? # => True
def drain_to_string -> String {
_INKOC.byte_array_to_string(self, True)
}
 
## Slices `self` into a new `ByteArray`.
##
## Similar to slicing a `String`, slicing a `ByteArray` allows one to extract
## a sub-array by providing a start position and the number of _bytes_ to
## include starting at the start position.
##
## # Examples
##
## Slicing a `ByteArray`:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(1, 2, 3, 4)
## let sliced = bytes.slice(start: 1, length: 2)
##
## sliced[0] # => 2
## sliced[1] # => 3
# Slices `self` into a new `ByteArray`.
#
# Similar to slicing a `String`, slicing a `ByteArray` allows one to extract
# a sub-array by providing a start position and the number of _bytes_ to
# include starting at the start position.
#
# # Examples
#
# Slicing a `ByteArray`:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(1, 2, 3, 4)
# let sliced = bytes.slice(start: 1, length: 2)
#
# sliced[0] # => 2
# sliced[1] # => 3
def slice(start: Integer, length: Integer) -> ByteArray {
let new_array = ByteArray.new
let mut index = (start >= 0).if true: {
start
}, false: {
start % self.length
}
let mut index =
(start >= 0).if(true: { start }, false: { start % self.length })
 
let mut until = index + length
 
Loading
Loading
@@ -255,92 +248,92 @@ impl ByteArray {
}
 
impl Index!(Integer, Integer) for ByteArray {
## Returns the byte at the given index, or Nil if no value was found.
##
## # Examples
##
## Retrieving an existing byte:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(10, 20)
##
## bytes[0] # => 10
##
## Using an out of bounds index:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(10, 20)
##
## bytes[5] # => Nil
# Returns the byte at the given index, or Nil if no value was found.
#
# # Examples
#
# Retrieving an existing byte:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(10, 20)
#
# bytes[0] # => 10
#
# Using an out of bounds index:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(10, 20)
#
# bytes[5] # => Nil
def [](index: Integer) -> ?Integer {
(index >= length).if true: {
Nil
}, false: {
_INKOC.byte_array_at(self, index)
(index >= length).if_true {
return Nil
}
_INKOC.byte_array_at(self, index)
}
}
 
impl SetIndex!(Integer, Integer) for ByteArray {
## Stores a byte at the given index, then returns it.
##
## Because there are no sensible values that can be used for padding a
## `ByteArray` (unlike a regular `Array`, where we can use a `Nil`), this
## method will panic if the supplied index is out of bounds.
##
## # Examples
##
## Setting the value of an existing index:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(10, 20)
##
## bytes[0] = 30 # => 30
## bytes[0] # => 30
# Stores a byte at the given index, then returns it.
#
# Because there are no sensible values that can be used for padding a
# `ByteArray` (unlike a regular `Array`, where we can use a `Nil`), this
# method will panic if the supplied index is out of bounds.
#
# # Examples
#
# Setting the value of an existing index:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(10, 20)
#
# bytes[0] = 30 # => 30
# bytes[0] # => 30
def []=(index: Integer, value: Integer) -> Integer {
_INKOC.byte_array_set(self, index, value)
}
}
 
impl ToString for ByteArray {
## Returns a new `String` using the bytes in this `ByteArray`.
##
## Any invalid UTF-8 sequences will be replaced with `U+FFFD REPLACEMENT
## CHARACTER`, which looks like this: �
##
## # Examples
##
## Converting a `ByteArray` into a `String`:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(105, 110, 107, 111)
##
## bytes.to_string # => 'inko'
# Returns a new `String` using the bytes in this `ByteArray`.
#
# Any invalid UTF-8 sequences will be replaced with `U+FFFD REPLACEMENT
# CHARACTER`, which looks like this: �
#
# # Examples
#
# Converting a `ByteArray` into a `String`:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(105, 110, 107, 111)
#
# bytes.to_string # => 'inko'
def to_string -> String {
_INKOC.byte_array_to_string(self, False)
}
}
 
impl ToArray!(Integer) for ByteArray {
## Converts the `ByteArray` to an `Array!(Integer)`.
##
## It is recommended to not use this method for very large byte arrays, as
## an `Integer` requires 8 times more memory compared to a single byte. This
## means that a 1 MB `ByteArray` would require roughly 8 MB of memory.
##
## # Examples
##
## Converting a `ByteArray`:
##
## import std::byte_array::ByteArray
##
## let bytes = ByteArray.new(105, 110, 107, 111)
##
## bytes.to_array # => Array.new(105, 110, 107, 111)
# Converts the `ByteArray` to an `Array!(Integer)`.
#
# It is recommended to not use this method for very large byte arrays, as
# an `Integer` requires 8 times more memory compared to a single byte. This
# means that a 1 MB `ByteArray` would require roughly 8 MB of memory.
#
# # Examples
#
# Converting a `ByteArray`:
#
# import std::byte_array::ByteArray
#
# let bytes = ByteArray.new(105, 110, 107, 111)
#
# bytes.to_array # => Array.new(105, 110, 107, 111)
def to_array -> Array!(Integer) {
let integers = Array.new
 
Loading
Loading
@@ -359,42 +352,42 @@ impl ToArray!(Integer) for ByteArray {
}
 
impl Equal for ByteArray {
## Returns `True` if two `ByteArray` objects are equal to each other.
##
## Two `ByteArray` objects are considered equal if they have the exact same
## values in the exact same order.
##
## # Examples
##
## Comparing two `ByteArray` objects:
##
## import std::byte_array::ByteArray
##
## ByteArray.new(10) == ByteArray.new(10) # => True
## ByteArray.new(10) == ByteArray.new(20) # => False
# Returns `True` if two `ByteArray` objects are equal to each other.
#
# Two `ByteArray` objects are considered equal if they have the exact same
# values in the exact same order.
#
# # Examples
#
# Comparing two `ByteArray` objects:
#
# import std::byte_array::ByteArray
#
# ByteArray.new(10) == ByteArray.new(10) # => True
# ByteArray.new(10) == ByteArray.new(20) # => False
def ==(other: ByteArray) -> Boolean {
_INKOC.byte_array_equals(self, other)
}
}
 
impl Length for ByteArray {
## Returns the number of bytes in this `ByteArray`.
##
## # Examples
##
## Obtaining the length of a `ByteArray`
##
## import std::byte_array::ByteArray
##
## ByteArray.new.length # => 0
## ByteArray.new(10).length # => 1
# Returns the number of bytes in this `ByteArray`.
#
# # Examples
#
# Obtaining the length of a `ByteArray`
#
# import std::byte_array::ByteArray
#
# ByteArray.new.length # => 0
# ByteArray.new(10).length # => 1
def length -> Integer {
_INKOC.byte_array_length(self)
}
}
 
impl Inspect for ByteArray {
## Formats this `ByteArray` into a human-readable representation.
# Formats this `ByteArray` into a human-readable representation.
def format_for_inspect(formatter: Formatter) {
formatter.push('ByteArray { ')
formatter.push(length.to_string)
Loading
Loading
@@ -403,21 +396,21 @@ impl Inspect for ByteArray {
}
 
impl ToByteArray for ByteArray {
## Converts `self` to a `ByteArray`.
# Converts `self` to a `ByteArray`.
def to_byte_array -> ByteArray {
self
}
}
 
impl ToByteArray for String {
## Returns a `ByteArray` containing the bytes of this `String`.
# Returns a `ByteArray` containing the bytes of this `String`.
def to_byte_array -> ByteArray {
_INKOC.string_to_byte_array(self) as ByteArray
}
}
 
impl ByteArray {
## Returns an `Iterator` that iterates over all values in `self`.
# Returns an `Iterator` that iterates over all values in `self`.
def iter -> Iterator!(Integer) {
iterator.index_enumerator(length) do (index) {
self[index]
Loading
Loading
# AST types for closures and lambdas.
import std::compiler::ast::expressions::Expressions
import std::compiler::ast::node::Node
import std::compiler::ast::type_parameter::TypeParameter
import std::compiler::source_location::SourceLocation
# An argument as defined in a closure, lambda, or method..
object Argument {
# The name of the argument.
@name: String
# The explicit type of the argument, if specified.
@value_type: ?Node
# The default value of the argument, if any.
@default_value: ?Node
# A boolean indicating if the argument is mutable (True) or immutable (False).
@mutable: Boolean
# A boolean indicating if the argument is a rest argument (True) or a regular
# argument (False).
@rest: Boolean
# The source location of the argument.
@location: SourceLocation
def init(
name: String,
value_type: ?Node,
default_value: ?Node,
mutable: Boolean,
rest: Boolean,
location: SourceLocation
) {
@name = name
@value_type = value_type
@default_value = default_value
@mutable = Boolean
@rest = rest
@location = location
}
# Returns the name of the argument.
def name -> String {
@name
}
# Returns the default value of the argument.
def default_value -> ?Node {
@default_value
}
# Returns the explicit type of the argument.
def value_type -> ?Node {
@value_type
}
## Returns `True` if the argument is mutable.
def mutable? -> Boolean {
@mutable
}
## Returns `True` if the argument is a rest argument.
def rest? -> Boolean {
@rest
}
}
impl Node for Argument {
def location -> SourceLocation {
@location
}
}
# A closure that was created without the `do` or `lambda` keyword.
object BasicClosure {
# The expressions in the block's body.
@body: Expressions
# The source location of the block.
@location: SourceLocation
def init(body: Expressions, location: SourceLocation) {
@body = body
@location = location
}
# Returns the expressions in the block's body.
def body -> Expressions {
@body
}
}
impl Node for BasicClosure {
def location -> SourceLocation {
@location
}
}
# A closure created with the `do` keyword.
object Closure {
# The type parameters of the closure.
@type_parameters: Array!(TypeParameter)
# The arguments of the closure.
@arguments: Array!(Argument)
# The expressions in the closure's body.
@body: Expressions
# The type of the value this closure might throw.
@throw_type: ?Node
# The return type of this closure.
@return_type: ?Node
# The source location of the closure.
@location: SourceLocation
def init(
type_parameters: Array!(TypeParameter),
arguments: Array!(Argument),
body: Expressions,
throw_type: ?Node,
return_type: ?Node,
location: SourceLocation
) {
@type_parameters = type_parameters
@arguments = arguments
@body = body
@throw_type = throw_type
@return_type = return_type
@location = location
}
# Returns the type parameters of the closure.
def type_parameters -> Array!(TypeParameter) {
@type_parameters
}
# Returns the arguments of the closure.
def arguments -> Array!(Argument) {
@arguments
}
# Returns the expressions in the block's body.
def body -> Expressions {
@body
}
# Returns the return type of this lambda.
def return_type -> ?Node {
@return_type
}
# Returns the throw type of this lambda.
def throw_type -> ?Node {
@throw_type
}
}
impl Node for Closure {
def location -> SourceLocation {
@location
}
}
# A lambda created with the `do` keyword.
object Lambda {
# The type parameters of the lambda.
@type_parameters: Array!(TypeParameter)
# The arguments of the lambda.
@arguments: Array!(Argument)
# The expressions in the lambda's body.
@body: Expressions
# The type of the value this lambda might throw.
@throw_type: ?Node
# The return type of this lambda.
@return_type: ?Node
# The source location of the lambda.
@location: SourceLocation
def init(
type_parameters: Array!(TypeParameter),
arguments: Array!(Argument),
body: Expressions,
throw_type: ?Node,
return_type: ?Node,
location: SourceLocation
) {
@type_parameters = type_parameters
@arguments = arguments
@body = body
@throw_type = throw_type
@return_type = return_type
@location = location
}
# Returns the type parameters of the lambda.
def type_parameters -> Array!(TypeParameter) {
@type_parameters
}
# Returns the arguments of the lambda.
def arguments -> Array!(Argument) {
@arguments
}
# Returns the expressions in the block's body.
def body -> Expressions {
@body
}
# Returns the return type of this lambda.
def return_type -> ?Node {
@return_type
}
# Returns the throw type of this lambda.
def throw_type -> ?Node {
@throw_type
}
}
impl Node for Lambda {
def location -> SourceLocation {
@location
}
}
# A method created using the `def` keyword.
object MethodDefinition {
# The name of the method.
@name: String
# The type parameters of the method.
@type_parameters: Array!(TypeParameter)
# The arguments of the method.
@arguments: Array!(Argument)
# The expressions in the method's body.
@body: Expressions
# The type of the value this method might throw.
@throw_type: ?Node
# The return type of this method.
@return_type: ?Node
# The source location of the method.
@location: SourceLocation
# A boolean indicating if the method is a static method (True) or not
# (False).
@static_method: Boolean
def init(
name: String,
type_parameters: Array!(TypeParameter),
arguments: Array!(Argument),
throw_type: ?Node,
return_type: ?Node,
static_method: Boolean,
body: Expressions,
location: SourceLocation
) {
@name = name
@type_parameters = type_parameters
@arguments = arguments
@body = body
@throw_type = throw_type
@return_type = return_type
@static_method = static_method
@location = location
}
# Returns the name of the method.
def name -> String {
@name
}
# Returns the type parameters of the method.
def type_parameters -> Array!(TypeParameter) {
@type_parameters
}
# Returns the arguments of the method.
def arguments -> Array!(Argument) {
@arguments
}
# Returns the expressions in the block's body.
def body -> Expressions {
@body
}
# Returns the return type of this lambda.
def return_type -> ?Node {
@return_type
}
# Returns the throw type of this lambda.
def throw_type -> ?Node {
@throw_type
}
# Returns `True` if this method is a static method.
def static_method? -> Boolean {
@static_method
}
}
impl Node for MethodDefinition {
def location -> SourceLocation {
@location
}
}
# A required method created using the `def` keyword.
object RequiredMethodDefinition {
# The name of the method.
@name: String
# The type parameters of the method.
@type_parameters: Array!(TypeParameter)
# The arguments of the method.
@arguments: Array!(Argument)
# The type of the value this method might throw.
@throw_type: ?Node
# The return type of this method.
@return_type: ?Node
# The source location of the method.
@location: SourceLocation
# A boolean indicating if the method is a static method (True) or not
# (False).
@static_method: Boolean
def init(
name: String,
type_parameters: Array!(TypeParameter),
arguments: Array!(Argument),
throw_type: ?Node,
return_type: ?Node,
static_method: Boolean,
location: SourceLocation
) {
@name = name
@type_parameters = type_parameters
@arguments = arguments
@throw_type = throw_type
@return_type = return_type
@static_method = static_method
@location = location
}
# Returns the name of the method.
def name -> String {
@name
}
# Returns the type parameters of the method.
def type_parameters -> Array!(TypeParameter) {
@type_parameters
}
# Returns the arguments of the method.
def arguments -> Array!(Argument) {
@arguments
}
# Returns the return type of this lambda.
def return_type -> ?Node {
@return_type
}
# Returns the throw type of this lambda.
def throw_type -> ?Node {
@throw_type
}
# Returns `True` if this method is a static method.
def static_method? -> Boolean {
@static_method
}
}
impl Node for RequiredMethodDefinition {
def location -> SourceLocation {
@location
}
}
# AST types for comments.
import std::compiler::ast::node::Node
import std::compiler::source_location::SourceLocation
# A source code comment.
object Comment {
# The text of the comment, excluding the comment character (`#`).
@text: String
# The source location of the comment.
@location: SourceLocation
def init(text: String, location: SourceLocation) {
@text = text
@location = location
}
# Returns the text of the comment.
def text -> String {
@text
}
}
impl Node for Comment {
def location -> SourceLocation {
@location
}
}
# AST types for control flow constructs.
import std::compiler::ast::expressions::Expressions
import std::compiler::ast::node::Node
import std::compiler::source_location::SourceLocation
# A return expression, with an optional value to return.
object Return {
# An expression to return, if any.
@expression: ?Node
# The source location of the return expression.
@location: SourceLocation
def init(expression: ?Node, location: SourceLocation) {
@expression = expression
@location = location
}
# Returns the expression to return.
def expression -> ?Node {
@expression
}
}
impl Node for Return {
def location -> SourceLocation {
@location
}
}
object Throw {
# The expression to throw.
@expression: Node
# The source location of the throw expression.
@location: SourceLocation
def init(expression: Node, location: SourceLocation) {
@expression = expression
@location = location
}
# Returns the expression to throw.
def expression -> Node {
@expression
}
}
impl Node for Throw {
def location -> SourceLocation {
@location
}
}
# An expression that might throw.
object Try {
# The expression to try to run.
@expression: Node
# The name of the local variable to store the error in.
@error_variable: ?String
# The expression to run when an error is thrown.
@else_expression: Expressions
# The source location of the throw expression.
@location: SourceLocation
def init(
expression: Node,
error_variable: ?String,
else_expression: Expressions,
location: SourceLocation
) {
@expression = expression
@error_variable = error_variable
@else_expression = else_expression
@location = location
}
# Returns the expression to try to run.
def expression -> Node {
@expression
}
# Returns the name of the error variable.
def error_variable -> ?String {
@error_variable
}
# Returns the expression to run when an error is thrown.
def else_expression -> Expressions {
@else_expression
}
}
impl Node for Try {
def location -> SourceLocation {
@location
}
}
# An expression that should panic if an error is thrown.
object TryPanic {
# The expression to try to run.
@expression: Node
# The source location of the throw expression.
@location: SourceLocation
def init(expression: Node, location: SourceLocation) {
@expression = expression
@location = location
}
# Returns the expression to try to run.
def expression -> Node {
@expression
}
}
impl Node for TryPanic {
def location -> SourceLocation {
@location
}
}
# AST types for collections of expressions.
import std::compiler::ast::node::Node
import std::compiler::source_location::SourceLocation
# A collection of different expressions.
object Expressions {
# The nodes stored in this collection of expressions.
@children: Array!(Node)
# The source location of the start of the collection of expressions.
@location: SourceLocation
def init(children: Array!(Node), location: SourceLocation) {
@children = children
@location = location
}
# Returns the nodes stored in this collection.
def children -> Array!(Node) {
@children
}
}
impl Node for Expressions {
def location -> SourceLocation {
@location
}
}
# AST types for import statements.
import std::compiler::ast::node::Node
import std::compiler::ast::variables::Identifier
import std::compiler::source_location::SourceLocation
# The alias to import a symbol under.
object ImportAlias {
# The name of the alias.
@name: String
# The source location of the import alias.
@location: SourceLocation
def init(name: String, location: SourceLocation) {
@name = name
@location = location
}
# Returns the name of the alias.
def name -> String {
@name
}
def location -> SourceLocation {
@location
}
}
# A symbol to import, with an optional alias.
object ImportSymbol {
# The name of the symbol to import.
@name: String
# The alias to use for the symbol, if any.
@alias: ?ImportAlias
# The source location of the symbol that is imported.
@location: SourceLocation
def init(name: String, location: SourceLocation, alias: ?ImportAlias = Nil) {
@name = name
@location = location
@alias = alias
}
# Returns the alias of the imported symbol.
def alias -> ?ImportAlias {
@alias
}
# Returns the name of the symbol being imported.
def name -> String {
@name
}
# Returns `True` if the module itself is being imported.
def import_self? -> Boolean {
@name == 'self'
}
def location -> SourceLocation {
@location
}
}
# A single import statement, containing a list of symbols to import.
object Import {
# The path to the module to import.
@path: Array!(Identifier)
# The symbols to import.
@symbols: Array!(ImportSymbol)
# The source location of the import.
@location: SourceLocation
# A boolean indicating if all symbols should be imported (True), instead of a
# specific list.
@import_all: Boolean
def init(
path: Array!(Identifier),
symbols: Array!(ImportSymbol),
import_all: Boolean,
location: SourceLocation
) {
@path = path
@symbols = symbols
@import_all = import_all
@location = location
}
# Returns the import path, consisting out of one or more identifiers.
def path -> Array!(Identifier) {
@path
}
# Returns the symbols to import, if any.
def symbols -> Array!(ImportSymbol) {
@symbols
}
## Returns `True` if all symbols should be imported from the module.
def import_all? -> Boolean {
@import_all
}
}
impl Node for Import {
def location -> SourceLocation {
@location
}
}
# AST types for literal values, such as integers and strings.
import std::compiler::ast::node::Node
import std::compiler::source_location::SourceLocation
# A simple literal value, such as an integer or a float.
trait Literal: Node {
# Returns the value of the literal.
def value -> String
}
# AST node for integer literals.
object IntegerLiteral {
# The value of the literal.
@value: String
# The source location of the literal.
@location: SourceLocation
def init(value: String, location: SourceLocation) {
@value = value
@location = location
}
}
impl Node for IntegerLiteral {
def location -> SourceLocation {
@location
}
}
impl Literal for IntegerLiteral {
def value -> String {
@value
}
}
# AST node for float literals.
object FloatLiteral {
# The value of the literal.
@value: String
# The source location of the literal.
@location: SourceLocation
def init(value: String, location: SourceLocation) {
@value = value
@location = location
}
}
impl Node for FloatLiteral {
def location -> SourceLocation {
@location
}
}
impl Literal for FloatLiteral {
def value -> String {
@value
}
}
# AST node for string literals.
object StringLiteral {
# The value of the literal.
@value: String
# The source location of the literal.
@location: SourceLocation
def init(value: String, location: SourceLocation) {
@value = value
@location = location
}
}
impl Node for StringLiteral {
def location -> SourceLocation {
@location
}
}
impl Literal for StringLiteral {
def value -> String {
@value
}
}
# Types for Abstract Syntax Trees produced by the parser.
import std::compiler::source_location::SourceLocation
# A trait for providing and adhering to common behaviour across AST nodes.
trait Node {
# Returns the location of the AST node.
def location -> SourceLocation
}
# AST types for objects and traits.
import std::compiler::ast::expressions::Expressions
import std::compiler::ast::node::Node
import std::compiler::ast::type_parameter::TypeParameter
import std::compiler::ast::variables::Constant
import std::compiler::source_location::SourceLocation
# A named object defined using the "object" keyword.
object ObjectDefinition {
# The name of the defined object.
@name: Constant
# The type parameters of the defined object.
@type_parameters: Array!(TypeParameter)
# The expressions inside the object body.
@body: Expressions
# The source location of the definition.
@location: SourceLocation
def init(
name: Constant,
type_parameters: Array!(TypeParameter),
body: Expressions,
location: SourceLocation
) {
@name = name
@type_parameters = type_parameters
@body = body
@location = location
}
# Returns the name of this object.
def name -> Constant {
@name
}
# Returns the type parameters of this object.
def type_parameters -> Array!(TypeParameter) {
@type_parameters
}
# Returns the expressions contained in this object definition.
def body -> Expressions {
@body
}
}
impl Node for ObjectDefinition {
def location -> SourceLocation {
@location
}
}
# The definition of an attribute in an object.
object AttributeDefinition {
# The name of the attribute.
@name: String
# The type of the attribute.
@type: Node
# The source location of the definition.
@location: SourceLocation
def init(name: String, type: Node, location: SourceLocation) {
@name = name
@type = type
@location = location
}
# Returns the name of the attribute.
def name -> String {
@name
}
# Returns the type of the attribute
def type -> Node {
@type
}
}
impl Node for AttributeDefinition {
def location -> SourceLocation {
@location
}
}
# A trait defined using the "trait" keyword.
object TraitDefinition {
# The name of the trait that is defined.
@name: Constant
# The type parameters of the trait.
@type_parameters: Array!(TypeParameter)
# The list of traits that must be implemented before implementing this trait.
@required_traits: Array!(Constant)
# The expressions inside the trait's body.
@body: Expressions
# The source location of the trait definition.
@location: SourceLocation
def init(
name: Constant,
type_parameters: Array!(TypeParameter),
required_traits: Array!(Constant),
body: Expressions,
location: SourceLocation
) {
@name = name
@type_parameters = type_parameters
@required_traits = required_traits
@body = body
@location = location
}
# Returns the name of this object.
def name -> Constant {
@name
}
# Returns the type parameters of this object.
def type_parameters -> Array!(TypeParameter) {
@type_parameters
}
# Returns the traits required by this trait.
def required_traits -> Array!(Constant) {
@required_traits
}
# Returns the expressions contained in this object definition.
def body -> Expressions {
@body
}
}
impl Node for TraitDefinition {
def location -> SourceLocation {
@location
}
}
# The implementation of a trait for an object.
object ImplementTrait {
# The name of the trait that is implemented.
@trait_name: Constant
# The name of the object to implement the trait for.
@object_name: Constant
# The trait bounds that must be met for the trait to be available.
@trait_bounds: Array!(TypeParameter)
# The expressions contained in the body of the implementation.
@body: Expressions
# The source location of the implementation.
@location: SourceLocation
def init(
trait_name: Constant,
object_name: Constant,
trait_bounds: Array!(TypeParameter),
body: Expressions,
location: SourceLocation
) {
@trait_name = trait_name
@object_name = object_name
@trait_bounds = trait_bounds
@body = body
@location = location
}
# Returns the name of the trait being implemented.
def trait_name -> Constant {
@trait_name
}
# Returns the name of the object the trait is implemented for.
def object_name -> Constant {
@object_name
}
# Returns the trait bounds of the implementation.
def trait_bounds -> Array!(TypeParameter) {
@trait_bounds
}
# Returns the expressions contained in this implementation.
def body -> Expressions {
@body
}
}
impl Node for ImplementTrait {
def location -> SourceLocation {
@location
}
}
# An object that is reopened.
object ReopenObject {
# The name of the object that is reopened.
@name: Constant
# The expressions contained in the body of the object.
@body: Expressions
# The source location of the implementation.
@location: SourceLocation
def init(
name: Constant,
body: Expressions,
location: SourceLocation
) {
@name = name
@body = body
@location = location
}
# Returns the name of the object that is reopened.
def name -> Constant {
@name
}
# Returns the body of the `impl` expression.
def body -> Expressions {
@body
}
}
impl Node for ReopenObject {
def location -> SourceLocation {
@location
}
}
# AST types for operators.
import std::compiler::ast::node::Node
import std::compiler::ast::variables::Constant
import std::compiler::source_location::SourceLocation
# An expression to cast from `?T` to `T`.
object NotNil {
# The expression to convert to a non-Nil type.
@expression: Node
# The source location of the cast.
@location: SourceLocation
def init(expression: Node, location: SourceLocation) {
@expression = expression
@location = location
}
# Returns the expression to convert.
def expression -> Node {
@expression
}
}
impl Node for NotNil {
def location -> SourceLocation {
@location
}
}
# An expression that should be casted to a different type.
object TypeCast {
# The expression of which the type should be casted to something else.
@expression: Node
# The type to cast the expression to.
@cast_to: Node
# The source location of the type cast.
@location: SourceLocation
def init(expression: Node, cast_to: Node, location: SourceLocation) {
@expression = expression
@cast_to = cast_to
@location = location
}
# Returns the expression to type cast.
def expression -> Node {
@expression
}
# Returns the type to cast to.
def cast_to -> Node {
@cast_to
}
}
impl Node for TypeCast {
def location -> SourceLocation {
@location
}
}
# AST types message sends.
import std::compiler::ast::node::Node
import std::compiler::ast::variables::*
import std::compiler::source_location::SourceLocation
# A keyword argument and its value.
object KeywordArgument {
# The name of the keyword argument.
@name: Identifier
# The value of the argument.
@value: Node
# The source location of the send operation.
@location: SourceLocation
def init(name: Identifier, value: Node, location: SourceLocation) {
@name = name
@value = value
@location = location
}
# Returns the name of the keyword argument.
def name -> Identifier {
@name
}
# Returns the value of the keyword argument.
def value -> Node {
@value
}
}
impl Node for KeywordArgument {
def location -> SourceLocation {
@location
}
}
# A message sent to a receiver.
object Send {
# The name of the message that is sent.
@message: String
# The receiver the message is sent to.
@receiver: Node
# The arguments passed with the message.
@arguments: Array!(Node)
# Any type arguments used to initialise type parameters.
@type_arguments: Array!(Node)
# The source location of the send operation.
@location: SourceLocation
def init(
message: String,
receiver: Node,
arguments: Array!(Node),
type_arguments: Array!(Node),
location: SourceLocation
) {
@message = message
@receiver = receiver
@arguments = arguments
@type_arguments = type_arguments
@location = location
}
# Returns the message that is sent.
def message -> String {
@message
}
# Returns the receiver the message is sent to.
def receiver -> Node {
@receiver
}
# Returns the arguments for the message.
def arguments -> Array!(Node) {
@arguments
}
# Returns the type arguments used to initialise any type parameters.
def type_arguments -> Array!(Node) {
@type_arguments
}
}
impl Node for Send {
def location -> SourceLocation {
@location
}
}
# AST types for type parameters.
import std::compiler::ast::node::Node
import std::compiler::ast::variables::Constant
import std::compiler::source_location::SourceLocation
# A type parameter and its required traits.
object TypeParameter {
# The name of the type parameter.
@name: String
# The traits required by this type parameter.
@required_traits: Array!(Constant)
# The source location of the type parameter.
@location: SourceLocation
def init(
name: String,
required_traits: Array!(Constant),
location: SourceLocation
) {
@name = name
@required_traits = required_traits
@location = location
}
# Returns the name of the type parameter.
def name -> String {
@name
}
# Returns the traits required by the type parameter.
def required_traits -> Array!(Constant) {
@required_traits
}
}
impl Node for TypeParameter {
def location -> SourceLocation {
@location
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment