Skip to content

EBNF Grammar

(* ============================================ *)
(* Endo Language Grammar - EBNF Specification  *)
(* ============================================ *)

(* ---------- Top-level ---------- *)

program         = { statement } ;

(* ---------- Statements ---------- *)

statement       = let_statement
                | type_definition
                | for_statement
                | while_statement
                | match_statement
                | try_statement
                | return_statement
                | break_statement
                | continue_statement
                | export_statement
                | import_statement
                | expression_statement
                ;

let_statement   = "let" [ "export" ] [ "rec" ] [ "mut" ] let_binding { "and" let_binding } ;
let_binding     = pattern [ type_annotation ] "=" expression
                | identifier { pattern } [ type_annotation ] "=" expression
                ;

type_definition = "type" identifier [ type_params ] "=" type_body ;
type_params     = "<" type_var { "," type_var } ">" ;
type_var        = "'" lowercase_ident ;
lowercase_ident = lowercase_letter { lowercase_letter | digit | "_" } ;
type_body       = record_type | union_type | type ;

record_type     = "{" field_def { ";" field_def } [ ";" ] "}" ;
field_def       = identifier ":" type ;

union_type      = [ "|" ] union_case { "|" union_case } ;
union_case      = identifier [ "of" union_fields ] ;
union_fields    = union_field { "*" union_field } ;
union_field     = [ identifier ":" ] type ;

for_statement   = "for" pattern "in" expression [ ";" ] "do" block "end" ;

while_statement = "while" expression [ ";" ] "do" block "end" ;

match_statement = "match" expression "with" { match_arm } ;
match_arm       = "|" pattern [ "when" expression ] "->" block_or_expr ;

try_statement   = "try" block "with" { match_arm } [ "finally" block ] ;

return_statement   = "return" [ expression ] ;
break_statement    = "break" ;
continue_statement = "continue" ;
export_statement   = "export" identifier [ "=" expression ] ;
import_statement   = "import" string_literal [ "as" identifier ]
                   | "from" string_literal "import" import_list ;
import_list        = "(" identifier { "," identifier } ")" | "*" ;

expression_statement = expression ;

block           = { statement [ ";" ] } ;
block_or_expr   = block | expression ;

(* ---------- Types ---------- *)

type            = function_type ;
function_type   = tuple_type [ "->" function_type ] ;
tuple_type      = type_application { "," type_application } ;
type_application = type_atom [ "<" type { "," type } ">" ] ;
type_atom       = "int" | "float" | "str" | "bool" | "unit"
                | "list" | "option" | "result"
                | identifier
                | "(" type ")"
                ;

type_annotation = ":" type ;

(* ---------- Expressions ---------- *)

expression      = let_in_expression ;

let_in_expression = "let" [ "use" | "manual" ] pattern "=" expression "in" expression
                  | lambda_expression
                  ;

lambda_expression = "fun" { pattern } [ ":" base_type ] "->" expression
                  | if_expression
                  ;

if_expression   = "if" expression "then" expr_sequence [ "else" expr_sequence ]
                | match_expression
                ;

expr_sequence   = expression                                          (* single expression *)
                | expression NEWLINE { expression NEWLINE } expression (* multi-expression via offside rule *)
                ;

match_expression = "match" expression "with" { match_arm }
                 | pipeline_expression
                 ;

pipeline_expression = logical_or { "|>" logical_or } ;

logical_or      = logical_and { "||" logical_and } ;
logical_and     = comparison { "&&" comparison } ;
comparison      = range { comparison_op range } ;
range           = additive [ ".." additive [ ".." additive ] ] ;
additive        = multiplicative { ( "+" | "-" ) multiplicative } ;
multiplicative  = power { ( "*" | "/" | "%" ) power } ;
power           = unary [ "**" power ] ;
unary           = [ "!" | "-" ] postfix ;
postfix         = application { postfix_op } ;
postfix_op      = "?" | "." identifier | "[" expression "]" | "?." identifier | "?|" expression ;
application     = primary { primary } ;

