E-Lang stands for English Language. It is a small interpreted programming language written in C that tries to read like simple English while still being useful for real beginner-sized programs.
The implementation favors clarity over cleverness:
lexer.cturns source text into tokens.parser.cturns tokens into an abstract syntax tree (AST).interpreter.cwalks the AST and runs the program.runtime.cstores values, scopes, variables, records, lists, and functions.builtins.cholds the standard built-in functions.main.cexposes the CLI, REPL, formatter, linter, token dump, AST dump, and test runner.
E-Lang is line-based, forgiving where practical, and designed to be easy to extend later.
E-Lang is being built for a specific job:
- make programming less syntax-hostile for absolute beginners
- turn pseudocode-like English into something runnable
- give non-programmers a readable scripting language
So the language is aimed at teaching, first programs, classroom exercises, and small readable scripts. It is not trying to replace C for systems programming.
- Most syntax is made of words instead of punctuation.
- Blocks use
end. - Arithmetic uses words like
plus,times, andmod. - Comparisons use phrases like
is greater than. - Lists start with
list of. - Records start with
record of.
- Keywords are case-insensitive.
- Both full-line comments and inline comments are supported.
- Error messages show file, line, source text, and a pointer when possible.
- The interpreter prefers understandable runtime checks over speed.
- Top-level variables are global.
if,repeat,while, andfor eachcreate block scopes.- Functions create their own local scope.
letcreates a variable in the current scope.setupdates an existing variable from the nearest matching scope.
main.c: CLI, REPL, formatter, lint command, test commandlexer.c/lexer.h: tokenization and source-line trackingparser.c/parser.h: AST and parserinterpreter.c/interpreter.h: execution, imports, runtime errors, tracingruntime.c/runtime.h: values, lists, records, variables, functions, scopesbuiltins.c/builtins.h: standard library functionsfiles.c/files.h: file loading and path resolutiondump.c/dump.h:--tokensand--astformatter.c/formatter.h:--formatanalyzer.c/analyzer.h:--lintsample.elang: main example programsample_lib.elang: imported helper used by the sampleTesting-elang/: extra example programs, including a guessing gametests/: small regression suiteMakefile: build helper
On Linux with gcc:
makeor:
gcc -std=c11 -Wall -Wextra -pedantic -g \
-o e-lang \
main.c lexer.c parser.c interpreter.c runtime.c builtins.c files.c dump.c formatter.c analyzer.c -lmRun a program:
./e-lang sample.elangRun with tracing:
./e-lang --trace sample.elangStart the REPL:
./e-lang --replDump tokens:
./e-lang --tokens sample.elangDump the AST:
./e-lang --ast sample.elangRun the linter:
./e-lang --lint sample.elangFormat a file to standard output:
./e-lang --format sample.elangRun tests:
./e-lang --testKeywords are case-insensitive, so LET, Let, and let all work.
Full-line comment:
note this is a comment
Inline comment:
let x be 10 # this is also a comment
Numbers:
10
3.14
.5
-2
Text:
"Hello"
"Ada"
Booleans:
true
false
Nothing:
nothing
Lists:
list
list of 1, 2, 3
list of "red", "green", "blue"
Records:
record
record of name is "Ada", age is 12
record of title is "Book", pages is 300
Create:
let age be 14
let name be "John"
let scores be list of 3, 4, 5
let person be record of name is "Ada", age is 12
Update:
set age to age plus 1
append 6 to scores
set item 1 of scores to 10
set field age of person to 13
say "Hello"
say name
say "Hello, " plus name
ask "What is your name?" and store in name
ask "How old are you?" and store in age
If the user enters something numeric, E-Lang stores it as a number. If the user enters true or false, it stores a boolean. Otherwise it stores text.
use "library.elang"
use loads another E-Lang file once, using a path relative to the current file.
Arithmetic:
5 plus 3
total minus 1
count times 2
amount divided by 4
value mod 3
2 power 5
Logic:
true and false
not done
likes_math or likes_art
Comparisons:
age is greater than 10
age is less than 18
age is equal to 14
name is not equal to ""
score is at least 60
score is at most 100
items contains 4
text contains "Ada"
person contains "name"
Grouping:
(score plus bonus) times 2
not (age is less than 13)
Function calls inside expressions:
call length with name
call append with numbers, 4
call greet with "Ada"
Natural collection and record access:
item 1 of scores
item 2 of field scores of student
field age of person
field "display name" of user
Important note: when a function call is only part of a larger expression, grouping is the clearest form:
if (field age of user) is greater than 10 then
say "older than ten"
end
if age is greater than 10 then
say "Older than ten"
end
With else:
if age is at least 18 then
say "Adult"
else
say "Not an adult yet"
end
With else if:
if score is at least 90 then
say "A"
else if score is at least 80 then
say "B"
else
say "Keep practicing"
end
Repeat:
repeat 5 times
say "Hi"
end
While:
while counter is less than 10 do
say counter
set counter to counter plus 1
end
For each:
for each score in scores
say score
end
for each works with lists, text, and records. When used on a record, it loops over the field names.
Loop control:
break
continue
Define:
define function greet
say "Hello"
end
With parameters:
define function add with left, right
return left plus right
end
Call:
call greet
let total be call add with 4, 5
say call add with 4, 5
Return:
define function square with n
return n times n
end
Common list and record work also has direct syntax now:
item 1 of list_namefield age of personappend value to list_nameset item 2 of list_name to valueremove item 2 from list_nameset field age of person to value
Assertions:
assertassert_equal
Lists:
lengthitemappendset_iteminsert_itemremove_itemslicesort
Conversions and type info:
to_numberto_texttype_of
Text:
lowercaseuppercasetrimsplitjoin
Math:
sqrtrandomrandom_betweenroundfloorceilingabsoluteminimummaximum
Files:
read_filewrite_fileappend_filefile_exists
Records:
get_fieldset_fieldhas_fieldkeys
This is the implemented shape of the language:
program -> statement*
statement -> use_statement
| let_statement
| set_statement
| append_statement
| remove_item_statement
| say_statement
| ask_statement
| if_statement
| repeat_statement
| while_statement
| for_each_statement
| function_statement
| call_statement
| return_statement
| break_statement
| continue_statement
| note_statement
use_statement -> "use" STRING
let_statement -> "let" NAME "be" expression
set_statement -> "set" NAME "to" expression
| "set" "item" expression "of" NAME "to" expression
| "set" "field" (NAME | STRING) "of" NAME "to" expression
append_statement -> "append" expression "to" NAME
remove_item_statement
-> "remove" "item" expression "from" NAME
say_statement -> "say" expression
ask_statement -> "ask" expression "and" "store" "in" NAME
if_statement -> "if" expression ["then"] block
("else" block | "else" "if" expression ["then"] block)?
"end"
repeat_statement -> "repeat" expression ("time" | "times") block "end"
while_statement -> "while" expression ["do"] block "end"
for_each_statement -> "for" "each" NAME "in" expression block "end"
function_statement -> "define" "function" NAME ["with" param_list] block "end"
call_statement -> call_expression
return_statement -> "return" ["with"] expression?
break_statement -> "break"
continue_statement -> "continue"
note_statement -> "note" ...
param_list -> NAME ("," NAME)*
block -> statement*
expression -> logic_or
logic_or -> logic_and ("or" logic_and)*
logic_and -> comparison ("and" comparison)*
comparison -> additive
( "contains" additive
| "is" "greater" "than" additive
| "is" "less" "than" additive
| "is" "equal" "to" additive
| "is" "not" "equal" "to" additive
| "is" "at" "least" additive
| "is" "at" "most" additive )*
additive -> multiplicative (("plus" | "minus") multiplicative)*
multiplicative -> power (("times" | "divided" ["by"] | "mod") power)*
power -> unary ("power" power)?
unary -> ("minus" | "not") unary | primary
primary -> NUMBER
| STRING
| "true"
| "false"
| "nothing"
| NAME
| "(" expression ")"
| call_expression
| item_expression
| field_expression
| list_expression
| record_expression
call_expression -> "call" NAME ["with" expression ("," expression)*]
item_expression -> "item" expression "of" expression
field_expression -> "field" (NAME | STRING) "of" expression
list_expression -> "list" ["of" expression ("," expression)*]
record_expression -> "record" ["of" field ("," field)*]
field -> (NAME | STRING) "is" expression
sample.elang does the following:
- Imports
sample_lib.elang. - Builds a
studentrecord with a name, a level, and a score list. - Uses natural
field ... of ...access while calling imported functions. - Uses
for eachto print every score. - Sums the scores with
set. - Uses
containsandsplit/join. - Reads input with
ask.
There is also a smaller example game in Testing-elang/guessing_game.elang.
Run it with:
./e-lang sample.elangThe current project is already a real mini-language, but obvious next steps are:
- first-class modules and exports instead of simple
use - nested update syntax for expressions beyond simple variable targets
- a stronger standard library
- better argument parsing for complex nested call expressions
- a bytecode backend for speed
- richer static analysis
- source maps for formatter/linter fixes
- package management and a standard module folder