swapro
v4.0.1
Published
swap is a programming language that allows programmers to code in Swahili language constructs
Maintainers
Readme
Swap
Swahili Programming Language (SWAP)
SWAP is a programming language that uses Swahili keywords to represent common programming constructs. It is designed to be simple and easy to learn, making it accessible for beginners and those who are more comfortable with the Swahili language.
It was the first of its kind to bring programming concepts to Swahili speakers in a native language context, and it has inpired to the creation of other programming languages in Swahili, such as pyswahili and swahili-lang.
Credits
Lots of credit to the author of yorlang @anoniscoding, he's the one who inspired me to create this programming language, and also for his great work on yorlang which was the base foundation for the initial version of swap.
TUTORIAL
INSTALLATION
Getting Started with Swap you need to have node runtime installed on your machine, After making sure that node runtime works on your machine run the following command
npm install -g swapro
After installation download run the command
swap -h
if your installation was successful the following result will show up
██████ ██ ██ ██████ ████████
██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██
██████ ██ ██ ██ ██████████ ████████
██ ██ ██ ██ ██ ██ ██
██ ██ ████ ████ ██ ██ ██
██████ ██ ██ ██ ██ ██
swahili programming language v3.0.0
by Abdulbasit Rubeya
Usage: cli [options] [file]
Options:
-v, --version output the version number
-l, --lang <lang> language for error messages (english|swahili)
-h, --help output usage information
author: Abdulbasit Sultan Rubeiyya
Examples:
$ swap file.sw
$ swap -h
$ swap -vThe banner is only shown for these info commands (bare swap, -h/--help, -v/--version, -i/--interactive) - it's skipped while a .sw file is running so it doesn't clutter your program's output, and it's drawn in a random color gradient each time, so don't worry if the colors look different between runs.
INTERACTIVE REPL
Run swap -i (or swap --interactive) to start an interactive session instead of running a file - useful for trying out small snippets of Swap without creating a .sw file. Variables and functions you declare stay available for the rest of the session, so you can build on earlier lines:
$ swap -i
swap> hifadhi x = 5;
swap> andika x + 10;
15
swap> toka;
Kwaheri!Statements are evaluated one at a time as you press Enter, same as in a file - each one still needs its own terminating ;. A block that spans multiple lines (wakati, kama, njia, jaribu, ...) is buffered and only run once its closing } is typed, shown with a continuation prompt (...>). Type toka; to exit the session.
Unlike Node's REPL, a bare expression like 1 + 2 on its own is not valid syntax (same as in a .sw file) - use andika ...; to print a value.
Runtime errors are reported as a single clean message (e.g. Error: There's an error at line 3 near column 10 in file example.sw : ...) rather than a raw Node.js stack trace. If you're working on the interpreter itself and want the full JS stack trace, run with SWAP_DEBUG=1, e.g. SWAP_DEBUG=1 swap file.sw.
To program using swap we will be using vscode, so open the program and install the swap extension
- Linux : no more configurations start right away
- Windows: no extra configuration needed either — swap accepts LF (Unix), CRLF (Windows), and legacy CR (old Mac) end-of-line formats and normalizes them automatically, so your editor's default line ending just works.
NOTE: The file extension for a swap file is .sw
First Program
Swap does not use any preprocessors, It uses the constant andika to print out the desired content, andika literally means "write"
eg. 1
andika "habari yako";Result:
habari yakoEvery line of code has and must be terminated by a delimiter which is a semicolon(;)
To run your program initiate a CMD in your project directory and run the command
swap file.sw
File should be replaced with the name of your file.
Comments
Swap supports three comment styles: # and // for a single-line comment (everything up to the end of the line is ignored), and /* ... */ for a comment that can span multiple lines.
eg. 1b
# this is a comment
// this is also a comment
/* this is a
multi-line comment */
andika "habari"; // prints habariResult
habariNote that string literals have no escape sequence, so a " can't appear inside a string, and there's no way to write a literal */ inside a block comment either — the first */ a block comment finds always ends it.
Variable Declaration
Variables can only be declared in Swap using the keyword hifadhi which means to store something.
eg. 2
hifadhi a = 10;
hifadhi b = 20;
andika a + b;Result
30eg. 3
hifadhi jina= "juma";
hifadhi umri = 5 ;
andika jina + " " + "is" + " " + umri + " " + "years old" ;Result
Juma is 5 years oldRequesting User Input
Swap also supports the program interactive programming by requesting inputs from the user, the input request constant is dai which means "request".
eg. 4
hifadhi jina = dai("andika jina lako: ");Displaying User Input
eg. 5
hifadhi jina = dai("andika jina lako: ");
andika "Habari " + jina;Assuming after running the program the user provided it with input "Abdulbasit", then the results will be
Habari AbdulbasitConditionals like if, else, else if and switch statements are also defined and used in Swap language.
If / Else If Conditions
if is denoted by kama
else is denoted by basi
else if as basi kama
eg. 6
hifadhi umri = 20;
kama ( umri < 18 ){
hifadhi makamo = "mtoto" ;}
basi kama ( umri > 18 && umri < 50){
hifadhi makamo = "kijana" ;}
basi {
hifadhi makamo = "mzee" ;}
andika "juma ni " + makamo ;Result
juma ni kijanaExplanation: The above program is used to show from which age group does a person belong whether young, youth or an old person.
Switch Case
Also, switch case expressions are included. The switch case is only evaluated once, the value of each expression is always compared with the values of each case. If there is a match, the associated code block is run and then escapes the sequence.
Unlike loops, chagua already stops as soon as the matching kesi (or zaidi) block finishes running - there's no fall-through to the next case, so the vunja; (break;) statement is neither needed nor valid inside a chagua block; using it there will throw a parse error. vunja; is only for escaping wakati/hakika loops early.
The switch case values are presented as follows:
| Concept | Swap keyword |
|---|---|
| switch | chagua |
| case | kesi |
| default | zaidi |
eg. 7
andika "1. cct basic";
andika "2. cct ordinary";
hifadhi teule = dai("weka chaguo lako hapa: ");
wakati (teule > 0){
chagua (teule){
kesi 1 :
andika "chaguo lako ni: " + teule ;
kesi 2 :
andika "chaguo lako ni: " + teule ;
zaidi :
andika "umekosea tafadhali chagua tena";
}
hifadhi teule = dai("weka chagua lako hapa: ");
}Explanation: The following program prompts a user to choose a tv package. If a program choice is present and is matched with the associated case, the program will echo the user's input and exit, else if the input value is not matched the program will continue to loop until a right input is given or the program is manually terminated.
Increments and Decrements
Increments and decrements like i++ or i-- are not valid and would throw out a fatal error when used. To declare an increment or decrement, normal mathematical expressions are used, i.e.
hifadhi a = a + 1; // for increment
hifadhi a = a - 1; // for decrementLoops
for Loop
In Swap, the "for" loop is implemented by the hakika statement and expressed as
hakika(hifadhi a = 0; a < 10; hifadhi a = a+1){
//statement
}while Loop
In Swap, the while loop is represented by the wakati() statement and is expressed as
wakati(hali/condition){
//statement;
}eg. 8
hifadhi a = 10;
wakati(a!=0){
a = a-1;
}Result:
The program will continue to run until variable "a" decreases to 0. The vunja; keyword can be used to escape loops when necessary.
Functions
A function is a code-block that performs a certain task. In Swap, a function can be a group of a procedure which performs a certain work or can be used to return a value.
Functions in Swap can be defined as independent modules of code blocks that perform certain work.
eg. 9
The function keyword is held by njia.
njia hesabu (a, b){
rejesha a+b;
}
hifadhi x = hesabu(12,6);
andika x;or
njia hesabu (a, b){
andika a+b;
}
hesabu(12,6);or
njia hesabu (a, b){
rejesha a+b;
}
andika hesabu(12,6);Result
18In Swap there is no pre-declaration of functional prototypes as in languages like C++, therefore the use of functions have to be fully declared before they are called.
Functions as Values (Lambdas)
A njia written where a value is expected — instead of as its own top-level statement — evaluates to a callable value instead of registering a named function. This lets you store a function in a variable, pass one as an argument, or return one from another function.
eg. 9b
hifadhi ongeza = njia (a, b) {
rejesha a + b;
};
andika ongeza(2, 3);Result
5Note the trailing ; after the lambda's closing } — unlike a normal top-level njia declaration, this whole njia (...) { ... } is the right-hand side of a hifadhi ... = ...; assignment, so it needs the same statement-terminating ; any other assignment does.
A lambda can be passed as an argument and called from inside the function it's passed to:
eg. 9c
hifadhi ongezaMaraMbili = njia (a) {
rejesha a * 2;
};
njia tumia(kazi, x) {
rejesha kazi(x);
}
andika tumia(ongezaMaraMbili, 10);Result
20A lambda can also call itself recursively, and (unlike this version's named njia functions — see the note below) doing so is safe: each call gets its own private set of local variables, so a recursive lambda won't see its own parameters overwritten by a nested call to itself.
Note on scope: a lambda does not automatically capture variables from where it was defined (no lexical closures). Reaching an outer variable from inside a lambda still requires ita, exactly as with named functions (see Variable Scopes) — and resolves dynamically, against whichever scope is actually live on the call stack at the moment the lambda runs, not against the scope it was written in. A pattern like a "counter factory" that expects a returned function to remember state private to where it was created will not behave as it would in a language with true lexical closures.
Known limitation: named (non-lambda) njia functions that call themselves recursively currently share one set of local variable bindings across all of the active recursive calls, rather than each call getting its own — so a parameter or local variable read after a recursive call returns can come back with the innermost call's value instead of the current call's own value. Prefer a lambda (as above) over a named function for recursion until this is addressed.
Import
The import keyword is supplemented by the constant lete which literally means "bring". The import (lete) constant is used to import other files into the main program file.
The constant is followed by a string value which should contain the path to the imported file, and this path must be provided as a suffix to the absolute path of the needed file.
eg. 10
lete "file.sw";The command will bring a file.sw into your program.
Suppose you want to import a file from another directory:
lete "PATH/file.sw";Error Handling
Runtime errors (e.g. dividing by zero, or reading an undefined variable) normally stop the whole program. To recover from one and keep running, wrap the risky code in a jaribu ("try") block followed by a required kamata ("catch") block. kamata takes a single variable name in parentheses; if the jaribu block throws an error, that variable is bound to the error's message and the kamata block runs instead of the program crashing.
eg. 12b
jaribu {
hifadhi x = 10 / 0;
} kamata (hitilafu) {
andika "Kosa limepatikana: " + hitilafu;
}
andika "Programu inaendelea";Result
Kosa limepatikana: There's an error at line 2 near column 1 in file example.sw :
SwapArithmeticException - cannot divide by zero
Programu inaendeleavunja and rejesha used inside a jaribu/kamata block still work exactly as they do anywhere else — a vunja inside a jaribu nested in a loop still breaks that loop, and a rejesha inside a jaribu nested in a njia still returns from that function.
A bare jaribu without a kamata block is not valid — every jaribu must be followed by kamata (varname) { ... }.
Variable Scopes
A variable scope is the setting within which the variable is declared. All the inner functions (njia) have access to the variables that are from the outer function, unlike the inner functions - the outer functions do not have access to their inner functions.
eg. 11
hifadhi a=3;
njia namba(){
hifadhi a=a+10;
andika a;
}
namba();
andika a;Result:
13
3To adopt the changes of a variable in inner functions, a variable must be marked with ita, giving it the backtick-quoted variable name(s) to link to the outer scope. This will make all changes in the inner function to the outer function noticeable.
eg. 12
hifadhi a=3;
njia namba(){
ita `a`;
hifadhi a=a+10;
andika a;
}
namba();
andika a;Result:
13 13Arrays
An array is a data structure that stores multiple elements in a single variable, and in most cases these elements are all of the same types, like integer or string.
Swap supports two types of arrays:
- the one-dimensional array and
- the multi-dimensional array.
One Dimensional Array
It is also known as the linear array. All elements stored can be accessed through a single subscript which either represents a row or a column.
eg. 13
hifadhi array = ["moja","mbili"];
andika array;
andika array[0];Result:
[ 'moja', 'mbili' ]
mojaMulti-Dimensional Array
It is an array that stores data with more than one array level. A multi-dimensional array is used to store several data groups in one variable.
eg. 14
hifadhi array = [["macho","pua","mdomo","sikio"],[1, 2, 3]];
andika array;
andika array[0];
andika array[1][2];Result:
[ [ 'macho', 'pua', 'mdomo', 'sikio' ], [ 1, 2, 3 ] ]
[ 'macho', 'pua', 'mdomo', 'sikio' ]
3Elements can be added to a swap array by leaving the index empty in the last position.
eg. 15
hifadhi array = [1, 2, 3];
andika array;
hifadhi array[]=4;
andika array;Result:
[ 1, 2, 3 ]
[ 1, 2, 3, 4 ]Maps (ramani)
A ramani ("map") stores data as key/value pairs, rather than by numeric position like an array. Keys are bare identifiers (like variable or function-parameter names) — they can't be quoted strings or computed expressions.
eg. 15b
hifadhi mtu = ramani(jina: "Juma", umri: 25);
andika mtu;
andika mtu.jina;Result
{ jina: 'Juma', umri: 25 }
JumaReading a property uses . (e.g. mtu.jina). Writing to a property — whether changing an existing key or adding a new one — always needs the hifadhi keyword in front, exactly like assigning to an array element:
eg. 15c
hifadhi mtu = ramani(jina: "Juma");
hifadhi mtu.jina = "Asha";
hifadhi mtu.umri = 30;
andika mtu;Result
{ jina: 'Asha', umri: 30 }A value inside a ramani can be any type, including an array or another ramani. ramani() with no entries is a valid, empty map. Property access is single-level only in this version — to reach a value nested inside a ramani stored as another ramani's value, read the inner one into its own variable first (e.g. hifadhi anwani = mtu.anwani; andika anwani.mtaa;) rather than chaining mtu.anwani.mtaa directly.
Built-in Functions
Swap has several helper functions, the following is a list of those helper functions.
herufiKubwa
herufiKubwa is used to convert a string value of a variable into uppercase letters.
eg. 16
andika herufiKubwa("herufi");Result
HERUFIherufiNdogo
herufiNdogo is the inverse of herufiKubwa. It converts a string value of a variable into lowercase letters.
eg. 17
andika herufiNdogo("HerUFI");Result
herufikaunta
kaunta is used to count the length of an array.
eg. 18
andika kaunta([26,78,75,"mango"]);Result
4hariri
hariri constant is used to edit a part of a string or substring of a string.
eg. 19
andika hariri("wewe ni mbaya", "mbaya", "mzuri");The function hariri takes in three arguments. Assuming the parameters used by the function hariri are x, y and z, then:
- x will be the initial input
- y is the string to find and replace in the input of x
- z is the string value to replace the input of y
Result
wewe ni mzuriIt has replaced the string "mbaya" in a sentence with the string "mzuri".
tafuta
tafuta constant is used to find a substring in a string.
eg. 20
andika tafuta ("wewe ni mbaya", "mbaya");Result
kweliIf the substring does not exist in the main string then it would have returned
sikwelibadili
badili is Swap's str_replace equivalent. It replaces occurrences of a substring within a string, and supports several replacement styles depending on the arguments given: simple, multi, global ("yote"), and regex.
Simple replacement — badili(neno, kutafuta, badala) replaces the first occurrence of kutafuta found in neno with badala.
eg. 21
andika badili("wewe ni mbaya", "mbaya", "mzuri");Result
wewe ni mzuriGlobal replacement — passing "yote" (meaning "all") as a fourth argument replaces every occurrence of kutafuta instead of just the first.
eg. 22
andika badili("mbaya mbaya mbaya", "mbaya", "mzuri", "yote");Result
mzuri mzuri mzuriMulti replacement — passing arrays of equal length for kutafuta and badala replaces every occurrence of each pair throughout the string, similar to PHP's array form of str_replace.
eg. 23
andika badili("mbwa na paka", ["mbwa","paka"], ["ng'ombe","kuku"]);Result
ng'ombe na kukuNote that replacements happen sequentially, one pair at a time (like PHP's str_replace) — so swapping two values via badili(neno, ["a","b"], ["b","a"]) will not work as expected, since the first replacement's output becomes the input for the second.
Regex replacement — passing "regex" as a fourth argument treats kutafuta as a regular expression pattern. An optional fifth argument supplies regex flags (e.g. "g" for global, "i" for case-insensitive).
eg. 24
andika badili("a1 b2 c3", "[0-9]", "#", "regex", "g");Result
a# b# c#More Built-in Functions
The following helper functions are also available. See examples/12_more_helpers.sw for a runnable demo of all of them.
Math
| Function | Description | Example | Result |
|---|---|---|---|
| zungusha(namba) | round to the nearest integer | zungusha(4.5) | 5 |
| sakafu(namba) | round down (floor) | sakafu(4.9) | 4 |
| paa(namba) | round up (ceiling) | paa(4.1) | 5 |
| kamili(namba) | absolute value | kamili(-7) | 7 |
| kubwa(...namba) | largest of the given numbers | kubwa(3, 7, 2) | 7 |
| ndogo(...namba) | smallest of the given numbers | ndogo(3, 7, 2) | 2 |
| kipeo(msingi, kiwango) | power (msingi to the kiwango) | kipeo(2, 10) | 1024 |
| mzizi(namba) | square root | mzizi(9) | 3 |
Arrays
| Function | Description | Example | Result |
|---|---|---|---|
| ongeza(orodha, thamani) | push a value onto the end (mutates, returns the array) | ongeza([1,2,3], 4) | [1,2,3,4] |
| toa(orodha) | remove and return the last element (mutates) | toa([1,2,3]) | 3 |
| panga(orodha) | sort ascending, in place (numeric if all numbers, else text) | panga([10,2,33,4]) | [2,4,10,33] |
| unganisha(orodha, kitenganishi) | join elements into a string | unganisha(["moja","mbili"], "-") | "moja-mbili" |
| geuza(orodha) | reverse, in place | geuza([1,2,3]) | [3,2,1] |
| kata(orodha, mwanzo, mwisho) | slice (does not mutate); mwisho is optional | kata([1,2,3,4,5], 1, 3) | [2,3] |
Strings
| Function | Description | Example | Result |
|---|---|---|---|
| pogoa(neno) | trim leading/trailing whitespace | pogoa(" hi ") | "hi" |
| jaza(neno, urefu, tabia, mahali) | pad to a length; pads at the start unless mahali is "mwisho" | jaza("5", 3, "0") | "005" |
| gawa(neno, kitenganishi) | split into an array; splits into characters if no separator given | gawa("moja,mbili", ",") | ["moja","mbili"] |
Type / JSON
| Function | Description | Example | Result |
|---|---|---|---|
| ainaYa(thamani) | returns "namba", "neno", or "orodha" for the value's type | ainaYa(5) | "namba" |
| kwaJson(thamani) | serialize a number/string/array/ramani to a JSON string | kwaJson([1,2,3]) | "[1,2,3]" |
| kutokaJson(maandishi) | parse a JSON string into a number/string/array/ramani | kutokaJson("[1,2,3]") | [1,2,3] |
Note: kweli/sikweli (boolean) values share the same internal representation as strings, so ainaYa currently reports "neno" for a boolean value rather than a distinct boolean type name. Also, Swap string literals have no escape sequence, so a literal " can't appear inside one directly — if you need JSON text containing quotes in source code, build it with kwaJson rather than typing it by hand (see examples/15_closure_helpers.sw).
Functions that take a function (closures)
These take a njia value (see Functions as Values) as one of their arguments — either a variable holding a lambda, a named function passed by name, or an inline lambda literal.
| Function | Description | Example | Result |
|---|---|---|---|
| ramanisha(orodha, kazi) | map — build a new array from calling kazi on each element | ramanisha([1,2,3], njia(x){ rejesha x*2; }) | [2,4,6] |
| chuja(orodha, kazi) | filter — keep elements where kazi is truthy (anything but sikweli) | chuja([1,2,3,4], njia(x){ rejesha x>2; }) | [3,4] |
| punguza(orodha, kazi, awali) | reduce — fold the array to one value, starting from the required awali | punguza([1,2,3,4], njia(j,x){ rejesha j+x; }, 0) | 10 |
| kilamoja(orodha, kazi) | forEach — call kazi once per element for side effects (e.g. andika); returns the array unchanged | kilamoja([1,2], njia(x){ andika x; }) | prints 1, 2 |
