Tya v0.1 Language Spec

This document is the language specification for Tya v0.1.

The canonical v0.1 execution path lexes, parses, and checks Tya source, emits C, and uses a C compiler to produce an executable.

Tya source -> lexer -> parser -> AST -> checker -> C emitter -> C compiler -> executable

The Go interpreter, current selfhost/*, ASTMODE, legacy node strings, and self-host bootstrap gates are not v0.1 specification authority. They do not need to be deleted, but they are outside the maintained v0.1 path until the v0.1 implementation is complete.

Scope

Included in v0.1:

Not included in v0.1:

Source Files

Tya source files use the .tya extension.

An entry file can contain imports and statements. An imported module file has exactly one module declaration with the same name as the file.

# main.tya
import greeting

print greeting.hello("komagata")
# greeting.tya
module greeting
  hello = name -> "Hello, {name}"

Lexical Structure

Comments start with # and continue to the end of the line.

# comment
name = "Tya"

Blocks are represented by indentation. One indentation level is 2 spaces. The lexer emits INDENT and DEDENT tokens when indentation increases or decreases.

Tokens include identifiers, integer literals, float literals, string literals, newlines, indentation tokens, and the following operators and delimiters.

= == != < <= > >= : , . + - * / % -> ( ) [ ] { }

The following identifiers have reserved meanings.

if elseif else while for in of break continue return
import module
true false nil and or not try

print, panic, exit, error, and similar names are standard built-in functions, not keywords.

Names

Names follow docs/NAMING.md.

variable / function:    snake_case
private binding:        _snake_case
module / file:          snake_case
dictionary key:         snake_case
module member:          snake_case
constant:               SCREAMING_SNAKE_CASE
type / class name:      PascalCase  # reserved in v0.1

Values

v0.1 has the following runtime values.

nil
boolean
integer
float
string
array
dictionary
function
error
module

v0.1 has no objects. If classes are introduced in the future, objects will be defined as class instances.

Strings

Strings support the following escapes.

\" \\ \n \t

{expression} inside a string is interpolated.

name = "Tya"
print "Hello, {name}"

Dictionaries

Dictionaries are key-value collections.

user = { name: "komagata", age: 20 }
empty = {}

Dictionary literal keys are snake_case identifiers in v0.1. Dictionary values are read with index access.

print user["name"] # ok
print user.name    # invalid

In v0.1, . is reserved for module member access. Member access on dictionaries is invalid.

Statements

assignment:

name = "Tya"
left, right = pair
items[0] = 10
user["name"] = "komagata"

expression statement:

print "hello"

conditional branch:

if score >= 90
  print "A"
elseif score >= 80
  print "B"
elseif score >= 70
  print "C"
else
  print "D"

An if can have zero or more elseif branches. It can have zero or one final else branch. elseif and else are placed at the same indentation as the matching if. elseif is a single reserved identifier, not else if.

while loop:

count = 0
while count < 3
  print count
  count = count + 1

array iteration:

for value in items
  print value

for value, index in items
  print "{index}: {value}"

dictionary iteration:

for key, value of user
  print "{key}: {value}"

loop control:

while true
  break

while true
  continue

return:

return
return value
return value, err

Expressions

primary expression:

identifier
integer
float
string
true
false
nil
[items]
{ name: value }
(expression)

function literal:

name -> expression
left, right -> expression

name ->
  statement

call:

fn()
fn(arg)
fn(arg1, arg2)
module.member()

In v0.1, ordinary function calls must use (). No-paren call chains such as fn arg, fn arg1, arg2, fn arg1 arg2, and print len keys user are not part of the syntax.

As an exception, print is allowed as statement-level output syntax: print expression. print accepts only the single expression that follows it.

print "hello"
print len(items)
print len(keys(user))
print add(2, 3)

Multiple arguments and nested calls are written explicitly with ().

add(2, 3)
len(keys(user))
has(user, "name")

index access:

items[0]
dictionary["name"]

module member access:

module_name.member_name

unary expression:

not expression
-expression
try expression

Binary operator precedence, from lowest to highest, is as follows.

or
and
== !=
< <= > >=
+ -
* / %
unary: not, -, try
call / member / index
primary

Truthiness

nil and false are falsey. All other values are truthy.

Functions

Functions create a child scope.

double = value -> value * 2

sum = values ->
  total = 0
  for value in values
    total = total + value
  total

If a block function has no explicit return, it returns the final expression. Functions can return multiple values.

parse_user = text ->
  if text == ""
    return nil, error("empty user")
  return { name: text }, nil

Errors

error("message") creates an error value with a message. In v0.1, . is reserved for module member access, so read the message with err["message"].

err = error("file not found")
print err["message"]

try expression can only be used inside a function. The target expression is expected to return two values: value, err. If err is truthy, the current function returns nil, err. If err is falsey, value becomes the expression value.

load_user = text ->
  user = try parse_user(text)
  user["name"]

Modules

import module_name reads module_name.tya from the same directory as the importing file.

An imported module file has exactly one module declaration with the same name as the file.

# greeting.tya
module greeting
  hello = name -> "Hello, {name}"
# main.tya
import greeting

print greeting.hello("komagata")

Module members are read with module_name.member_name. In v0.1, . is reserved for module member access.

Import aliases are not included in v0.1.

Standard Builtins

Standard built-in functions for v0.1 are defined in docs/API.md. Convenience functions that are not required in v0.1 are not included as standard builtins.

Execution

The canonical v0.1 execution path builds and runs an executable from Tya source. The user-facing CLI does not require Go commands.

Command Line

v0.1 has the following user-facing commands.

tya run file.tya [args...]
tya build file.tya -o output
tya version

tya run builds a temporary executable, runs it, and removes the temporary file after execution. It has the same role as Go's go run; it is not interpreter execution.

tya build builds an executable and leaves it at the specified output path. If -o is not specified, it removes .tya from the input file basename and writes the executable in the current directory.

tya build hello.tya
# writes ./hello

tya build examples/hello.tya
# writes ./hello

tya build examples/hello.tya -o bin/hello
# writes ./bin/hello

tya version prints the Tya version to stdout.

The Go interpreter and Go development commands are not the user-facing canonical execution path for v0.1.

v0.1 Reference Implementation

The following components are maintained as the v0.1 reference implementation.

Go lexer
Go parser
Go AST
Go checker
Go C emitter
C runtime
v0.1 specification tests

The Tya-written compiler should be newly implemented on an AST basis after the v0.1 specification works fully through the Go compile-to-C path.