
import maybe.*;
import clay.common.*;
import clay.ast as ast;
import clay.diagnostics.*;



//
// Module
//

record Module = referenceType(
    name: String,
    ast: ast.SourceAST,
    publicGlobals: SymbolTable,
    allGlobals: SymbolTable,
);

overload Module(name:String) =
    Module(name, ast.SourceAST(), SymbolTable(), SymbolTable());

private var modulesMap = HashMap[String,Module]();



//
// lookupModule, installModule
//

define lookupModule;

[S | String?(S)]
overload lookupModule(name:S) = lookupModule(String(name));

overload lookupModule(name:String) Maybe[Module] {
    var ptr = lookup(modulesMap, name);
    if (null?(ptr))
        return nothing(Module);
    return Maybe(ptr^);
}

installModule(name:String, module:Module) {
    modulesMap[name] = module;
}



//
// symbol table entries for top-level items
//

record Record = referenceType(
    ast: ast.Record,
);

record Variant = referenceType(
    ast: ast.Variant,
);

record Procedure = referenceType(
    ast: ast.Procedure,
);

record Enum = referenceType(
    ast: ast.Enum,
);

record EnumMember = referenceType(
    parent: Enum,
    index: SizeT,
);

record GlobalVariable = referenceType(
    ast: ast.GlobalVariable,
);

record GlobalAlias = referenceType(
    ast: ast.GlobalAlias,
);

record ExternalProcedure = referenceType(
    ast: ast.ExternalProcedure,
);

record ExternalVariable = referenceType(
    ast: ast.ExternalVariable,
);



//
// primitives
//

enum PrimitiveTypeConstructorKind (
    PRIM_Pointer,
    PRIM_CodePointer,
    PRIM_CCodePointer,
    PRIM_VarArgsCCodePointer,
    PRIM_StdCallCodePointer,
    PRIM_FastCallCodePointer,
    PRIM_Array,
    PRIM_Tuple,
    PRIM_Union,
    PRIM_Static,
);

enum StaticPrimitiveKind (
    PRIM_Type?,
    PRIM_CallDefined?,

    PRIM_AttributeVarArgs,
    PRIM_AttributeStdCall,
    PRIM_AttributeFastCall,
    PRIM_AttributeCDecl,

    PRIM_AttributeDLLImport,
    PRIM_AttributeDLLExport,

    PRIM_CCodePointer?,

    PRIM_TupleElementCount,

    PRIM_UnionMemberCount,

    PRIM_Record?,
    PRIM_RecordFieldCount,
    PRIM_RecordFieldName,
    PRIM_RecordWithField?,

    PRIM_Variant?,
    PRIM_VariantMemberIndex,
    PRIM_VariantMemberCount,

    PRIM_staticIntegers,

    PRIM_Enum?,
    PRIM_EnumMemberCount,

    PRIM_Identifier?,
    PRIM_IdentifierSize,
    PRIM_IdentifierConcat,
    PRIM_IdentifierSlice,
);

enum PrimitiveKind (
    PRIM_TypeSize,
    PRIM_TypeAlignment,

    PRIM_primitiveCopy,

    PRIM_boolNot,

    PRIM_numericEquals?,
    PRIM_numericLesser?,
    PRIM_numericAdd,
    PRIM_numericSubtract,
    PRIM_numericMultiply,
    PRIM_numericDivide,
    PRIM_numericNegate,

    PRIM_integerRemainder,
    PRIM_integerShiftLeft,
    PRIM_integerShiftRight,
    PRIM_integerBitwiseAnd,
    PRIM_integerBitwiseOr,
    PRIM_integerBitwiseXor,
    PRIM_integerBitwiseNot,

    PRIM_numericConvert,

    PRIM_addressOf,
    PRIM_pointerDereference,
    PRIM_pointerEquals?,
    PRIM_pointerLesser?,
    PRIM_pointerOffset,
    PRIM_pointerToInt,
    PRIM_intToPointer,

    PRIM_makeCodePointer,
    PRIM_makeCCodePointer,
    PRIM_pointerCast,

    PRIM_arrayRef,

    PRIM_tupleRef,
    PRIM_tupleElements,

    PRIM_recordFieldRef,
    PRIM_recordFieldRefByName,
    PRIM_recordFields,

    PRIM_variantRepr,

    PRIM_ModuleName,
    PRIM_StaticName,
    PRIM_staticFieldRef,

    PRIM_EnumMemberName,
    PRIM_enumToInt,
    PRIM_intToEnum,
);

record PrimitiveTypeConstructor = referenceType(
    kind: PrimitiveTypeConstructorKind
);

record StaticPrimitive = referenceType(
    kind: StaticPrimitiveKind,
);

record Primitive = referenceType(
    kind: PrimitiveKind,
);



//
// type tags
//

record BoolTag();
record IntegerTag(signed?:Bool, bits:Int);
record FloatTag(imaginary?:Bool, bits:Int);
record ComplexTag(bits:Int);
record PointerTag();
record CodePointerTag();
record RefTag();
record CCodePointerTag();
record VarArgsCCodePointerTag();
record StdCallCodePointerTag();
record FastCallCodePointerTag();
record ArrayTag();
record TupleTag();
record UnionTag();
record StaticTag();
record RecordTag(info:Record);
record VariantTag(info:Variant);
record EnumTag(info:Enum);

