<commit_before>import structs/ArrayList
import ../frontend/Token
import Expression, Visitor, Type, Node, FunctionCall, OperatorDecl,
       Import, Module, FunctionCall, ClassDecl, CoverDecl, AddressOf,
       ArrayAccess, VariableAccess, Cast, NullLiteral, PropertyDecl,
       Tuple, VariableDecl, FuncType, TypeDecl, StructLiteral, TypeList,
       Scope, TemplateDef
import tinker/[Trail, Resolver, Response, Errors]

OpType: enum {
    add        /*  +  */
    sub        /*  -  */
    mul        /*  *  */
    exp        /*  ** */
    div        /*  /  */
    mod        /*  %  */
    rshift     /*  >> */
    lshift     /*  << */
    bOr        /*  |  */
    bXor       /*  ^  */
    bAnd       /*  &  */

    doubleArr  /*  => */
    ass        /*  =  */
    addAss     /*  += */
    subAss     /*  -= */
    mulAss     /*  *= */
    expAss     /*.**=.*/
    divAss     /*  /= */
    rshiftAss  /* >>= */
    lshiftAss  /* <<= */
    bOrAss     /*  |= */
    bXorAss    /*  ^= */
    bAndAss    /*  &= */

    or         /*  || */
    and        /*  && */
}

opTypeRepr := [
        "+",
        "-",
        "*",
        "**",
        "/",
        "%",
        ">>",
        "<<",
        "|",
        "^",
        "&",

        "=>",
        "=",
        "+=",
        "-=",
        "*=",
        "**=",
        "/=",
        ">>=",
        "<<=",
        "|=",
        "^=",
        "&=",

        "||",
        "&&"]

