Home - Hitonoriol/Methan0l GitHub Wiki
Up-to-date as of 7ecbd88
-
expr
-- any expression. -
idfr
-- identifier, alphanumeric name (underscores can be a part of idfr too) associated with a Value.
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.
Primitives:
-
Nil
- Typeid literal:
Nil
- Literals that evaluate to nil:
nil
,void
- Typeid literal:
-
Integer
- Typeid literal:
Int
- Supports hex (
0x123
) and binary (0b0110101
) literal formats
- Typeid literal:
-
Float
- Typeid literal:
Float
- Typeid literal:
-
Boolean
- Typeid literal:
Boolean
- Typeid literal:
-
String
- Typeid literal:
String
- Escape sequences are supported inside string literals.
- Typeid literal:
-
Char
- Typeid literal:
Character
- Literal format:
'c'
, escape sequences are allowed too:'\n'
, ...
- Typeid literal:
-
Reference
- Typeid literal:
Reference
- Typeid literal:
Data Structures:
-
List
- Typeid literal:
List
- Typeid literal:
-
Set
- Typeid literal:
Set
- Typeid literal:
-
Map
- Typeid literal:
Map
- Typeid literal:
Callables:
-
Unit
- Typeid literal:
Unit
- Typeid literal:
-
Function
- Typeid literal:
Function
- Typeid literal:
Custom types:
-
Object
- Type literal:
Object
- Type literal:
Unevaluated expressions:
-
Expression
- Typeid literal:
Expression
- Returned by
noeval: expr
operator
- Typeid literal:
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)
...
-
nil
andvoid
-- evaluate to an empty value -
newl
-- evaluates to the newline character -
true
andfalse
-- evaluate to Boolean -
Type literals -- evaluate to Int, can be used in RHS of
expr instanceof: int_expr
operator, compared to the result oftypeid: expr
operator, etc.
There's only one type of comments supported:
/* Comment text */
.
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
Most Methan0l operators have traditional behavior that doesn't differ from any other language.
Operator expressions can also be grouped using (
)
.
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 |
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 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
-- convertexpr
's Value to string and print it out to stdout. -
<% expr
-- print with trailing newline.
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)
-
::
-- 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" */
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 then
-th argument. Numbering starts with 1.
==
, !=
, >
, >=
, <
, <=
Equals operator ==
works for every type (including Units -- in their case expression lists are compared), while all others are implemented only for numeric types.
Binary:
&&
, ||
, ^^
Unary:
!
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.
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` */
-
unwrap: idfr
-- unwraps a named reference, e.g.x = **y; unwrap: x
--x
becomes a copy ofy
instead of the reference to its value. Returns a reference to the unwrapped identifier. -
is_reference: expr
-- returns true ifexpr
evaluates to a Reference -
expr!!
-- returns a copy of the value behind the referenceexpr
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 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
...
}
for (i = 0, i < 10, ++i) {
...
}
while (i < 10) {
...
i++
}
for (as_elem, list) {
...
}
You can interrupt a loop via the break expression:
return: break
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 {
...
}
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.
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 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.
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 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.
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.
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)
-
Remove element:
container[~expr]
, whereexpr
is element's index for Lists / the element itself for Sets / key for Maps. -
Get size:
size(container)
orcontainer.size()
-
Clear:
container.clear()
orcontainer[~]
-- 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 fromb
toa
-
a.remove_all(b)
-- remove all elements ofa
that are present inb
-
a.retain_all(b)
-- remove all elements ofa
that are not present inb
-
list.fill(val, [n])
-- filllist
with copies ofval
. It's filled to its full capacity or resized ton
elements and then filled. -
ctr.sum()
-
ctr.product()
-
ctr.mean()
-
ctr.rms()
-
ctr.deviation()
-
ctr.join()
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.
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)
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)
.
Herekeys
andvalues
are reserved words, not identifiers.
Iterate over a map:
do $(key, map.list_of$(keys)) -> {
val = map[key]
...
}
-
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.
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.
Input operator:
%> idfr
String input function:
read_line()
-- read string from stdin and return it.
Usage: foo = read_line()
Output operators:
<% expr
, %% expr
hashcode: expr
(overloadable for classes)
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.
-
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
-
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)
-
get_args()
-- can be called from inside a function's body to get a list of all arguments passed to it.
-
get_launch_args()
-- Get command line args passed to the currently running Methan0l program.
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)
get_os_name()
-
get_runpath()
,get_rundir()
-- get path to the currently running interpreter executable / its parent directory
get_version_code()
get_minor_version()
get_release_version()
get_major_version()
-
get_version()
-- returns version string
sync_work_dir()
-- change working directory to the location of currently running script
unit.make_box()
-- get a persistent copy of Unit (preserves Unit's data table after the execution)
is_main_unit()
-- true if and only if unit currently being executed is at the bottom of the execution stack
-
exit()
-- stop program execution. -
die(expr)
-- throw an exceptionexpr
that can be caught by a try-catch statement. -
err(msg)
-- printmsg
to stderr. -
pause(msecs)
-- pauses execution for at leastmsecs
milliseconds. -
selfinvoke(...)
-- can be used from inside a function's body to recursively call it. -
unit.local(action)
-- executeaction
insideunit
's scope.
Ifunit
is non-persistent, it's executed first andaction
is executed after it.unit
's data table is then cleared as if after a regular execution.
measure_time(expr)
-- returns time spent on evaluation of expr
in milliseconds, discarding the result, if any.
Get a reference to identifier's value by name:
-
unit.value("idfr_name")
orunit["idfr_name"]
-- performs lookup inunit
's scope. -
value("idfr_name")
-- global scope lookup.
-
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)
pow(x, n)
sqrt(x)
exp(x)
log(x)
log10(x)
logn(base, x)
ceil(x)
floor(x)
-
round(dbl, n)
- rounddbl
ton
decimal places
abs(x)
-
str.substr(start, [length])
-- returns a substring of the specified string, starting from thestart
'th character. If length is not specified, the remainder is used. -
str.find(substr, [start_pos])
-- returns index ofsubstr
inside the specified string. If not found,-1
is returned instead. -
str.contains(substr)
-- returnstrue
if specified string containssubstr
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 basebase
. -
str.repeat(times)
-
str.erase(start, [length])
-- erases a substring from the specified string, starting from thestart
'th character. If length is not specified, the remainder is used. -
str.replace(from_str, to_str, [limit])
-- replacelimit
occurrences offrom_str
withto_str
. If limit is not specified, all occurrences will be replaced. -
str.insert(pos, substr)
-- insertsubstr
after thepos
'th character of the provided string.
file = File.new(path_str)
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"
-
file.open()
-- open file for reading / writing -
file.close()
-- close file -
file.read_line()
-- read one line from file as String. Setsfile.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 thefile
to beopen()
'd. -
file.write_contents(str)
-- writes the whole stringstr
tofile
, deleting previous contents, if any.
Doesn't require thefile
to beopen()
'd.
-
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)
-- returnstrue
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()
-- returnstrue
if this file is a directory -
file.exists()
-- returnstrue
if this file exists -
file.for_each(action)
-- iterates recursively through all files in directoryfile
, callingaction(file_name)
for each one of them. -
file.rename(new_name)
-
file.remove()
-
File@cwd()
-- returns path to current working directory.
-
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.
rnd = Random.new()
rnd = Random.new(seed)
If seed
is not specified, a random one will be used (provided by std::random_device
).
-
rnd.get_seed()
-- returns Integer Value of seed used by this rng. -
rnd.reseed(new_seed)
-- re-seeds the rng with specified seed,
-
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].
-
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].
-
rnd.next_boolean()
-- returnstrue
with ~50% probability. -
rnd.next_boolean(probability)
-- returnstrue
with probabilityprobability
in range [0; 1].
using_module: "modules/system"
-
exec(str)
-- executestr
as a system shell command. Returns a list of$(stdout_str, return_value)
using_module: "modules/ncurses"
Partial libncurses bindings.