
import maybe.*;
import clay.v0_0.lexer.*;



//
// TokenRange
//

alias TokenRange = Range[SizeT];



//
// AST nodes
//

private ast(..fields) = referenceTypeWithPredicate(
    ASTNode?,
    tokens: TokenRange,
    mainToken: SizeT,
    ..fields,
);

define ASTNode?;
overload ASTNode?(x) = false;

[A|ASTNode?(A)] astFields(x:A) = ..dropValues(static 2, ..referenceTypeFields(x));

[T, ..A | ASTNode?(T)
           and (Tuple[..ReferenceTypeFieldTypes(T)] ==
                Tuple[TokenRange, SizeT, ..A])]
alias overload T(..args:A) {
    alias tokens = TokenRange(SizeT(0), SizeT(0));
    return T(tokens, SizeT(0), ..args);
}

[T | ASTNode?(T)]
overload printReprTo(stream, x:T) {
    printTo(stream, T);
    printReprValuesTo(stream, ..astFields(x));
}

[A|ASTNode?(A)]
astTokens(node:A) = ref node.tokens;
[V|Variant?(V)]
overload astTokens(node:V) = ref astTokens(*node);


//
// VariadicList
//

record VariadicList[T] (
    items: Vector[T],
    variadicItem: Maybe[T],
);



//
// Identifier, DottedName
//

record Identifier = ast(
    str: String,
);

record DottedName = ast(
    parts: Vector[Identifier],
);

private OtherString?(x) = String?(x) and (x != String);

[S | OtherString?(S)]
overload Identifier(forward x:S) = Identifier(String(x));

[..A | allValues?(OtherString?, ..A)]
overload DottedName(forward ..a:A) =
    DottedName(Vector[Identifier](..mapValues(Identifier, ..a)));

[A | Sequence?(A) and String?(SequenceElementType(A))]
overload DottedName(a:A) =
    DottedName(Vector[Identifier](mapped(Identifier, a)));



//
// Expr
//

variant Expr ( BoolLiteral
             , IntLiteral
             , FloatLiteral
             , CharLiteral
             , StringLiteral
             , IdentifierLiteral
         
             , NameRef
             , TupleExpr
             , ArrayExpr
             , Indexing
             , Call
             , FieldRef
             , StaticIndexing
             , UnaryOp
             , BinaryOp
             , And
             , Or
         
             , IfExpr
             , LambdaExpr
             , Unpack
             , New
             , StaticExpr
             , PairExpr
             , DispatchExpr

             , FILELiteral
             , LINELiteral
             , COLUMNLiteral
             );

overload Expr() = Expr(BoolLiteral());

record BoolLiteral = ast(
    value: Bool,
);

record IntLiteral = ast(
    value: String,
    suffix: Maybe[String],
);

record FloatLiteral = ast(
    value: String,
    suffix: Maybe[String],
);

record CharLiteral = ast(
    value: String,
);

record StringLiteral = ast(
    value: String,
);

record IdentifierLiteral = ast(
    value: String,
);

record NameRef = ast(
    name: Identifier,
);

record TupleExpr = ast(
    args: Vector[Expr],
);

record ArrayExpr = ast(
    args: Vector[Expr],
);

record Indexing = ast(
    expr: Expr,
    args: Vector[Expr],
);

record Call = ast(
    expr: Expr,
    args: Vector[Expr],
);

record FieldRef = ast(
    expr: Expr,
    name: Identifier,
);

record StaticIndexing = ast(
    expr: Expr,
    index: String,
);

enum UnaryOpKind (
    DEREFERENCE, ADDRESS_OF, PLUS, MINUS, NOT,
);

record UnaryOp = ast(
    kind: UnaryOpKind,
    expr: Expr,
);

enum BinaryOpKind (
    ADD, SUBTRACT, MULTIPLY, DIVIDE, REMAINDER,
    EQUALS, NOT_EQUALS, LESSER, LESSER_EQUALS, GREATER, GREATER_EQUALS,
);

record BinaryOp = ast(
    kind:  BinaryOpKind,
    expr1: Expr,
    expr2: Expr,
);

record And = ast(
    expr1: Expr,
    expr2: Expr,
);

record Or = ast(
    expr1: Expr,
    expr2: Expr
);

record IfExpr = ast(
    condition: Expr,
    thenPart: Expr,
    elsePart: Expr,
);

record LambdaExpr = ast(
    captureByRef: Bool,
    formalArgs: VariadicList[Identifier],
    body: Statement,
);

record Unpack = ast(
    expr: Expr,
);

record New = ast(
    expr: Expr,
);

record StaticExpr = ast(
    expr: Expr,
);

record PairExpr = ast(
    name: Identifier,
    expr: Expr,
);

record DispatchExpr = ast(
    expr: Expr
);

record FILELiteral = ast();
record LINELiteral = ast();
record COLUMNLiteral = ast();



//
// Statement
//

variant Statement ( Block
                  , Label
                  , Binding
                  , Assignment
                  , InitAssignment
                  , UpdateAssignment
                  , Goto
                  , Return
                  , If
                  , Switch
                  , CaseBody
                  , ExprStatement
                  , While
                  , Break
                  , Continue
                  , For
                  , Try
                  , Throw
                  , StaticFor
                  );

overload Statement() = Statement(Block());

record Block = ast(
    statements: Vector[Statement],
);

record Label = ast(
    name: Identifier,
);

enum BindingKind ( VAR, REF, ALIAS, );

record Binding = ast(
    kind: BindingKind,
    names: Vector[Identifier],
    values: Vector[Expr],
);