variant TypeTag ( BoolTag
                , IntegerTag
                , FloatTag
                , ComplexTag
                , PointerTag
                , CodePointerTag
                , RefTag
                , CCodePointerTag
                , VarArgsCCodePointerTag
                , StdCallCodePointerTag
                , FastCallCodePointerTag
                , ArrayTag
                , TupleTag
                , UnionTag
                , StaticTag
                , RecordTag
                , VariantTag
                , EnumTag
                );



//
// types
//

record Type = referenceType(
    tag: TypeTag,
    params: Vector[StaticValue],
);

overload Type(tag:TypeTag) = Type(tag, Vector[StaticValue]());

overload Type(tag:TypeTag, forward value:StaticValue) {
    var t = Type(tag);
    push(t.params, value);
    return move(t);
}

[T | VariantMember?(TypeTag, T)]
overload Type(tag:T, forward ..args) =
    Type(TypeTag(tag), ..args);



//
// primitive types
//

var boolType = Type(BoolTag());

var int8Type = Type(IntegerTag(true, 8));
var int16Type = Type(IntegerTag(true, 16));
var int32Type = Type(IntegerTag(true, 32));
var int64Type = Type(IntegerTag(true, 64));

var uint8Type = Type(IntegerTag(false, 8));
var uint16Type = Type(IntegerTag(false, 16));
var uint32Type = Type(IntegerTag(false, 32));
var uint64Type = Type(IntegerTag(false, 64));

var float32Type = Type(FloatTag(false, 32));
var float64Type = Type(FloatTag(false,64));
var float80Type = Type(FloatTag(false,80));

var imag32Type = Type(FloatTag(true, 32));
var imag64Type = Type(FloatTag(true, 64));
var imag80Type = Type(FloatTag(true, 80));

var complex32Type = Type(ComplexTag(32));
var complex64Type = Type(ComplexTag(64));
var complex80Type = Type(ComplexTag(80));



//
// ModuleHolder
//

record ModuleHolder = referenceType(
    imported: Maybe[Module],
    children: HashMap[String, ModuleHolder],
);



//
// static values
//

record BoolValue(value: Bool);
record IntValue(value: Int);
record TupleValue(elements: Vector[StaticValue]);
record IdentifierValue(value: String);

variant StaticValue ( BoolValue
                    , IntValue
                    , TupleValue
                    , IdentifierValue
                    , Procedure
                    , Record
                    , Variant
                    , PrimitiveTypeConstructor
                    , StaticPrimitive
                    , Primitive
                    , Type
                    , ModuleHolder
                    );



//
// local symbol table entries
//

record LocalVariable = referenceType(
    type: Type,
    ref?: Bool,
);

record LocalAlias = referenceType(
    expr: ast.Expr,
    symbolTable: SymbolTable,
);



//
// SymbolTable
//

variant SymbolTableEntry ( Record
                         , Variant
                         , Procedure
                         , Enum
                         , EnumMember
                         , GlobalVariable
                         , GlobalAlias
                         , ExternalProcedure
                         , ExternalVariable
                         , PrimitiveTypeConstructor
                         , StaticPrimitive
                         , Primitive
                         , Type
                         , ModuleHolder
                         , BoolValue
                         , IntValue
                         , TupleValue
                         , IdentifierValue
                         , LocalVariable
                         , LocalAlias
                         );

overload SymbolTableEntry() = SymbolTableEntry(Record());


record SymbolTable = referenceType(
    entries: HashMap[String, SymbolTableEntry],
);

newSymbolTable() = SymbolTable(HashMap[String, SymbolTableEntry]());



//
// location context for errors
//

private var currentModuleStack = Vector[Module]();

private currentModule() {
    if (empty?(currentModuleStack))
        return nothing(Module);
    return Maybe(back(currentModuleStack));
}

withCurrentModule(module:Module, f) {
    push(currentModuleStack, module);
    try {
        var x = captureValues(..f());
        pop(currentModuleStack);
        return forward ..forwardValues(x);
    }
    catch (e) {
        pop(currentModuleStack);
        throw e;
    }
}



//
// semantic errors
//

[T]
private SemanticError?(static T) = false;

private semanticError(..fields) = recordWithPredicate(
    SemanticError?,
    where: Maybe[Module],
    ..fields,
);

[T, ..A | Record?(T)
           and SemanticError?(T)
           and (Tuple[..RecordFieldTypes(T)] ==
                Tuple[Maybe[Module], ..A])]
alias overload T(..args:A) =
    T(currentModule(), ..args);

variant SemanticError;

instance ClayError (SemanticError);

[T | VariantMember?(SemanticError, T)]
overload Exception(forward e:T) =
    Exception(SemanticError(e));

overload displayError(e:SemanticError) {
    displayError(*e);
}

private displaySemanticError(err, node, ..message) {
    if (nothing?(err.where) or empty?(node.tokens)) {
        println(err);
        return;
    }
    var m = require(err.where);
    var stokens = m.ast.tokens;
    var offset = stokens.vector[node.mainToken].range.begin;

    errorWithLocation(stokens.file, offset, ..message);
}



//
// semantic error instances
//

record ModuleNotFoundError = semanticError(
    name: ast.DottedName,
);
instance SemanticError (ModuleNotFoundError);

overload displayError(e:ModuleNotFoundError) {
    displaySemanticError(
        e, e.name,
        "module not found: ",
        join(".", mapped(ident => ident.ustr, e.name.parts)),
    );
}

record GlobalRedefinitionError = semanticError(
    name: ast.Identifier,
);
instance SemanticError (GlobalRedefinitionError);

overload displayError(e:GlobalRedefinitionError) {
    displaySemanticError(
        e, e.name,
        "name redefinition: ", e.name.str,
    );
}
