Currently under early personal experiment alpha version development. No feature or documentation is to be considered stable, correct, optimized, or secure. Current state of code can be summed up as "hacks upon duct tapes upon hacks, with some neat bits randomly showing through". Any help and support appreciated.
Development is currently slow, as real life is pushing the author's free time to a minimum.
/// Application entrypoint.
function main() int
{
// Say hello politely
print("Hello world!");
// Just return an integer for exit coding
return 0;
}
Saha is an interpreted general purpose programming language with the following basic features acting as the basis for current development:
- Static typing
- Functions
- Objects
- Behaviors (interfaces)
- Type parameterization (generics)
Aimed to fill a space in the PHP, Python, Ruby, etc. language market, being mainly inspired by PHP and Rust.
This project has been written on Linux, so you will have to find out ways to build this on Windows, Mac, and others by yourself. No guarantees this project will work on those platforms.
Clone this repository and make sure you have a stable Rust toolchain installed.
Build with
$ cd /path/to/saha/source
$ cargo build
Binaries should appear inside target/debug/
.
A testbench tool is also provided, and some "end-to-end" tests are available
under tests/e2e/
. These can be run to make sure everything works after a
build. To run these tests invoke
$ ./target/debug/saha_testbench --command ./target/debug/saha_interpreter --test ./tests/e2e
Each passed test outputs a dot (.
), failed tests output an error.
Write a Saha source file (e.g. main.saha
)
function main() int
{
return 0;
}
Then invoke the interpreter as follows:
$ ./target/debug/saha_interpreter /path/to/saha.main
You can try out the files that are available under samples/
and see how
changing lines of code alter the output and results.
You can pass simple code to the interpreter via STDIN, but these are limited to single-file programs:
$ cat main.saha | ./target/debug/saha_interpreter
A short list of stuff that are super broken, somewhat broken, works-but-in-a-wrong-way, and completely missing:
- Garbage collection (as in I have not tested how the current symbol table value setup works in real life, I presume it is not working properly at all and memory and data is leaked like heck)
- Standard library (super slim, nothing useful available yet)
- Static analysis of AST (most currently encountered runtime errors should be parse errors instead)
- Documentation (as in how to use this language properly)
- Testing (as in we need to automatically test the interpreter and tooling actually work on a deeper level)
- Fix the workspace and crates (current separation to subcrates might be silly)
- Extensions (for things like database connections etc. which should be outside the scope of the standard library)
Wishlist for upcoming core features:
- Threading
- Async execution
- Package management and installing 3rd party code into a project properly
Below is an example of what Saha looks like and what it can do on a basic level.
/// Anything which can be run is Runnable.
///
/// Behaviors are used to define methods for other classes to implement.
behavior Runnable
{
/// Either return an exit code, or a string containing an error message.
run() Result<int, str>;
}
/// A runnable application implementation.
class Application
{
implements Runnable;
pub prop app_name'str;
/// Implement the `run()` method from the `Runnable` behavior.
pub method run() Result<int, str>
{
print_line(text = self->getRunMessage());
var ret'Result<int, str> = new Result<int, str>();
ret->succeed(value = 0);
return ret;
}
method getRunMessage() str
{
return "Hello from " + self->app_name;
}
}
function handle_run_result(result'Result<int, str>) int
{
if (result.isSuccess()) {
return result.unwrap();
} else {
print_line(text = result.unwrap());
return 1;
}
}
/// Program main entrypoint function.
function main() int
{
// No constructors, arguments given to the "newup" are passed directly
// into the object as properties.
var app'Runnable = new Application(app_name = "TestApp");
return handle_run_result(result = app->run());
}
Saha code revolves around functions at its core. The absolute unit of
interpretation is a function call, of which a function called main() int
is
deemed the entrypoint of each program.
function my_func(str'value)
{
print(text = value);
}
function main() int
{
my_func(value = "hello");
return 0;
}
Function call args are keyworded. In case the function only accepts a single argument, you can omit the keyword as it is inferred by the interpreter.
function main() int
{
print(text = "foo");
print("foo");
// calls above work identically.
return 0;
}
Saha supports classes and objects, but not inheritance. Classes can implement behaviors, which provide an interface for other pieces of code to use.
behavior SaysHello
{
sayHello();
}
class MyClass
{
implements SaysHello;
pub method sayHello()
{
print("Hello!");
}
}
Class members are private by default, and can be made public with the pub
keyword. Properties which are not defined with a default value must be
initialized when a new class instance is created.
class HelloClass
{
prop a'int;
prop b'int = 2;
pub prop c'str;
}
function main() int
{
var hc'HelloClass = new HelloClass(a = 1, c = "foo");
print(hc->c);
return 0;
}
Saha is statically typed, from local variables to function parameters and return types. Type checking makes it certain that you do not attempt to operate with mixed types.
function hello(int'value) int
{
print(text = value);
// above call results in an error, as print expects a `str` and not `int`
return 1;
}
Saha offers the following builtin primary types:
int
for whole numbersfloat
for floating point numbersstr
for unicode stringsbool
for boolean values,true
andfalse
Other types are classes either defined in the standard library or in your own code. Notable standard library classes are
List<T>
for a list of itemsDict<T>
for a key-value list of items, where keys arestr
Option<T>
for items that are eitherT
or nothingResult<T, U>
for items that are OK withT
, or errors withU
Project author has no CS background and so has no idea which term is more correct here.
Saha has generics:
class MyGenericClass<T>
{
prop t_prop'T;
pub method myMethod() T
{
return self->t_prop;
}
}
function main() int
{
var mgc'MyGenericClass<int> = new MyGenericClass<int>(t_prop = 1);
var value'int = mgc->myMethod();
return 0;
}
Generics are not limited to builtin types, you can use your own classes and nest generic code as you wish.
Saha has no concept of exceptions. With the use of Result<T, U>
type error
management and recovery is forced upon the calling party, who can then decide
whether to handle the error value or pass it on as an error.
function my_func() Result<int, str>
{
var res'Result<int, str> = new Result<int, str>();
// int is the success type, str is the error type
return res->succeed(value = 2);
}
function main() int
{
var result'Result<int, str> = my_func();
if (result->isOk()) {
return result->unwrap();
}
print(result->unwrap());
return 1;
}
This is very similar to how Rust and Go use result values in error handling.
Saha supports splitting code into multiple files and directories.
In /path/to/project/src/main.saha
:
use pkg.my_module.SubmoduleClass; // `pkg` marks the root of the project which contains the `main.saha` file
use pkg.another.submodule.AnotherClass as AC;
function main() int
{
var obj'SubmoduleClass = new SubmoduleClass();
var ac'AC = new AC();
return obj->getNumber();
}
In /path/to/project/src/my_module.saha
:
class SubmoduleClass
{
pub method getNumber() int
{
return 4; // guaranteed to be random
}
}
In /path/to/project/src/another/submodule.saha
:
class AnotherClass()
{
//
}
To be written.
To be written.
To be written.
MIT License. See LICENSE.md
.