Skip to content

🐸FroLang: An interpreted toy language written in Go

License

Notifications You must be signed in to change notification settings

mochatek/frolang

Repository files navigation

🐸 FroLang v0.1.0

FroLang is an interpreted, interactive, dynamic typed, open-source toy programming language created for the sole purpose of learning how to build an interpreter and to sharpen my Go skills.

FroLang is purely written in Go, with a syntax that is a hybrid of Python and JS. It contains all the basic features in any programming language along with a wide range of built-in methods for general use. FroLang is portable: it runs on many Unix variants, on the Mac, and on Windows 2000 and later.

Logo

How does it work?

FroLang interpreter uses an approach called Tree-Walking, which parses the source code, builds an abstract syntax tree (AST), and then evaluates this tree. These are the steps involved:

  1. Lexical Analysis: from source code (free text) to Tokens/Lexemes;
  2. Parsing: uses the generated tokens to create an Abstract Syntax Tree;
  3. AST Construction: a structural representation of the source code with its precedence level, associativity, etc;
  4. Evaluation: runs through the AST evaluating all expressions.

Installation and Usage

You can follow any of these methods to install and use FroLang:

  1. Running from source code:
    • Clone this repo
    • Install Go
    • Run go run main.go for the REPL
    • Run go run main.go fro_script_path to run a valid .fro script
  2. If Go is already installed in the system, then:
    • Install frolang: go install github.com/mochatek/frolang
    • Run frolang for the REPL
    • Run frolang fro_script_path to run a valid .fro script
  3. Docker: FroLang Image
  4. Download the compiled binary from Releases
    • Add the binary to PATH if you want to use FroLang from any location

Features

Variables

  • Declare variables using let keyword
  • Variable name should only contain letters and underscore
  • Variable names are case sensitive
  • Variables in FroLang are block scoped

Example

let language_name = "FroLang";

Comments

In FroLang, you can create single/multi-line comment using /* */

Example

/* This is a comment */

Primitive Data Types

Following primitive types are available in FroLang:

Integer

  • Whole-valued positive, negative number or zero
  • Truthy value: Non zero value

Example

let rank = 1;

Float

  • Positive or negative whole number with a decimal point
  • Truthy value: Non zero value

Example

let version = 1.1;

String

  • Sequence of characters enclosed in double quotes
  • You can access individual character by their index and index starts from 0
  • Strings in FroLang are immutable
  • Truthy value: Non empty string

Example

let author = "MochaTek";
let firstCharacter = author[0];

Boolean

  • Represents truth value; ie, true / false
  • Truthy value: true

Example

let completed = false;

Container Types

Following container types are available in FroLang:

Array

  • Represents a collection of elements
  • Elements of a FroLang array can be of completely different types
  • Multi-dimensional arrays are also supported
  • Array elements are ordered by their index and index starts from 0
  • Arrays in FroLang are immutable
  • Truthy value: Non empty array (contains at least 1 element)

Example

let items = [1, 2.5, true, "Go", [1, 2]];
let lastItem = items[4][1];

Hash

  • Represents dictionary that can store key-value pairs
  • Keys of a hash must be of primitive type (hash-able)
  • Keys of a hash is unordered
  • Values can be of any type
  • Retrieve value from a hash using the key as the index
  • Truthy value: Non empty hash (contains at least 1 key)

Example

let passwordDict = {"gmail": 123, "fb": 456};
let fbPassword = passwordDict["fb"];

Functions

  • Functions in FroLang are fist class citizens
  • Functions are created using fn keyword
  • FroLang as of now doesn't support default arguments
  • Functions in FroLang does create closures
  • Functions in froLang implicitly returns the value of last statement
  • You can explicitly return from anywhere within the body using return keyword

Example

let speak = fn(prefix) {
    let sep = ">>";
    return fn(message) { prefix + sep + message }
};

print(speak("Bot")("Hello World"));

Operators

Following operators are supported by FroLang:

Arithmetic operators

Operator Description Operands Example
+ Sum integer/float let sum = 3 + 1;
- Subtract integer/float let diff = 3 - 1;
* Multiply integer/float let prod = 3 * 2;
/ Divide integer/float let quot = 6 / 2;

💡In case of arithmetic operation, if any of the operand is having float value, then the result of the operation will also be a float value

String operators

Operator Description Operands Example
+ Concatenate string let msg = "Mocha" + "Tek";

Conditional operators

Operator Description Operands Example
< Less than int/float let res = 3 < 4;
> Greater than int/float let res = 3 > 4;
<= Less than or equal int/float let res = 3 <= 4;
>= Less than or equal int/float let res = 3 >= 4;
== Equality any let res = 3 == 4;
!= Inequality any let res = 3 != 4;

