The Nara programming language specification
This is a work-in-progress specification of the Nara programming language.
Nara is a statically typed, compiled programming language. It compiles to its own bytecode format and runs on its own virtual machine.
Scope and notation
The current narac implementation defines the token forms below. It does not
yet define a complete parser grammar or the meaning of expressions and
statements, so this document describes lexemes only. In the EBNF fragments,
uppercase names refer to grammar productions, lowercase names to lexical
productions, { ... } means zero or more repetitions, [ ... ] means
optional, and | means alternative. Quoted text is literal input. Character
ranges such as a..z are inclusive and refer to ASCII characters. In grammar
terminals, a backslash escapes the next character.
Lexemes
Identifiers, keywords, and datatypes
Identifiers start with an ASCII lowercase letter or underscore. Subsequent characters may also include uppercase letters and decimal digits. Uppercase- starting names are recognized as datatype tokens, not identifiers.
digit = "0".."9" ;
lowercase = "a".."z" ;
uppercase = "A".."Z" ;
identifier_start = lowercase | "_" ;
identifier_char = lowercase | uppercase | digit | "_" ;
identifier = identifier_start, { identifier_char } ;
named_datatype = uppercase, { identifier_char } ;
These words are reserved and each receives its own token:
var val for if else fun
true and false are boolean-literal tokens. The primitive datatype names
recognized by the lexer are:
bool u8 u64 i64 f64
Numeric literals
Integer literals may be decimal, hexadecimal, octal, or binary. A prefixed
integer must contain at least one valid digit after its prefix. Decimal
integers may be 0 or a nonzero digit followed by digits; a multi-digit
decimal integer beginning with 0 is an error.
nonzero_digit = "1".."9" ;
binary_digit = "0" | "1" ;
octal_digit = "0".."7" ;
hex_digit = digit | "a".."f" | "A".."F" ;
decimal_int = "0" | nonzero_digit, { digit } ;
hex_int = "0", ("x" | "X"), hex_digit, { hex_digit } ;
octal_int = "0", ("o" | "O"), octal_digit, { octal_digit } ;
binary_int = "0", ("b" | "B"), binary_digit, { binary_digit } ;
int_literal = decimal_int | hex_int | octal_int | binary_int ;
Floating-point literals require digits on both sides of a decimal point.
Scientific notation uses a lowercase e followed by an explicit + or -
sign and at least one digit. The mantissa may be an integer or a fractional
number. Unlike decimal integer literals, floating-point mantissas may have
leading zeroes.
decimal_digits = digit, { digit } ;
fraction = decimal_digits, ".", decimal_digits ;
exponent = "e", ("+" | "-"), decimal_digits ;
float_literal = fraction, [ exponent ]
| decimal_digits, exponent ;
A dot not followed by a digit does not start a fractional literal: for example,
1. is tokenized as the integer 1 followed by the operator .. An incomplete
prefixed number, a leading-zero decimal integer, or an incomplete exponent is
reported as a lexical error.
Strings
String literals use double quotes and must end on the same line they start. A backslash consumes the following byte as part of the string; the lexer does not restrict escapes to a predefined set or decode them. A backslash followed by LF is invalid.
quote = "\"" ;
backslash = "\\" ;
newline = "\n" ;
string_char = (any_character - (quote | newline | backslash))
| backslash, (any_character - newline) ;
string_literal = quote, { string_char }, quote ;
Comments and newlines
Comments start with // and continue up to, but do not include, LF. A carriage
return (CR) encountered in a comment is a lexical error. LF is emitted as a
newline token.
comment = "//", { any_character - ("\n" | "\r") } ;
newline = "\n" ;
Operators, delimiters, and punctuation
An operator is a maximal run of operator characters. The lexer groups these characters but does not check whether the resulting operator is supported by the language grammar.
operator_char = "+" | "-" | "=" | "*" | "!" | "/" | "|" | "@" | "#" | "$"
| "~" | "%" | "&" | "?" | "<" | ">" | "^" | "." | ":" ;
operator = operator_char, { operator_char } ;
The grouping delimiters are each separate one-character tokens:
( ) [ ] { }
The punctuation tokens currently defined are comma (,) and LF newline. The
lexer also has token kinds for integer, floating-point, string, boolean,
identifier, datatype, keyword, comment, operator, and the delimiters above.