primary         = literal
                | identifier
                | "_"
                | "(" expression ")"
                | "(" expression "," expression { "," expression } ")"
                | list_expression
                | record_expression
                | lazy_expression
                | seq_expression
                | command_substitution
                | variable_expansion
                ;

lazy_expression = "lazy" primary ;

seq_expression  = "seq" "{" { seq_yield } "}" ;
seq_yield       = "yield" [ "!" ] expression ;

literal         = integer_literal
                | float_literal
                | size_literal
                | timespan_literal
                | string_literal
                | "true" | "false"
                | "()"
                ;

size_literal    = ( integer_literal | float_literal ) size_suffix ;
size_suffix     = "B" | "KB" | "MB" | "GB" | "TB" ;

timespan_literal = ( integer_literal | float_literal ) timespan_suffix ;
timespan_suffix  = "ms" | "s" | "min" | "h" ;

list_expression = "[" [ list_elements ] "]" ;
list_elements   = expression { ";" expression } [ ";" ]
                | expression ".." expression [ ".." expression ]
                | "for" pattern "in" expression [ "when" expression ] "->" expression
                ;

record_expression = "{" [ record_base ] field_assign { ";" field_assign } [ ";" ] "}" ;
record_base     = expression "with" ;
field_assign    = identifier [ "=" expression ] ;

command_substitution = "$(" pipeline ")" | "`" { command_char } "`" ;
variable_expansion   = "$" identifier
                     | "${" expansion_body "}"
                     | "$((" arithmetic_expr "))"
                     ;
expansion_body  = identifier [ expansion_op ] ;
expansion_op    = ":-" word | ":+" word | "#" pattern_str | "%" pattern_str
                | "/" pattern_str "/" word ;

(* ---------- Patterns ---------- *)

pattern         = or_pattern ;
or_pattern      = as_pattern { "|" as_pattern } ;
as_pattern      = cons_pattern [ "as" identifier ] ;
cons_pattern    = primary_pattern { "::" primary_pattern } ;

primary_pattern = "_"
                | literal
                | identifier
                | identifier primary_pattern
                | "(" pattern ")"
                | "(" pattern "," pattern { "," pattern } ")"
                | "[" [ list_pattern_elements ] "]"
                | "{" [ record_pattern_fields ] "}"
                ;

list_pattern_elements = pattern { ";" pattern } [ ";" pattern "..." ] ;
record_pattern_fields = field_pattern { ";" field_pattern } [ ";" "_" ] ;
field_pattern   = identifier [ "=" pattern ] ;

(* ---------- Commands and Pipelines ---------- *)

pipeline        = command { "|" command } [ "&" ] ;
command         = simple_command { redirect } ;
simple_command  = word { word } ;

redirect        = ">" word
                | ">>" word
                | "<" word
                | "2>" word
                | "2>>" word
                | "2>&1"
                | "&>" word
                | "<<<" word
                | "<<" heredoc_delimiter
                ;

word            = bare_word | string_literal | variable_expansion | glob_pattern ;

heredoc_delimiter = identifier | "'" identifier "'" ;

(* ---------- Lexical Elements ---------- *)

identifier      = ( letter | "_" ) { letter | digit | "_" } ;
bare_word       = ( letter | digit | "_" | "-" | "." | "/" | "~" )+ ;
glob_pattern    = { glob_char | "[" char_class "]" } ;
glob_char       = "*" | "?" | any_char ;
char_class      = { char_range | single_char } ;
char_range      = single_char "-" single_char ;

integer_literal = [ "-" ] digits
                | "0x" hex_digits
                | "0o" octal_digits
                | "0b" binary_digits
                ;

float_literal   = [ "-" ] digits "." digits [ exponent ] ;
exponent        = ( "e" | "E" ) [ "+" | "-" ] digits ;

string_literal  = double_string | single_string ;
double_string   = '"' { string_char | escape_seq | interpolation } '"' ;
single_string   = "'" { any_char_except_quote } "'" ;
escape_seq      = "\\" ( "n" | "t" | "r" | "\\" | '"' | "$" ) ;
interpolation   = "$" identifier
                | "${" expression "}"
                | "$(" pipeline ")"
                | "$((" arithmetic_expr "))"
                ;