💡== and != returns boolean value. It compares the value of operands in case of primitive types, whereas it compares the reference in case of containers. Therefore, [1, 2] will not be equal to [1, 2]

💡Comparison like: (2.0 == 2) will evaluate to true, but (2.1 == 2) will not

Logical operators

Operator Description Operands Example
! Not - negates the truth value any let res = !true;
& And - evaluates the first falsy value. If both are truthy/falsy, it will evaluate the right operand any let res = true & true;
| Or - evaluates the first truthy value. If both are truthy/falsy, it will evaluate the right operand any let res = true | false;

Presence operators

Operator Description Operands Example
in Check if an element exists in a sequence or not string/array/hash let res = "a" in "FroLang";

💡In case of hash, the in operator looks for the key rather than value as in string/array

Conditionals

  • FroLang only has if and else. It doesn't have any elif or else if like in other languages
  • In FroLang, you can use if - else as an expression to mimic a ternary operation
  • Parentheses () around the condition is optional in FroLang

Example

let name = "Ronaldinho";
let goals = 5;
let rank = if goals >= 3 { 1 } else { 2 };

if(rank == 1) {
    print(name + " won 🥇");
} else {
    if goals {
        print(name, " won 🥈");
    } else {
        print(name + " won 🥉");
    }
};

Loops

FroLang supports for in and while loop for iteration

For in Loop

  • Used to iterate through each element of a sequence
  • In case of hash, iterating element is the key
  • For looping n times, you can use range(start, end) to create a sequence of length: n
  • Parentheses () around the loop expression is optional in FroLang

Example

let string = "FroLang";
for (char in string) {
    print(char);
}

let array = ["Pen", 5];
for element in array {
    print(element);
}

let hash = {"fb": 123, true: "Valid!"};
for (key in hash) {
    print(key, hash[key]);
}

/* n = 2 */
for i in range(1, 3) {
    print("count", i);
}

While Loop

  • Used to execute a block of code while a specified condition is true
  • Parentheses () around the condition is optional in FroLang

Example

let count = 1;
while count < 3 {
    print(count);
    count = count + 1;
}

Jump Statements

  • Jump statements transfer control of the program to another part of the program
  • FroLang contains 2 jump statements: break and continue which serve the same purpose as in other languages
  • Jump statements in FroLang can only be used inside loop body

Example

let count = 0;
print("Counting from 1 to 5");

 while count <= 5 {
    count = count + 1;
    if count == 2 {
        print("Skipping 2");
        continue;
    }
    if count == 4 {
        print("Stopping at 4");
        break;
    }
    print(count);
}

Error Handling

  • FroLang provides error handling mechanism to catch runtime errors using try-catch-finally block
  • The try statement defines a code block to run (to try)
  • The catch statement defines a code block to handle any error from try block
  • The finally statement defines a code block to run regardless of the result
  • Catch block is mandatory whereas finally is optional
  • Parentheses around the caught error in catch is optional

Example

try {
    print("Trying to divide 10 by 0")
    let quot = 10/0;
} catch error {
    print(error)
} finally {
    print("Done")
}

Builtin Methods

Method Description Example
print(...args) Prints arguments to stdout separated by space print("Hello ", "World")
type(arg) Returns the type of the argument type(1)
str(arg) Returns the stringified form of the argument str([1, 2])
len(iterable) Returns the length of a string/array/hash len("FroLang")
reversed(str_or_array) Reverse the order of elements in a string/array reversed("FroLang")
slice(str_or_array, start, end) Returns a slice from start to end index of a string/array. End index is exclusive slice("MochaTek", 0, 5)
range(start, end) Returns an integer array with elements ranging from start to end. End is exclusive range(0, 5)
lower(str) Returns the lower case representation of a string lower("HeLlO")
upper(str) Returns the upper case representation of a string upper("HeLlO")
split(str) Returns an array with characters of a string as elements split("FroLang")
join(array, sep=", ") Returns a string created by combing array elements separated by sep, which is ", " by default join(["F", "r", "o", "L", "a", "n", "g"], "")
push(array, ...elements) Returns a new array with elements inserted at the end push([1, 2], 3, 4)
pop(array) Returns a new array with the last element removed pop([1, 2, 3])
unshift(array, ...elements) Returns a new array with elements inserted at the beginning unshift([3, 4], 1, 2)
shift(array) Returns a new array with the first element removed shift([1, 2, 3])
keys(hash) Returns an array of keys in a hash keys({1: "one", "two": 2})
values(hash) Returns an array of values in a hash values({1: "one", "two": 2})
delete(hash, key)_ Returns a new hash with the key-value pair removed for the supplied key delete({1: "one", "two": 2}, 1)

To-Do

  • Environment variables
  • Modules
  • StdLib: datetime, fileIO
  • Help
  • Example programs
  • Compiler

Reference

Writing An Interpreter In Go by Thorsten Ball