record Assignment = ast(
    left: Vector[Expr],
    right: Vector[Expr],
);

record InitAssignment = ast(
    left: Vector[Expr],
    right: Vector[Expr],
);

enum UpdateOpKind (
    UPDATE_ADD, UPDATE_SUBTRACT, UPDATE_MULTIPLY, UPDATE_DIVIDE,
    UPDATE_REMAINDER,
);

record UpdateAssignment = ast(
    kind: UpdateOpKind,
    left: Expr,
    right: Expr,
);

record Goto = ast(
    labelName: Identifier,
);

enum ReturnKind (
    RETURN_VALUE, RETURN_REF, RETURN_FORWARD,
);

record Return = ast(
    kind: ReturnKind,
    exprs: Vector[Expr],
);

record If = ast(
    condition: Expr,
    thenPart: Statement,
    elsePart: Maybe[Statement],
);

record Switch = ast(
    expr: Expr,
    caseBlocks: Vector[CaseBlock],
    defaultCase: Maybe[Statement],
);

record CaseBlock = ast(
    caseLabels: Vector[Expr],
    body: Statement,
);

record CaseBody = ast(
    statements: Vector[Statement],
);

record ExprStatement = ast(
    expr: Expr,
);

record While = ast(
    condition: Expr,
    body: Statement,
);

record Break = ast(
    dummy: Int,
);

record Continue = ast(
    dummy: Int,
);

record For = ast(
    variables: Vector[Identifier],
    expr: Expr,
    body: Statement,
);

record Try = ast(
    tryBlock: Statement,
    catchBlocks: Vector[Catch],
);

record Catch = ast(
    variable: Identifier,
    type: Maybe[Expr],
    body: Statement,
);

record Throw = ast(
    expr: Expr,
);

record StaticFor = ast(
    variable: Identifier,
    values: Vector[Expr],
    body: Statement,
);



//
// Code
//

record PatternVar = ast(
    multi?: Bool,
    name: Identifier,
);

enum ArgumentKind (
    ARG_DEFAULT,
    ARG_LVALUE,
    ARG_RVALUE,
    ARG_FORWARD,
);

record NormalArgument = ast(
    kind: ArgumentKind,
    name: Identifier,
    type: Maybe[Expr],
);

record StaticArgument = ast(
    expr: Expr,
);

variant Argument ( NormalArgument
                 , StaticArgument
                 );

record ReturnSpec = ast(
    multi?: Bool,
    name: Maybe[Identifier],
    type: Expr,
);

record LLVMBody = ast(
    body: String,
);

record NoBody = ast();

variant CodeBody ( Statement
                 , LLVMBody
                 , NoBody
                 );

record Code = top(
    callByName?: Bool,
    inline?: Bool,
    patternVars: Vector[PatternVar],
    predicate: Maybe[Expr],
    args: VariadicList[Argument],
    returns: Vector[ReturnSpec],
    body: CodeBody,
);



//
// top level items
//

enum Visibility (
    PUBLIC,
    PRIVATE,
);

private top(..fields) = ast(..fields);

private named(..fields) = top(
    visibility: Maybe[Visibility],
    name: Identifier,
    ..fields,
);

record Record = named(
    params: VariadicList[Identifier],
    body: RecordBody,
);

variant RecordBody ( ComputedRecordBody
                   , DeclaredRecordBody
                   );

record ComputedRecordBody = ast(
    fields: Vector[Expr],
);

record DeclaredRecordBody = ast(
    fields: Vector[RecordField],
);

record RecordField = ast(
    name: Identifier,
    type: Expr,
);

record Variant = named(
    params: VariadicList[Identifier],
    defaultInstances: Vector[Expr],
);

record Instance = top(
    patternVars: Vector[PatternVar],
    predicate: Maybe[Expr],
    target: Expr,
    member: Expr,
);

record Procedure = named(
    code: Maybe[Code],
);

record Overload = top(
    target: Expr,
    code: Code,
);

record Enum = named(
    members: Vector[Identifier],
);

record GlobalVariable = named(
    params: VariadicList[Identifier],
    expr: Expr,
);

record GlobalAlias = named(
    params: VariadicList[Identifier],
    expr: Expr,
);

record ExternalProcedure = named(
    attributes: Vector[Expr],
    args: Vector[ExternalArg],
    hasVarArgs?: Bool,
    returnType: Maybe[Expr],
    body: Maybe[Statement],
);

record ExternalArg = ast(
    name: Identifier,
    type: Expr,
);

record ExternalVariable = named(
    attributes: Vector[Expr],
    type: Expr,
);

variant TopLevelItem ( Record
                     , Variant
                     , Instance
                     , Procedure
                     , Overload
                     , Enum
                     , GlobalVariable
                     , GlobalAlias
                     , ExternalProcedure
                     , ExternalVariable
                     , LLVMBody
                     );



//
// imports
//

record ImportModule = ast(
    visibility: Maybe[Visibility],
    name: DottedName,
    aliasName: Maybe[Identifier],
);

record ImportStar = ast(
    visibility: Maybe[Visibility],
    name: DottedName,
);

record ImportMembers = ast(
    visibility: Maybe[Visibility],
    name: DottedName,
    members: Vector[ImportedMember],
);

record ImportedMember = ast(
    name: Identifier,
    aliasName: Maybe[Identifier],
);

variant Import ( ImportModule
               , ImportStar
               , ImportMembers
               );



//
// SourceAST
//

record SourceAST = referenceType(
    tokens : SourceTokens,
    imports : Vector[Import],
    topLevelItems : Vector[TopLevelItem],
);
