Home - Hitonoriol/Methan0l GitHub Wiki

Methan0l Documentation

Up-to-date as of 7ecbd88

Aliases in this document

  • expr -- any expression.
  • idfr -- identifier, alphanumeric name (underscores can be a part of idfr too) associated with a Value.

Introduction

Methan0l is a general-purpose interpreted programming language.

Every Methan0l program is a Unit - a block of expressions that can return a Value.

Expressions can be either separated by a semicolon / newline or just a space (semicolons are not required to write multiple expressions on one line):

foo = 999; bar = 0.0725

/* Valid too */
calc = foo * bar bar += 123

%% calc
...


Expressions can be either evaluated or executed.

Evaluation of an expression returns some kind of value, while execution doesn't return anything. Execution and evaluation are equivalent by default for all expressions, but may behave differently for some (for example: if identifier is used inside other expression (evaluation), its value is extracted from the nearest local scope, while if it's used by itself (execution) the value is extracted and then executed).

Every parentless expression is executed by the interpreter while expressions that have parents get evaluated for the result to be used in the parent expression.

Data Types

Primitives:

  • Nil
    • Typeid literal: Nil
    • Literals that evaluate to nil: nil, void
  • Integer
    • Typeid literal: Int
    • Supports hex (0x123) and binary (0b0110101) literal formats
  • Float
    • Typeid literal: Float
  • Boolean
    • Typeid literal: Boolean
  • String
    • Typeid literal: String
    • Escape sequences are supported inside string literals.
  • Char
    • Typeid literal: Character
    • Literal format: 'c', escape sequences are allowed too: '\n', ...
  • Reference
    • Typeid literal: Reference

Data Structures:

  • List
    • Typeid literal: List
  • Set
    • Typeid literal: Set
  • Map
    • Typeid literal: Map

Callables:

  • Unit
    • Typeid literal: Unit
  • Function
    • Typeid literal: Function

Custom types:

  • Object
    • Type literal: Object

Unevaluated expressions:

  • Expression
    • Typeid literal: Expression
    • Returned by noeval: expr operator

Identifiers

Identifiers (variables) are dynamically-typed and can be accessed either locally or globally.

Local access is performed just by referencing the identifier's name:

{
	x = 123
	foo(x) /* Access local variable x */
}


Global access is used to access variables from the parent scopes (if the current unit is not weak):

