Structure

Letlang source files SHOULD be parsed into the following Abstract Syntax Tree:

unit = CompilationUnit(path: identifier+, body: statement*)

statement =
  | Import(path: identifier+, alias?: identifier)
  | EffectDeclaration(
      is_public: bool,
      symbol_name: identifier,
      call_params: call_param*,
      return_type: typeref,
    )
  | ClassDeclaration(
      is_public: bool,
      symbol_name: identifier,
      type_params: type_param*,
      cons_param: cons_param,
      constraints?: proposition+,
    )
  | FunctionDeclaration(
      is_public: bool,
      symbol_name: identifier,
      type_params: type_param*,
      call_params: call_param*,
      return_type: typeref,
      body: proposition+,
    )
  | TailRecursiveFunctionDeclaration(
      is_public: bool,
      symbol_name: identifier,
      type_params: type_param*,
      call_params: call_param*,
      return_type: typeref,
      body: proposition+,
    )

type_param = TypeParam(param_name: identifier)
call_param = CallParam(param_name: identifier, param_type: typeref)
cons_param = ConsParam(param_name: identifier, param_type: typeref)

typeref =
  | TypeValue(val: literal)
  | TypeName(symbol: identifier+, type_params: type_param*)
  | StructDefinition(members: typeref_struct_member*)
  | TupleDefinition(members: typeref*)
  | FunctionSignature(params: typeref*, return_type: typeref)
  | OneOf(alternatives: typeref*)
  | AllOf(alternatives: typeref*)
  | Not(typeref: typeref)

typeref_struct_member = StructMemberDefinition(name: identifier, typeref: typeref)

proposition =
  | Evaluation(expr: expression)
  | Constraint(symbol_name: identifier, symbol_type: typeref, checks: expression*)

expression =
  | Symbol(path: identifier+)
  | Literal(val: literal)
  | Structure(members: expression_struct_member*)
  | Tuple(members: expression*)
  | List(items: expression*)
  | EffectCall(name: identifier+, params: expression*)
  | FunctionCall(func: expression, params: expression*)
  | SpawnCall(func: expression, params: expression*)
  | GenericResolve(val: expression, type_params: typeref+)
  | MemberAccess(lhs: expression, rhs: identifier)
  | TypeCheck(lhs: expression, rhs: typeref, negate: bool)
  | UnaryOperation(op: unary_operator, expr: expression)
  | BinaryOperation(lhs: expression, op: binary_operator, rhs: expression)
  | PatternMatch(lhs: pattern, rhs: expression)
  | TailRecFinal(val: expression)
  | TailRecRecurse(args: expression*)
  | MatchBranching(expr: expression, cases: match_branch+)
  | ConditionalBranching(cases: cond_branch*, else_case: proposition+)
  | DoBlock(
      body: proposition+,
      effect_handlers: do_effect_handler+,
      exception_handlers: do_exception_handler+,
    )
  | Receive(cases: receive_branch+, after?: receive_timeout_branch)

unary_operator = ...
binary_operator = ...

match_branch = MatchBranch(pattern: pattern, body: proposition+)
cond_branch = ConditionalBranch(cond: expression, body: proposition+)
do_effect_handler = DoEffectHandler(
  effect_name: identifier+,
  effect_params: pattern+,
  body: proposition+,
)
do_exception_handler = DoExceptionHandler(
  exception: pattern,
  body: proposition+,
)
receive_branch = ReceiveBranch(pattern: pattern, body: proposition+)
receive_timeout_branch = ReceiveTimeoutBranch(timeout: expression, body: proposition+)

pattern =
  | VariableBinding(symbol_name: identifier)
  | ValuePattern(val: expression)
  | LiteralPattern(val: literal)
  | TuplePattern(members: pattern*)
  | StructPattern(members: pattern_struct_member*)
  | ListPattern(items: pattern*)
  | ListHeadTailPattern(head: pattern, tail: pattern)

pattern_struct_member = StructMemberPattern(name: identifier, pattern: pattern)

literal =
  | BooleanLiteral(val: bool)
  | NumberLiteral(val: f64)
  | AtomLiteral(repr: string)
  | StringLiteral(val: string)

identifier = /[_a-zA-Z][_0-9a-zA-Z]*/

Semantic validation

The following nodes MUST create a new scope:

  • CompilationUnit
  • ClassDeclaration
  • FunctionDeclaration
  • TailRecursiveFunctionDeclaration
  • MatchBranch
  • ConditionalBranch
  • DoBlock
  • DoEffectHandler
  • DoExceptionHandler
  • ReceiveBranch
  • ReceiveTimeoutBranch

The following nodes MUST create a new symbol in the current scope:

  • Import
  • EffectDeclaration
  • ClassDeclaration
  • FunctionDeclaration
  • TailRecursiveFunctionDeclaration
  • TypeParam
  • CallParam
  • ConsParam
  • VariableBinding

The compiler MUST ensure that Symbol, TypeName, EffectCall and DoEffectHandler nodes point to a symbol that exists in the current scope.

The compiler MUST ensure that symbols are used in the right context:

  • symbols created by ClassDeclaration nodes MUST be used only in a TypeName node
  • symbols created by TypeParam nodes MUST be used only in a TypeName node
  • symbols created by FunctionDeclaration nodes MUST be used only in a Symbol node
  • symbols created by TailRecursiveFunctionDeclaration nodes MUST be used only in a Symbol node
  • symbols created by EffectDeclaration nodes MUST be used only in a EffectCall node or DoEffectHandler node

The compiler MUST ensure that GenericResolve nodes are only applied to symbols having at least one type parameter.

The compiler MUST ensure that the val field of a GenericResolve node is a Symbol node.

The compiler MUST ensure that every Import nodes point to an existing Letlang module.