Skip to content

Latest commit

 

History

History
334 lines (250 loc) · 12 KB

README.md

File metadata and controls

334 lines (250 loc) · 12 KB

onyo

onyo is a interpreted, dynamically typed, automatic memory managed programming language. onyo was designed to have a simple implementation while maintaining the user friendliness of dynamic programming languages such as Python, Javascript and Ruby.

Contents

C onyo Interpreter

The original onyo interpreter was written in C. I chose C because it let me implement all the constructs of the language such as Dynamic arrays, Hash tables and Reference-counted pointers on my own. By implementing the interpreter in C, I learn't how these constructs work behind the scenes of a dynamic programming language.

Parsing

The first step in interpreting code is to parse it into a Abstract Syntax Tree. Using the recursive descent parsing method, it is trivial to parse a lisp-like syntax.

(+ A (* B C))
  +
 / \
a   *
   / \
  b   c

Though this syntax is easy to implement, its harder to write and format. Thus the Rust version of onyo does not use this syntax.

Tree-walking interpreter

The interpreter works by walking the Abstract Syntax Tree and performing the operators and instructions.

Memory Management

Programming languages such as Java, Python and Javascript automatically manage memory for the programmer using various methods. onyo uses a method called Reference Counting.

In Reference Counting, each object contains a counter saying how many references to it there are. When you refer to an object, you increment the counter. When you're done with it, you decrement the counter. When the counter reaches 0, the object can be recycled.

Though this is a simple solution, it does not prevent the programmer from creating cyclic references, which creates a memory leak. It also does not do any optimization for allocation and deallocation.

Data types implementation

Dynamic arrays

Dynamic arrays in C onyo were implemented in a similar way to how the Vec data structure works in Rust. The capacity of a dynamic array will grow with a factor of 2 times the old capacity. onyo provides no way to shrink the capacity of a dynamic array.

Hash maps

The table data type in C onyo was implemented using a placeholder implementation which uses linear search. The plan was to later replace the linear search with a proper hash table implementation which was eventually ditched after switching to Rust.

Rust onyo Interpreter (onyo-rs)

Though C taught me how to implement the constructs by my own, it was getting quite difficult to debug. Thus, I decided to rewrite the interpreter in the Rust programming language. This lead to major design changes in the language. The rust version of onyo uses a JSON format instead of S-expressions. A compiler written in Python is used to compile onyo code into a JSON file.

Differences from C onyo

C onyo's compiler was optional, but the compiler is required in the Rust version. onyo-rs is much more stable than C onyo and it is faster to develop features. Rust's features such as Rc for reference-counting, Vec for dynamic arrays and HashMap for hash maps were helpful.

Language Reference

Contents

Data Types

Name Description
nil The null type has the only value null.
iterend Marks the end of a iterator.
err Err is used to return errors, it can contain any value.
bool The bool type has two values, true or false.
int Signed integers, equal to i64 in Rust.
float Double precession floating point, equal to f64 in Rust.
str Immutable string.
list Mutable dynamic array of values.

Lists

a = [1, 2, 3]
push(a, 4)
;a[2] = "Hello"
print(a[2])
value = remove(a, len(a) - 1)
print(index(a, 2))
i = 0
while i < len(a) {
   print(a[i])
   i = i + 1
}

Language Features

Comments

; Single-line comments.
; There are no multi-line comments.

Functions

function_name(arguments, etc) {
   ; ...
}

Calling functions

result = function()
function()

Returning values

return value

Bare returns are not possible because the syntax avoids semi-colons.

An explicit

return nil

must be used.

Functions implicitly return nil.

Main function

Must be present in every program. Takes no arguments.

main() {
   ; ...
}

Structs

Structs are called classes in other programming languages. They can contain fields which hold values and methods with an implicit self parameter.

Struct fields are static, which means that you cannot add a new field to a struct instance at run-time like you could do in Javascript.

Person {
   name, age

   greet(self) {
      return "Hello, " + self.name + "!"
   }
}
bdfl = Person { name = "aspizu", age = 18 }
friend =
   Person {
      name = "friend",
      age = 17
   }
print(bdfl.greet())

Instance method binding behaviour

When a method is accessed on a struct instance, a bound method value is created. A bound method holds a reference to the corresponding instance. When it is called, the instance is passed as the first parameter automatically.

bound_method = bdfl.greet
print(bound_method()) ; Note that this does not require passing `bdfl` as an parameter.

Currently there is no way to get the instance from a lone bound method, unless the method returns self ofcourse.

Variables

Uninitialized variables are set to nil. There are no global variables.

name = value
name += value
name -= value
name -= value
name /= value
name %= value

Conditions

if condition {
   ; ...
} elif condition {
   ; ...
} else {
   ; ...
}

While and Do While loops

while condition {
   ; ...
}

Iterators

Onyo supports lazy iterators using structs. An iterator struct must define a .next() method which either returns a value or the special value iterend.

See ./examples/tests/itertools.onyo for various iterator functionality.

For loop

Iterators can be iterated on using the for loop.

for i in iter([1, 2, 3]) {
   print(i)
}

Operators

Operator Description
a + b Adds numbers, concats strings.
a - b Subtracts numbers.
-a Subtract number from 0.
a * b Multiplies numbers.
a / b Divides numbers.
a % b Mod operator.
a & b Bitwise And operator.
a ^ b Bitwise Xor operator.
a | b Bitwise Or operator.
~a Bitwise Not operator.
a << b Bitwise left shift operator.
a >> b Bitwise right shift operator.
a == b Equality operator.
a is b Identity operator. Returns true if both values point to same memory.
a != b Not equals operator.
a < b Less than operator. Compares numbers and strings.
a > b Greater than operator. ditto.
a <= b Less than equal to operator. ditto.
a >= b Greater than equal to operator. ditto.
a or b If a then b else a.
a and b If a then a else b.
not a Logical not operator.
iterable[index] Get element at index in iterable.
if b then a else c If b then a else c.
var := val Set var to val and return val.

For the arithmetic operators, if any one of the operands is a float, the result will be a float.

Type Errors

All operators return nil on type errors. Operators do not coerce types.

Builtin functions

Function Description
err(a) Wraps a in a err, if a is an err then returns the value inside it.
bool(a) Converts to bool.
int(a) Converts to int.
float(a) Converts to float.
str(a) Converts to str.
type(a) Returns the type name as a str.
index(iterable, element) Returns the index of element in iterable.
len(iterable) Returns the length of iterable.
push(list, element) Add element to the end of list.
remove(list, index) Remove element at index in list and return it.
list[index] = value Set element at index in list.
print(value) Prints value to stdout.
join(iterable, seperator) Join values in iterable by placing seperator between each element.
read(file_path) Return the contents of file at file_path as a str, returns a err(str) on failure.
write(file_path, data) Writes data into file at file_path, returns a err(str)on failure ortrue on success.

Iterable means either a str, list.

The type conversion functions return nil if the value cannot be converted.