x = "global value"
{
	x = "local value"
	foo(#x) /* Access x from the outer scope */
}


Variables from outer scopes can also be imported into the current one (by reference) via the global operator:

global: var1, var2, ...
foo(var1)
...

Reserved Identifiers

  • nil and void -- evaluate to an empty value
  • newl -- evaluates to the newline character
  • true and false -- evaluate to Boolean
  • Type literals -- evaluate to Int, can be used in RHS of expr instanceof: int_expr operator, compared to the result of typeid: expr operator, etc.

Comments

There's only one type of comments supported:
/* Comment text */.

Scopes

Each Unit has its own local scope. By default, Units can only access idfrs in it (or up to the first Regular Unit in case of Weak Units).
To access an idfr from the global scope, use # prefix before it:
foo = #global_idfr

Operators

Most Methan0l operators have traditional behavior that doesn't differ from any other language.
Operator expressions can also be grouped using ( ).

Operator precedence

The higher the precedence level is, the tighter the operators are bound to their operands.

Operators Precedence
noeval 1
<%, %%, %> 2
+=, -=, *=, /=, ... 3
=, ->, := 4
:: 5
e1 ? e2 : e3 6
|| 7
&& 8
| 9
^ 10
& 11
==, != 12
>, <, <=, >= 13
>>, << 14
+, - 15
*, /, % 16
-, !, ~ (prefix) 17
++, -- (prefix) 18
Postfix 19
., @ 20
[] 21
() 22
++, -- (postfix) 23

Assignment expressions

Copy assignment
Any expression's evaluated result can be assigned to an identifier:
dest_expr = expr
This effectively creates a full copy of the expr's Value. This means that for heap-stored types a new object containing the copy of the expr's Value is created.
Copy assignment is evaluated right-to-left.

Move assignment
expr -> dest_expr
Creates a temporary partial copy of expr, removes it from the data table (if expr is an idfr) and assigns the partial copy to dest_idfr.
Move assignment is evaluated left-to-right.

Type-keeping assignment
dest_expr := expr
Evaluates expr and converts it to the dest_expr's type before assigning to it.
Type-keeping assignment is evaluated right-to-left.

Input / output operators

Input operator: %> idfr -- get a value from stdin, deduce its type and assign it to the specified identifier.

Input can also be read as a String using read_line() function.

Output operators:

  • %% expr -- convert expr's Value to string and print it out to stdout.
  • <% expr -- print with trailing newline.

Arithmetic operators

If at least one operand of the expr is Float, the result is also Float (except for the case of bitwise operators and % -- only Integers are supported, no implicit conversion is performed). Otherwise Integer arithmetic is performed.

Binary:
+, -, *, /, %
+=, -=, *=, /=, %=
&, |, ^, >>, <<
&=, |=, ^=, >>=, <<=

Unary:
++, -- (both prefix and postfix)
-, ~ (prefix)

String operators

  • :: -- concatenation operator, converts operands to string and creates a new String Value with the result of concatenation.
    Example: result = "One " :: 2 :: " " :: 3.0.

  • $$ (prefix) -- string conversion operator. Example:

    x = 123
    <% ($$x).substr(1)
    /* Outputs "23" */
    

Formatted strings

Syntax:

$"Foo {} bar {} baz {2}!" arg1, arg2, ...

Formatter syntax:

  • {} is replaced by the next argument in list
  • {n}, (n is an Int) is replaced by the n-th argument. Numbering starts with 1.

Comparison operators

==, !=, >, >=, <, <=

Equals operator == works for every type (including Units -- in their case expression lists are compared), while all others are implemented only for numeric types.

Logical operators

Binary:
&&, ||, ^^

Unary:
!

Return operator

Return operators stop the execution of a Unit and returns the result of the provided expr evaluation.

Prefix:

  • return: expr -- return by partial copy (copies only the pointer for heap-stored types, "full" copy can then be made via the = operator).

Postfix:

  • expr! -- return by partial copy.

Reference operator

Get a reference to the Value associated with identifier:
**idfr

Example:

foo = 123
ref = **foo
/* Now <foo> and <ref> point at the same Value */

%% ++ref	<-- Outputs 124


! To assign a reference to a variable it's always required to use the ** operator before the expression being assigned, even if it evaluates to a reference. Otherwise, a copy of the value behind the reference will be assigned. Example:

f = func: x -> x *= 10 /* Explicit `**` is required only when assigning  */
foo = 123

bar = f(**foo)
/* `bar` is now an Int variable = 1230
 * (f() call evaluated to a copy instead of reference),
 * `foo` is now = 1230 */

baz = **f(**foo)
/* `baz` is now a Reference to `foo` = 12300,
 * so any subsequent changes to `baz` will modify
 * the value of `foo` */

Reference-related operators

  • unwrap: idfr -- unwraps a named reference, e.g. x = **y; unwrap: x -- x becomes a copy of y instead of the reference to its value. Returns a reference to the unwrapped identifier.
  • is_reference: expr -- returns true if expr evaluates to a Reference
  • expr!! -- returns a copy of the value behind the reference expr

Conditional (Ternary) Operator

condition ? expr_then : expr_else

The condition is converted to Boolean and if true, the Then expression is evaluated and returned; otherwise -- the Else expression result is returned.

If-else statements

If-else operator in Methan0l uses the same syntax as ternary operator, but with if and else words used before the conditions and in between the then and else branches:

/* if-else */
if (condition) ? {
	expr1
	...
} else: {
	else_expr1
	...
}

/* if only */
if (condition) ? {
	expr1
	...
}

/* if-elseif-...-else */
if (condition1) ? {
	expr1
	...
} else: if (condition2) ? {
	elseif_expr1
	...
} else: {
	else_expr1
	...
}

Loops

For loop

for (i = 0, i < 10, ++i) {
	...
}

While loop

while (i < 10) {
	...
	i++
}

Foreach loop

for (as_elem, list) {
	...
}

Break expression

You can interrupt a loop via the break expression:

return: break

Try-catch statement

During a program's execution exceptions may be thrown either by the interpreter itself or native modules or by using the die(expr) function, where expr can be of any type and can be caught by the try-catch expression:

try {
	...
} catch: name {
	...
}

Units

Regular Unit can be defined using the following syntax:

unit = {
	expr
	expr
	...
}

Exprs between { and } will be added to the Unit's expr list. If assigned to an idfr, it then can be called either as a pure expression (returned value will be discarded in this case):


unit	/* <-- calls the unit defined above */

or using the invocation syntax:

...

result = unit() /* <-- unit's return will be captured, if exists */


Function bodies are always regular units.

Weak Units

Return from a Weak Unit also causes all subsequent Weak Units stop their execution & carry the returned value up to the first Regular Unit. Typical use case for this is loop / conditional operator body.
Weak Units can also access idfrs from the scopes above the current one up to the first Regular Unit without using the # prefix.
Loop & if-else expression bodies are Weak Units.

Definition:

-> {
	expr1
	expr2
	...
}

Box Units

Box Unit preserves its data table after the execution and is executed automatically on definition. Fields inside it can then be accessed by using the . operator.
Definition:

module = box {
	some_field = "Blah blah blah"
	foo = func @(x) {
		x.pow(2)!
	}
}

<% module.foo(5)	/* Prints 25 */
<% module.some_field	/* Prints contents of the field */


Any Non-persistent Unit can also be converted to Box Unit using unit.make_box() function.

Box Units as well as Non-persistent ones can also be imported into the current scope:

module.import()

This effectively executes all expressions of module in the current scope.

Pseudo-function invocation

Units can also be invoked with a single argument that's also Unit (Init Block). Init block will be executed before the Unit being invoked and the resulting Data Table (which stores all identifiers local to the Unit) will be shared between them. This syntax can be used to "inject" identifiers into a Unit when calling it, for example:

ratio = {
	if (b == 0) ? {"inf"!}
	return: (a / b) * 100.0
}

%% "a is " :: ratio({a = 9.125; b = 13}) :: "% of b" :: newl

Execution Syntax

Execution syntax can be used with any expression:

Literals are converted to string and printed out:

"Some string"
123
0.456
true


String concatenation operator (::) expressions are evaluated and printed out.


Identifiers are evaluated and then executed:

some_number = (foo + bar) * c / 1.5
some_number	/* <-- Execution syntax, the result will be printed out */

unit = {%%("Hello!");}
unit	/* <-- Execution syntax, unit will be executed */


For the expressions not listed here Execution Syntax behavior is identical to Evaluation.

Functions

Definition:

foo = func (arg1, arg2, arg3 => def_value, ...) {
    ...
}


Or:

foo = func: arg1, arg2, arg3 => def_value, ... {
    ...
}


Or:

foo = @: arg1, arg2, arg3 => def_value, ... {
    ...
}


Or as a lambda expression:

foo = @: x, y -> x + y

Here the expression after the -> token is wrapped in a return expression, so its result will be returned from the function without needing to explicitly use the return operator.


Functions can be called using invocation syntax:

result = foo(expr1, expr2, ...)


Functions can also be called by using pseudo-method syntax:

value.func(arg1, arg2, ...)

The expression above is internally rewritten as func(value, arg1, arg2, ...) and can be used even with literals, for example:

42.sqrt()
"foo bar".find("bar")


Explicit pseudo-method syntax:

expr<-func(arg1, arg2, ...)

This syntax skips all type deductions of expr and calls func as pseudo-method of expr immediately.

Unevaluated expressions

Can be created by using the following syntax:

foo = noeval: expr

In this case expr won't be evaluated until the evaluation of foo. When foo gets evaluated or executed, a new temporary value containing expr's evaluation result is created and used in place of foo while foo's contents remain unevaluated.

Example:

x = noeval: y * 2
y = 2

/* Equivalent to x = 2 + 2 * 2 */
<% "Result: " :: (x += y)

Container types

Generic container operations

  • Remove element: container[~expr], where expr is element's index for Lists / the element itself for Sets / key for Maps.

  • Get size: size(container) or container.size()

  • Clear: container.clear() or container[~] -- works for Strings as well.

  • Test emptiness: container.is_empty() -- works for Strings as well.

  • Iterate over a container functionally (allows element modification):

    container.for_each(funcexpr)
    

    Iterate using bracket-foreach syntax:

    container[do: funcexpr]
    

    Where funcexpr is:

    • A 1-arg function for Lists and Sets.
    • A 2-arg function for Maps (where 1st argument holds a copy of the current key and 2nd holds a reference to the current Value).
  • a.add_all(b) -- add all elements from b to a

  • a.remove_all(b) -- remove all elements of a that are present in b

  • a.retain_all(b) -- remove all elements of a that are not present in b

  • list.fill(val, [n]) -- fill list with copies of val. It's filled to its full capacity or resized to n elements and then filled.

  • ctr.sum()

  • ctr.product()

  • ctr.mean()

  • ctr.rms()

  • ctr.deviation()

  • ctr.join()

Lists

Definition:
list = $(expr, expr, expr, ...)

  • Append a new element: list[] = "New element"

  • Access an element: list[expr]

  • Join as String: list.join()


Iterate over a list:

do $(elem, list) -> {
    ...
}


Map into another list:

list.map(funcexpr)

Where funcexpr is a 1-arg function. This can be used for list filtering: if funcexpr doesn't return anything for a given input, no element will be appended to the resulting list.

Sets

Definition:
set = defset $(expr, expr, expr, ...)
or
set = list.convert(type set)

  • Insert a new element: set[->expr]
  • Test element's existence: set[expr]

Set operations:

  • Union: a.union(b)
  • Intersection: a.intersect(b)
  • Difference: a.diff(b)
  • Symmetric difference: a.symdiff(b)

Map into another set:

set.map(funcexpr)

Maps

Definition:
map = @(key1 => val_expr1, key2 => val_expr2, ...)

Keys in map definition can be specified either with or without quotes: unquoted keys won't be evaluated as identifiers. If you want to use spaces / special characters in a key, you must declare it as a string literal.

  • Access existing / Add new element: map["key"]
    When using [] operator, expressions inside it are evaluated, so string literals must be used to access string keys, even if defined without quotes.

  • Get key / value list: map.list_of(keys) / map.list_of(values).
    Here keys and values are reserved words, not identifiers.


Iterate over a map:

do $(key, map.list_of$(keys)) -> {
	val = map[key]
	...
}

Modules

  • load(path_str_expr) -- load a Methan0l source file or a native module as a Box Unit.

It can be assigned to an identifier and its contents can be accessed via the . operator, as with regular Box Units:

sys = "modules/system".load()
<% sys.exec("ls -a")

or imported into the current scope - either by using load() function or using_module: path_to_module operator.

Classes

Definition:

class: ClassName @(
	private => $(field1, field2, field3)
	
	construct => func @(arg1, arg2, arg3) {
		...
	}
	
	method_a => func @(arg1, arg2) {
		this.field1 = arg1
		this.field2 = arg2
	}
	
	static_method => func @(x) {
		%% "Static method arg: " :: x :: newl
	}
	...
)


Constructor call syntax:

obj = new: ClassName(arg1, arg2, arg3)


Methods or fields of an object can be accessed by using "dot operator" ( . ):

foo = obj.field
obj.some_method(arg1, arg2, ...)


Static Methods can be called using the following syntax:

x = ClassName@static_method(foo)


Classes also support operator overloading:

class: Foo @(
	construct => func: x => 0, y => 0 {
		this.x = x
		this.y = y
	}
	
	`+` => @: rhs -> new: Foo(this.x + rhs.x, this.y + rhs.y)
	`*=` => @: () -> <% "Overloaded compound assignment"
	...
)


Class id of an object can be checked by calling class_id() method as a static method or as a method of an object itself:

obj = Class.new(a, b, c)
obj.class_id() == Class@class_id()
/*             ^^ Evaluates to true */


Objects can be copied using objcopy: obj operator.



Inbuilt library functions

Input / output operators & functions [LibIO]

Input operator:
%> idfr

String input function:
read_line() -- read string from stdin and return it.
Usage: foo = read_line()

Output operators:
<% expr, %% expr

Data structure operators & functions [LibData]

Hash a value of any type

hashcode: expr

(overloadable for classes)

Define a value of type

var = defval: typeid

Operator defval creates a new default-initialized Value of type typeid, where typeid is one of the typeid literals.

This can then be used in combination with type-keeping assignment := and type assert expr1 assert_type: typeid_expr to ensure that initial type of var remains unchanged.

Operators

  • delete: idfr -- delete idfr & the value associated with it

  • typeid: expr -- get typeid of an evaluated expr.
    Can be compared to one of typeid literals.

    Example:

    %% (typeid: "foo") == type string /* Prints "true" */
    
  • msg_expr assert: condition_expr

  • expr type_assert: typeid_expr

  • expr instanceof: typeid_expr

Generic Functions

  • expr.convert(typeid) -- returns a copy of evaluated expr converted to specified type, example:

    bool = "true".convert(type boolean)
    str = 1337.convert(type string)
    dbl = "-1.234".convert(type double)
    

Variadic function arguments

  • get_args() -- can be called from inside a function's body to get a list of all arguments passed to it.

Command line arguments

  • get_launch_args() -- Get command line args passed to the currently running Methan0l program.

Range

Generate a List with Integer Values in range [0; n - 1]:

range(n)


Range [start, n - 1]:

range(start, n)


Set {start, start + step, ..., n - 1}:

range(start, n, step)

System

  • get_os_name()
  • get_runpath(), get_rundir() -- get path to the currently running interpreter executable / its parent directory

Get Methan0l version:

  • get_version_code()
  • get_minor_version()
  • get_release_version()
  • get_major_version()
  • get_version() -- returns version string

Unit-related functions & operators related [LibUnit]

sync_work_dir() -- change working directory to the location of currently running script

Persistence

unit.make_box() -- get a persistent copy of Unit (preserves Unit's data table after the execution)

Check if current Unit is the program's entry point

is_main_unit() -- true if and only if unit currently being executed is at the bottom of the execution stack

Execution control

  • exit() -- stop program execution.

  • die(expr) -- throw an exception expr that can be caught by a try-catch statement.

  • err(msg) -- print msg to stderr.

  • pause(msecs) -- pauses execution for at least msecs milliseconds.

  • selfinvoke(...) -- can be used from inside a function's body to recursively call it.

  • unit.local(action) -- execute action inside unit's scope.
    If unit is non-persistent, it's executed first and action is executed after it. unit's data table is then cleared as if after a regular execution.

Benchmark

measure_time(expr) -- returns time spent on evaluation of expr in milliseconds, discarding the result, if any.

Reflection

Get a reference to identifier's value by name:

  • unit.value("idfr_name") or unit["idfr_name"] -- performs lookup in unit's scope.

  • value("idfr_name") -- global scope lookup.

Common math functions [LibMath]

Trigonometry

  • rad(deg) -- degrees to radians
  • deg(rad) -- radians to degrees
  • sin(rad)
  • cos(rad)
  • tan(rad)
  • acos(x)
  • asin(x)
  • atan(x)
  • atan2(x, y)

Power functions

  • pow(x, n)
  • sqrt(x)

Exponential and logarithmic functions

  • exp(x)
  • log(x)
  • log10(x)
  • logn(base, x)

Rounding

  • ceil(x)
  • floor(x)
  • round(dbl, n) - round dbl to n decimal places

Absolute value

  • abs(x)

Common String functions [LibString]

Functions that don't modify String contents

  • str.substr(start, [length]) -- returns a substring of the specified string, starting from the start'th character. If length is not specified, the remainder is used.

  • str.find(substr, [start_pos]) -- returns index of substr inside the specified string. If not found, -1 is returned instead.

  • str.contains(substr) -- returns true if specified string contains substr at least once.

  • str.split(delim_expr) -- returns a List of tokens.

  • int_val.to_base(base) -- returns a String representation of the provided Integer Value converted to base base.

  • str.repeat(times)

Functions that modify String contents

  • str.erase(start, [length]) -- erases a substring from the specified string, starting from the start'th character. If length is not specified, the remainder is used.

  • str.replace(from_str, to_str, [limit]) -- replace limit occurrences of from_str with to_str. If limit is not specified, all occurrences will be replaced.

  • str.insert(pos, substr) -- insert substr after the pos'th character of the provided string.



Inbuilt Classes

File

Constructor:

file = File.new(path_str)

Path prefixes

Special prefixes can be used at the beginning of the path string when constructing a File instance / calling File methods statically:

  • $: - expands into interpreter run directory
  • #: - expands into script run directory

When used, these prefixes are expanded and prepended to the rest of the path string.

Example: "$:/modules/ncurses" becomes: "/path/to/binary/modules/ncurses"

Methods:

Read / Write operations

  • file.open() -- open file for reading / writing

  • file.close() -- close file

  • file.read_line() -- read one line from file as String. Sets file.eof field to true if the end of file has been reached.

  • file.write_line(expr) -- evaluate expr, convert it to string and write to file

  • file.read_contents() -- returns full contents of the managed file as a String Value.
    Doesn't require the file to be open()'d.

  • file.write_contents(str) -- writes the whole string str to file, deleting previous contents, if any.
    Doesn't require the file to be open()'d.

Miscellaneous

  • file.set(path) -- sets the path of the file managed by this object.

  • file.mkdirs() -- creates all non-existing directories in the managed path String.

  • file.cd() -- changes current working directory to the managed path.

  • file.equivalent(path) -- returns true if managed file is equivalent to the specified file.

  • file.copy_to(path) -- copies managed file to the specified directory.

  • file.extension() -- returns the file's extension as String

  • file.size() -- returns file size in bytes as Integer

  • file.absolute_path() -- returns absolute path to the managed file as String

  • file.filename() -- returns filename of the managed file as String

  • file.is_dir() -- returns true if this file is a directory

  • file.exists() -- returns true if this file exists

  • file.for_each(action) -- iterates recursively through all files in directory file, calling action(file_name) for each one of them.

  • file.rename(new_name)

  • file.remove()

Static methods

  • File@cwd() -- returns path to current working directory.

Accessible fields

  • file.eof -- Boolean Value. Set only after calling read operations. True if the end of the managed file has been reached.


! All File methods (that don't modify the state of the object) can be invoked statically.

Random

Constructors:

rnd = Random.new()
rnd = Random.new(seed)

If seed is not specified, a random one will be used (provided by std::random_device).

Methods:

RNG Seeding

  • rnd.get_seed() -- returns Integer Value of seed used by this rng.

  • rnd.reseed(new_seed) -- re-seeds the rng with specified seed,

Random Integer

  • rnd.next_int() -- returns next pseudo-random Integer Value in 64-bit range.

  • rnd.next_int(n) -- returns next pseudo-random Integer Value in range [0; n - 1].

  • rnd.next_int(a, b) -- returns next pseudo-random Integer Value in range [a; b].

Random Double

  • rnd.next_double() -- returns next pseudo-random Double Value in range [0; 1].

  • rnd.next_double(n) -- returns next pseudo-random Double Value in range [0; n - 1].

  • rnd.next_double(a, b) -- returns next pseudo-random Double Value in range [a; b - 1].

Random Boolean

  • rnd.next_boolean() -- returns true with ~50% probability.

  • rnd.next_boolean(probability) -- returns true with probability probability in range [0; 1].



Inbuilt modules

System

using_module: "modules/system"
  • exec(str) -- execute str as a system shell command. Returns a list of $(stdout_str, return_value)

Ncurses

using_module: "modules/ncurses"

Partial libncurses bindings.

⚠️ **GitHub.com Fallback** ⚠️