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.