BinaryOp: class extends Expression {

    left, right: Expression
    type: OpType

    inferredType: Type
    replaced := false

    init: func ~binaryOp (=left, =right, =type, .token) {
        super(token)
    }

    clone: func -> This {
        new(left clone(), right clone(), type, token)
    }

    isAssign: func -> Bool { (type >= OpType ass) && (type <= OpType bAndAss) }

    isBooleanOp: func -> Bool { type == OpType or || type == OpType and }

    accept: func (visitor: Visitor) {
        visitor visitBinaryOp(this)
    }

    // It's just an access, it has no side-effects whatsoever
    hasSideEffects : func -> Bool { !isAssign() }

    getType: func -> Type { inferredType }

    getLeft:  func -> Expression { left  }
    getRight: func -> Expression { right }

    toString: func -> String {
        return left toString() + " " + repr() + " " + right toString()
    }

    repr: func -> String {
      opTypeRepr[type as Int - OpType add]
    }

    unwrapAssign: func (trail: Trail, res: Resolver) -> Bool {
        if(!isAssign()) return false

        innerType := type - (OpType addAss - OpType add)
        inner := BinaryOp new(left, right, innerType, token)
        right = inner
        type = OpType ass

        true
    }

    handleTuples: func (trail: Trail, res: Resolver) {
        match left {
            case t1: Tuple =>
                match right {
                    case t2: Tuple =>
                        unwrapTupleTupleAssign(t1, t2, trail, res)
                    case call: FunctionCall =>
                        unwrapTupleCallAssign(t1, call, trail, res)
                    case =>
                        message := "Invalid tuple usage: assignment rhs has incompatible type"
                        res throwError(InvalidTupleUse new(token, message))
                }
            case =>
                match right {
                    case sl: StructLiteral =>
                        // assigning struct literals is fine.
                    case t2: Tuple =>
                        message := "Invalid tuple usage: assignment lhs needs to be a tuple"
                        res throwError(InvalidTupleUse new(token, message))
                }
        }
    }

    unwrapTupleCallAssign: func (tuple: Tuple, fCall: FunctionCall, trail: Trail, res: Resolver) {
        if(fCall getRef() == null) {
            res wholeAgain(this, "Need fCall ref")
            return
        }

        if(fCall getRef() getReturnArgs() empty?()) {
            if(res fatal) {
                res throwError(TupleMismatch new(token, "Need a multi-return function call as the expression of a tuple-variable declaration."))
            }
            res wholeAgain(this, "need multi-return func call")
            return
        }

        returnArgs := fCall getReturnArgs()
        returnType := fCall getRef() getReturnType() as TypeList
        returnTypes := returnType types

        // If the tuple has fewer elements...
        if(tuple getElements() getSize() < returnTypes getSize()) {
            bad := false
            // empty is always invalid (also not parsable, probably)
            if(tuple getElements() empty?()) {
                bad = true
            } else {
                // the only case is where the last element is a VariableAccess...
                element := tuple getElements() last()
                if(!element instanceOf?(VariableAccess)) {
                    message := "Expected a variable access in a tuple-variable declaration!"
                    res throwError(InvalidTupleUse new(element token, message))
                    return
                }
                // ...that has the name '_' - which means 'soak / ignore the rest of the values'
                if(element as VariableAccess getName() != "_") {
                    bad = true
                }
            }
            if(bad) res throwError(TupleMismatch new(tuple token, "Tuple variable declaration doesn't match return type %s of function %s" format(returnType toString(), fCall getName())))
        }

        j := 0
        for(element in tuple getElements()) {
            match element {
                case vAcc: VariableAccess =>
                    argName := vAcc getName()
                    if(argName == "_") {
                        // '_' are skipped
                        returnArgs add(null)
                    } else {
                        // others are added as the call's returnArgs
                        returnArgs add(vAcc)
                    }
                case =>
                    message := "Expected a variable access in a tuple-variable declaration!"
                    res throwError(InvalidTupleUse new(element token, message))
                    return
            }
            j += 1
        }
        if (!trail addBeforeInScope(this, fCall)) {
            res throwError(CouldntAddBeforeInScope new(token, this, fCall, trail))
        }

        parent := trail peek()
        match parent {
            case s: Scope =>
                s remove(this)
                res wholeAgain(this, "replaced assignment with fCall and its returnArgs")
            case =>
                message := "Tuple / functionCall assignment in a strange place"
                res throwError(InvalidTupleUse new(token, message))
        }
    }

    unwrapTupleTupleAssign: func (t1: Tuple, t2: Tuple, trail: Trail, res: Resolver) {
        if(t1 elements getSize() != t2 elements getSize()) {
            message := "Invalid assignment (incompatible tuples) between types %s and %s\n" format(
                left getType() toString(), right getType() toString())
            res throwError(InvalidOperatorUse new(token, message))
            return
        }

        size := t1 elements getSize()

        for(i in 0..size) {
            l := t1 elements[i]
            if(!l instanceOf?(VariableAccess)) continue
            la := l as VariableAccess
<commit_msg>reduce expressions in tuple if they are not VariableAccess, fixes #4<commit_after>import structs/ArrayList
import ../frontend/Token
import Expression, Visitor, Type, Node, FunctionCall, OperatorDecl,
       Import, Module, FunctionCall, ClassDecl, CoverDecl, AddressOf,
       ArrayAccess, VariableAccess, Cast, NullLiteral, PropertyDecl,
       Tuple, VariableDecl, FuncType, TypeDecl, StructLiteral, TypeList,
       Scope, TemplateDef
import tinker/[Trail, Resolver, Response, Errors]

OpType: enum {
    add        /*  +  */
    sub        /*  -  */
    mul        /*  *  */
    exp        /*  ** */
    div        /*  /  */
    mod        /*  %  */
    rshift     /*  >> */
    lshift     /*  << */
    bOr        /*  |  */
    bXor       /*  ^  */
    bAnd       /*  &  */

    doubleArr  /*  => */
    ass        /*  =  */
    addAss     /*  += */
    subAss     /*  -= */
    mulAss     /*  *= */
    expAss     /*.**=.*/
    divAss     /*  /= */
    rshiftAss  /* >>= */
    lshiftAss  /* <<= */
    bOrAss     /*  |= */
    bXorAss    /*  ^= */
    bAndAss    /*  &= */

    or         /*  || */
    and        /*  && */
}

opTypeRepr := [
        "+",
        "-",
        "*",
        "**",
        "/",
        "%",
        ">>",
        "<<",
        "|",
        "^",
        "&",

        "=>",
        "=",
        "+=",
        "-=",
        "*=",
        "**=",
        "/=",
        ">>=",
        "<<=",
        "|=",
        "^=",
        "&=",

        "||",
        "&&"]

BinaryOp: class extends Expression {

    left, right: Expression
    type: OpType

    inferredType: Type
    replaced := false

    init: func ~binaryOp (=left, =right, =type, .token) {
        super(token)
    }

    clone: func -> This {
        new(left clone(), right clone(), type, token)
    }

    isAssign: func -> Bool { (type >= OpType ass) && (type <= OpType bAndAss) }

    isBooleanOp: func -> Bool { type == OpType or || type == OpType and }

    accept: func (visitor: Visitor) {
        visitor visitBinaryOp(this)
    }

    // It's just an access, it has no side-effects whatsoever
    hasSideEffects : func -> Bool { !isAssign() }

    getType: func -> Type { inferredType }

    getLeft:  func -> Expression { left  }
    getRight: func -> Expression { right }

    toString: func -> String {
        return left toString() + " " + repr() + " " + right toString()
    }

    repr: func -> String {
      opTypeRepr[type as Int - OpType add]
    }

    unwrapAssign: func (trail: Trail, res: Resolver) -> Bool {
        if(!isAssign()) return false

        innerType := type - (OpType addAss - OpType add)
        inner := BinaryOp new(left, right, innerType, token)
        right = inner
        type = OpType ass

        true
    }

    handleTuples: func (trail: Trail, res: Resolver) {
        match left {
            case t1: Tuple =>
                match right {
                    case t2: Tuple =>
                        unwrapTupleTupleAssign(t1, t2, trail, res)
                    case call: FunctionCall =>
                        unwrapTupleCallAssign(t1, call, trail, res)
                    case =>
                        message := "Invalid tuple usage: assignment rhs has incompatible type"
                        res throwError(InvalidTupleUse new(token, message))
                }
            case =>
                match right {
                    case sl: StructLiteral =>
                        // assigning struct literals is fine.
                    case t2: Tuple =>
                        message := "Invalid tuple usage: assignment lhs needs to be a tuple"
                        res throwError(InvalidTupleUse new(token, message))
                }
        }
    }

    unwrapTupleCallAssign: func (tuple: Tuple, fCall: FunctionCall, trail: Trail, res: Resolver) {
        if(fCall getRef() == null) {
            res wholeAgain(this, "Need fCall ref")
            return
        }

        if(fCall getRef() getReturnArgs() empty?()) {
            if(res fatal) {
                res throwError(TupleMismatch new(token, "Need a multi-return function call as the expression of a tuple-variable declaration."))
            }
            res wholeAgain(this, "need multi-return func call")
            return
        }

        returnArgs := fCall getReturnArgs()
        returnType := fCall getRef() getReturnType() as TypeList
        returnTypes := returnType types

        // If the tuple has fewer elements...
        if(tuple getElements() getSize() < returnTypes getSize()) {
            bad := false
            // empty is always invalid (also not parsable, probably)
            if(tuple getElements() empty?()) {
                bad = true
            } else {
                // the only case is where the last element is a VariableAccess...
                element := tuple getElements() last()
                if(!element instanceOf?(VariableAccess)) {
                    message := "Expected a variable access in a tuple-variable declaration!"
                    res throwError(InvalidTupleUse new(element token, message))
                    return
                }
                // ...that has the name '_' - which means 'soak / ignore the rest of the values'
                if(element as VariableAccess getName() != "_") {
                    bad = true
                }
            }
            if(bad) res throwError(TupleMismatch new(tuple token, "Tuple variable declaration doesn't match return type %s of function %s" format(returnType toString(), fCall getName())))
        }

        j := 0
        for(element in tuple getElements()) {
            match element {
                case vAcc: VariableAccess =>
                    argName := vAcc getName()
                    if(argName == "_") {
                        // '_' are skipped
                        returnArgs add(null)
                    } else {
                        // others are added as the call's returnArgs
                        returnArgs add(vAcc)
                    }
                case =>
                    message := "Expected a variable access in a tuple-variable declaration!"
                    res throwError(InvalidTupleUse new(element token, message))
                    return
            }
            j += 1
        }
        if (!trail addBeforeInScope(this, fCall)) {
            res throwError(CouldntAddBeforeInScope new(token, this, fCall, trail))
        }

        parent := trail peek()
        match parent {
            case s: Scope =>
                s remove(this)
                res wholeAgain(this, "replaced assignment with fCall and its returnArgs")
            case =>
                message := "Tuple / functionCall assignment in a strange place"
                res throwError(InvalidTupleUse new(token, message))
        }
    }

    unwrapTupleTupleAssign: func (t1: Tuple, t2: Tuple, trail: Trail, res: Resolver) {
        if(t1 elements getSize() != t2 elements getSize()) {
            message := "Invalid assignment (incompatible tuples) between types %s and %s\n" format(
                left getType() toString(), right getType() toString())
            res throwError(InvalidOperatorUse new(token, message))
            return
        }

        size := t1 elements getSize()

        /**
         * fix tuple swap such like (a,b) = (a,a+b)
         * if an expression in right side is not VariableAccess it will be reduced
         * to VariableAccess with temp variable
         */
        for(i in 0..size){
            r := t2 elements[i]
            if(!r instanceOf?(VariableAccess)){
                tmpDecl := VariableDecl new(null, generateTempName("exchange_r_in_t2"), r, r token)
                if(!trail addBeforeInScope(this, tmpDecl)) {
                    res throwError(CouldntAddBeforeInScope new(token, this, tmpDecl, trail))
                }
                t2 elements[i] = VariableAccess new(tmpDecl, tmpDecl token)
            }
        }

        for(i in 0..size) {
            l := t1 elements[i]
            if(!l instanceOf?(VariableAccess)) continue
            la := l as VariableAccess

{'fraction_non_alphanumeric': 0.11607257753184919, 'fraction_numerical': 0.002251962424398404, 'mean_word_length': 1.4400313971742542, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1662634', 'n_tokens_mistral': 4298, 'n_tokens_neox': 4076, 'n_words': 1181}