A syntax cheatsheet for Hapy, compiled from the parser/tokenizer source and the test suite. See the README for installation and CLI usage; this doc is about the language itself.
source text
-> InputStream (chars, tracks line/col, reads #!lang= and HAPY_LANG)
-> TokenStream (chars -> tokens: num / str / kw / var / op / punc)
-> token_parser.parse (tokens -> AST)
-> generate_py.make_py (AST -> Python source string)
-> exector.run2 (exec the generated Python)
Hapy has two vocabularies for every keyword/operator/builtin: Hausa (default) and English. Pick one with either:
#! lang=eng
as the very first line of a file, or by setting the HAPY_LANG environment
variable (eng for English, anything else falls back to Hausa). The CLI's
--english/-e flag sets this for you.
All examples below are in English for readability; every keyword has a Hausa equivalent (table below).
| Hausa | English | Meaning |
|---|---|---|
in |
if |
conditional |
kokuma |
elif |
else-if branch |
then |
then |
(reserved, not required in blocks) |
indai |
while |
while loop |
ma |
for |
for loop |
karo |
import |
import a module |
tsarin |
class |
class definition |
yanada |
has |
declare a class property |
gada |
inherits |
class inheritance |
anfani |
use |
forward a property to parent's init |
wuce |
pass |
no-op statement |
daga |
from |
(reserved; from X import Y is not supported) |
imbahakaba |
else |
else branch |
cikin |
in |
membership (also an operator word, see below) |
Babu |
None |
null value |
dawo |
return |
return from a function |
ayyana |
def |
function/method definition |
Gaskiya |
True |
boolean true |
Karya |
False |
boolean false |
Symbolic operators are the same in both languages: > < == != >= <= - + / * ** // % . = :
Word-form operators (translated, and each wired to a Python equivalent by
generate_py.py's word_ops table):
| Hausa | English (source word) | Compiles to |
|---|---|---|
da |
and |
and |
ko |
or |
or |
shine |
is |
= (assignment, see below) |
ba |
not |
!= |
cikin |
in |
in |
hada |
plus |
+ |
chire |
minus |
- |
times |
times |
* |
raba |
dividedby |
/ |
of,not in,is equal, andis not equalexist in the translation tables but aren't usable operators today. It's not just a missing dict entry: the tokenizer only ever reads one word at a time, so a two-word phrase likeis equalcomes out as two separate tokens (is,equal) rather than one operator token -age is equal 10currently compiles to nonsense (age = equal 10). Supporting these needs multi-word lookahead inTokenStream, a small lexer feature, not a quick dict fix.ofis additionally unclear in intent - it's been an untranslated placeholder since the very first commit, possibly meant for a possessive/genitive construct (age of person), but nothing in the codebase confirms that.
Assignment can be written with = or the word is - both produce the same
AST shape, just tagged with a different operator:
age = 20;
age is 20; # equivalent
| Hausa | English (py_-callable name) |
|---|---|
nuna |
print |
iri |
type |
tsakanin |
range |
kirga |
len |
rubuta |
input |
duka |
all |
tace |
filter |
koyar |
help |
id |
id |
lissafta |
eval |
ni |
self (inside class methods) |
__farada__ |
__startwith__ (maps to Python's __init__) |
__donnunawa__ |
__toshow__ (maps to Python's __repr__) |
Blocks are always { ... } (no Python-style indentation), and a statement
that ends in a block is itself terminated with a trailing ;.
if (20 > 10) {
print('Greater!');
} elif (10 > 20) {
print('Also checked');
} else {
print('Smaller!');
};
while (True) {
print('true!');
};
for (n in [1, 2, 3]) {
print(n);
};
The n in [1, 2, 3] header is itself a first-class "membership" expression
- the same node type you'd get from writing
n in [1,2,3]outside a loop.
nums = [1, 2, '3', [20]]; # lists can nest and mix types
empty = [];
person = {"name": "Ada", "age": 30}; # dict entries must be key: value pairs
Indexing works on both, chains, and can be an assignment target:
nums = [10, 20, 30];
first = nums[0]; # 10
nums[1] = 99; # in-place update
grid = [[1, 2], [3, 4]];
grid[1][0]; # 3, chained indexing
person = {"name": "Ada"};
person["name"]; # "Ada"
'hello'.isalpha();
. is parsed as a regular binary operator (highest precedence), so
method-chaining falls out of the normal expression grammar rather than a
special case.
def sayHello(name) {
print(name);
};
sayHello('Emmanuel');
class Woman {
has height;
has age = 22; # supports default values
def greet() {
print('hi');
}
}
class Child inherits Woman {
has height;
has age = 22;
use Woman(height); # forwards `height` to the parent's __init__
# (age is NOT duplicated as a plain self.age = age)
def __startwith__() { # -> Python's __init__
print('constructed!');
};
def __toshow__() { # -> Python's __repr__
return "Representation!";
};
}
import py_os; # a Python builtin module (the `py_` prefix is stripped)
import places; # a Hapy builtin module (see hapy/importer.py:hapy_modules)
import helpers; # a local `helpers.hapy` file in the same directory
- No
from X import Ysyntax. - No packages - local modules are single files resolved relative to the directory of the file being run.
# runs to end of line
- Error messages are not localized and are generally minimal.
- No multi-file packages; local modules are single
.hapyfiles ("bites"). of,not in,is equal,is not equalare reserved but not implemented as usable operators (see the Operators table above).- Performance has not been measured or optimized.
hapy/translations.py- the Hausa/English word tables (source of truth for the tables above).hapy/input_stream.py- raw character stream, language selection.hapy/token_stream.py- tokenizer.hapy/token_parser.py- recursive-descent parser, produces the AST.hapy/generate_py.py- AST -> Python source.hapy/importer.py- resolves builtin/local/Python module imports.tests/- the executable spec for all of the above; when in doubt about exact behavior, the tests are more precise than this document.