comparison_op   = "==" | "!=" | "<" | "<=" | ">" | ">=" ;

letter          = "a".."z" | "A".."Z" ;
digit           = "0".."9" ;
digits          = digit { digit } ;
hex_digits      = hex_digit { hex_digit } ;
hex_digit       = digit | "a".."f" | "A".."F" ;
octal_digits    = octal_digit { octal_digit } ;
octal_digit     = "0".."7" ;
binary_digits   = binary_digit { binary_digit } ;
binary_digit    = "0" | "1" ;

(* ---------- Comments ---------- *)

comment         = "#" { any_char } newline
                | "//" { any_char } newline
                | "(*" { any_char | comment } "*)"
                ;

Appendix: Quick Reference Card

+----------------------------------------------------------------------+
|                     ENDO LANGUAGE QUICK REFERENCE                     |
+----------------------------------------------------------------------+
| BINDINGS                                                             |
|   let x = 42                    Immutable binding                    |
|   let mut x = 42                Mutable binding                      |
|   let export X = 42             Bind and export as env var           |
|   x <- x + 1                    Mutation                             |
|   let (a, b) = tuple            Destructuring                        |
+----------------------------------------------------------------------+
| FUNCTIONS                                                            |
|   let f x y = x + y             Named function (curried)             |
|   let f = fun x -> x * 2        Lambda                               |
|   _.field, _ + 1                Placeholder lambda sugar              |
|   let add5 = add 5              Partial application                  |
|   f >> g                        Forward composition                  |
+----------------------------------------------------------------------+
| TYPES                                                                |
|   int, float, str, bool, unit   Primitives                           |
|   list<T>, option<T>            Generics                             |
|   result<T, E>                  Error handling                       |
|   { name: str; age: int }       Record                               |
|   | Case1 | Case2 of T          Union                                |
+----------------------------------------------------------------------+
| LISTS                                                                |
|   [1; 2; 3]                     List literal                         |
|   [1..10]                       Range                                |
|   head :: tail                  Cons                                 |
|   list1 @ list2                 Concatenate                          |
|   [for x in xs -> x * 2]        Comprehension                        |
+----------------------------------------------------------------------+
| PATTERN MATCHING                                                     |
|   match x with                                                       |
|   | pattern -> result           Basic arm                            |
|   | p when guard -> result      With guard                           |
|   | p1 | p2 -> result           Or pattern                           |
|   | p as name -> result         As pattern                           |
+----------------------------------------------------------------------+
| PIPELINES                                                            |
|   x |> f |> g                   Function pipeline                    |
|   cmd1 | cmd2                   Shell pipeline                       |
|   cmd | lines |> map f          Mixed                                |
+----------------------------------------------------------------------+
| CONTROL FLOW                                                         |
|   if cond then a [else b]       If expression (else optional)         |
|   for x in xs do block end      For loop                             |
|   while cond do block end       While loop                           |
+----------------------------------------------------------------------+
| ERROR HANDLING                                                       |
|   let x = cmd?                  Propagate error                      |
|   Ok value, Error e             Result constructors                  |
|   Some x, None                  Option constructors                  |
|   try block with | e -> ...     Try-with                             |
+----------------------------------------------------------------------+
| LAZY EVALUATION                                                      |
|   lazy expr                     Deferred computation                 |
|   force lazyVal                 Evaluate and cache result            |
|   lazyVal |> force              Pipeline force                       |
+----------------------------------------------------------------------+
| HTTP                                                                 |
|   fetch url                       Download to file -> result<str,str>|
|   fetch url headers               Download with custom headers       |
+----------------------------------------------------------------------+
| SHELL FEATURES                                                       |
|   $VAR, ${VAR}                  Variable expansion                   |
|   ${VAR:-default}               Default value                        |
|   $(command)                    Command substitution                 |
|   > >> < 2>&1                   Redirections                         |
|   <<EOF ... EOF                 Here document                        |
|   <(cmd) >(cmd)                 Process substitution                 |
+----------------------------------------------------------------------+

This specification is a living document. As endo evolves, this document will be updated to reflect new features and refinements.


See also: Implementation Notes | Lexical Elements | Philosophy & Goals