arturo-lang / arturo

Simple, expressive & portable programming language for efficient scripting
http://arturo-lang.io
MIT License
713 stars 32 forks source link

[Collections\in?] add new `.key` option? #1373

Open github-actions[bot] opened 10 months ago

github-actions[bot] commented 10 months ago

[Collections\in?] add new .key option? same as with contains?

https://github.com/arturo-lang/arturo/blob/411617a1906063cf0adfd3ac06804dc4b29403a0/src/library/Collections.nim#L2638

            else:
                let s = toSeq(x.o.objectValues)
                push(newBlock(s))

    #----------------------------
    # Predicates
    #----------------------------

    # TODO(Collections\contains?) add new `.key` option?
    #  this would allow us to check whether the given dictionary contains a specific key
    #  instead of a value, which is the default way `contains?` works right now with dictionaries
    #  labels: library, enhancement, open discussion
    builtin "contains?",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check if collection contains given value",
        args        = {
            "collection": {String, Block, Range, Dictionary, Object},
            "value"     : {Any}
        },
        attrs       = {
            "at"    : ({Integer}, "check at given location within collection"),
            "deep"    : ({Logical}, "searches recursively in deep for a value.")
        },
        returns     = {Logical},
        example     = """
            arr: [1 2 3 4]

            contains? arr 5             ; => false
            contains? arr 2             ; => true
            ..........
            user: #[
                name: "John"
                surname: "Doe"
            ]

            contains? dict "John"       ; => true
            contains? dict "Paul"       ; => false

            contains? keys dict "name"  ; => true
            ..........
            contains? "hello" "x"       ; => false
            contains? "hello" `h`       ; => true
            ..........
            contains?.at:1 "hello" "el" ; => true
            contains?.at:4 "hello" `o`  ; => true
            ..........
            print contains?.at:2 ["one" "two" "three"] "two"
            ; false

            print contains?.at:1 ["one" "two" "three"] "two"
            ; true
            ..........
            print contains?.deep [1 2 4 [3 4 [5 6] 7] 8 [9 10]] 6
            ; true
            ..........
            user: #[ 
                name: "John" surname: "Doe"
                mom: #[ name: "Jane" surname: "Doe" ]
            ]

            print contains?.deep user "Jane"
            ; true
        """:
            #=======================================================
            if checkAttr("at"):
                let at = aAt.i
                case xKind:
                    of String:
                        if yKind == Regex:
                            push(newLogical(x.s.contains(y.rx, at)))
                        elif yKind == Char:
                            push(newLogical(toRunes(x.s)[at] == y.c))
                        else:
                            push(newLogical(x.s.continuesWith(y.s, at)))
                    of Block:
                        push(newLogical(x.a[at] == y))
                    of Range:
                        push(newLogical(x.rng[at] == y))
                    of Dictionary:
                        let values = toSeq(x.d.values)
                        push(newLogical(values[at] == y))
                    of Object:
                        if unlikely(x.magic.fetch(ContainsQM)):
                            pushAttr("at", aAt)
                            mgk(@[x, y]) # already pushes value
                        else:
                            let values = toSeq(x.o.values)
                            push(newLogical(values[at] == y))
                    else:
                        discard
            else:
                case xKind:
                    of String:
                        if yKind == Regex:
                            push(newLogical(x.s.contains(y.rx)))
                        elif yKind == Char:
                            push(newLogical($(y.c) in x.s))
                        else:
                            push(newLogical(y.s in x.s))
                    of Block:
                        if hadAttr("deep"):
                            push newLogical(x.a.inNestedBlock(y))
                        else:
                            push(newLogical(y in x.a))
                    of Range:
                        push(newLogical(y in x.rng))
                    of Dictionary:
                        if hadAttr("deep"):
                            let values: ValueArray = x.d.getValuesinDeep()
                            push newLogical(y in values)
                        else:
                            let values = toSeq(x.d.values)
                            push(newLogical(y in values))
                    of Object:
                        if unlikely(x.magic.fetch(ContainsQM)):
                            if hadAttr("deep"):
                                pushAttr("deep", VTRUE)

                            mgk(@[x, y]) # already pushes value
                        else:
                            if hadAttr("deep"):
                                let values: ValueArray = x.o.getValuesinDeep()
                                push newLogical(y in values)
                            else:
                                let values = toSeq(x.o.values)
                                push(newLogical(y in values))
                    else:
                        discard

    builtin "empty?",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check if given collection is empty",
        args        = {
            "collection": {String, Block, Dictionary, Null}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            empty? ""             ; => true
            empty? []             ; => true
            empty? #[]            ; => true

            empty? [1 "two" 3]    ; => false
        """:
            #=======================================================
            case xKind:
                of Null: push(VTRUE)
                of String: push(newLogical(x.s == ""))
                of Block:
                    push(newLogical(x.a.len == 0))
                of Dictionary: push(newLogical(x.d.len == 0))
                else: discard

    # TODO(Collections\in?) add new `.key` option?
    #  same as with `contains?`
    #  labels: library, enhancement, open discussion
    builtin "in?",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check if value exists in given collection",
        args        = {
            "value"     : {Any},
            "collection": {String, Block, Range, Dictionary, Object}
        },
        attrs       = {
            "at"    : ({Integer}, "check at given location within collection"),
            "deep"    : ({Logical}, "searches recursively in deep for a value.")
        },
        returns     = {Logical},
        example     = """
            arr: [1 2 3 4]

            in? 5 arr             ; => false
            in? 2 arr             ; => true
            ..........
            user: #[
                name: "John"
                surname: "Doe"
            ]

            in? "John" dict       ; => true
            in? "Paul" dict       ; => false

            in? "name" keys dict  ; => true
            ..........
            in? "x" "hello"       ; => false
            in? `h` "hello"       ; => true
            ..........
            in?.at:1 "el" "hello" ; => true
            in?.at:4 `o` "hello"  ; => true
            ..........
            print in?.at:2 "two" ["one" "two" "three"]
            ; false

            print in?.at:1 "two" ["one" "two" "three"]
            ; true
            ..........
            print in?.deep 6 [1 2 4 [3 4 [5 6] 7] 8 [9 10]]
            ; true
            ..........
            user: #[ 
                name: "John" surname: "Doe"
                mom: #[ name: "Jane" surname: "Doe" ]
            ]

            print in?.deep "Jane" user
            ; true
        """:
            #=======================================================
            if checkAttr("at"):
                let at = aAt.i
                case yKind:
                    of String:
                        if xKind == Regex:
                            push(newLogical(y.s.contains(x.rx, at)))
                        elif xKind == Char:
                            push(newLogical(toRunes(y.s)[at] == x.c))
                        else:
                            push(newLogical(y.s.continuesWith(x.s, at)))
                    of Block:
                        push(newLogical(y.a[at] == x))
                    of Range:
                        push(newLogical(y.rng[at] == x))
                    of Dictionary:
                        let values = toSeq(y.d.values)
                        push(newLogical(values[at] == x))
                    of Object:
                        if unlikely(x.magic.fetch(ContainsQM)):
                            pushAttr("at", aAt)
                            mgk(@[y, x]) # already pushes value
                        else:
                            let values = toSeq(y.o.values)
                            push(newLogical(values[at] == x))
                    else:
                        discard
            else:
                case yKind:
                    of String:
                        if xKind == Regex:
                            push(newLogical(y.s.contains(x.rx)))
                        elif xKind == Char:
                            push(newLogical($(x.c) in y.s))
                        else:
                            push(newLogical(x.s in y.s))
                    of Block:
                        if hadAttr("deep"):
                            push newLogical(y.a.inNestedBlock(x))
                        else:
                            push(newLogical(x in y.a))
                    of Range:
                        push(newLogical(x in y.rng))
                    of Dictionary:
                        if hadAttr("deep"):
                            let values: ValueArray = y.d.getValuesinDeep()
                            push newLogical(x in values)
                        else:
                            let values = toSeq(y.d.values)
                            push(newLogical(x in values))
                    of Object:
                        if unlikely(x.magic.fetch(ContainsQM)):
                            if hadAttr("deep"):
                                pushAttr("deep", VTRUE)

                            mgk(@[y, x]) # already pushes value
                        else:
                            if hadAttr("deep"):
                                let values: ValueArray = y.o.getValuesinDeep()
                                push newLogical(x in values)
                            else:
                                let values = toSeq(y.o.values)
                                push(newLogical(x in values))
                    else:
                        discard

    builtin "key?",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check if collection contains given key",
        args        = {
            "collection": {Dictionary, Object},
            "key"       : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            user: #[
                name: "John"
                surname: "Doe"
            ]

            key? user 'age            ; => false
            if key? user 'name [
                print ["Hello" user\name]
            ]
            ; Hello John
        """:
            #=======================================================
            var needle: string
            if yKind == String: needle = y.s
            else: needle = $(y)

            if xKind == Dictionary:
                push(newLogical(x.d.hasKey(needle)))
            else:
                if unlikely(x.magic.fetch(KeyQM)):
                    mgk(@[x, y]) # already pushes value
                else:
                    push(newLogical(x.o.hasKey(needle)))

    builtin "one?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check if given number or collection size is one",
        args        = {
            "number"    : {Integer,Floating,String,Block,Range,Dictionary,Null},
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            one? 5              ; => false
            one? 4-3            ; => true
            ..........
            one? 1.0            ; => true
            one? 0.0            ; => false
            ..........
            items: ["apple"]
            one? items          ; => true

            items: [1 2 3]
            one? items          ; => false
            ..........
            one? ø              ; => false
        """:
            #=======================================================
            case xKind:
                of Integer:
                    if x.iKind == BigInteger:
                        when defined(WEB):
                            push(newLogical(x.bi==big(1)))
                        elif not defined(NOGMP):
                            push(newLogical(x.bi==newInt(1)))
                    else:
                        push(newLogical(x.i == 1))
                of Floating:
                    push(newLogical(x.f == 1.0))
                of String:
                    push(newLogical(runeLen(x.s) == 1))
                of Block:
                    push(newLogical(x.a.len == 1))
                of Range:
                    push(newLogical(x.rng.len == 1))
                of Dictionary:
                    push(newLogical(x.d.len == 1))
                else:
                    push(VFALSE)

    # TODO(Collections\sorted?) doesn't work properly
    #  it should work in an identical way as `sort`
    #  labels: library, enhancement
    builtin "sorted?",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check if given collection is already sorted",
        args        = {
            "collection": {Block}
        },
        attrs       = {
            "descending": ({Logical}, "check for sorting in ascending order")
        },
        returns     = {Logical},
        example     = """
            sorted? [1 2 3 4 5]         ; => true
            sorted? [4 3 2 1 5]         ; => false
            sorted? [5 4 3 2 1]         ; => false
            ..........
            sorted?.descending [5 4 3 2 1]      ; => true
            sorted?.descending [4 3 2 1 5]      ; => false
            sorted?.descending [1 2 3 4 5]      ; => false
        """:
            #=======================================================
            var ascending = true

            if (hadAttr("descending")):
                ascending = false

            push newLogical(isSorted(x.a, ascending = ascending))

    builtin "zero?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check if given number or collection size is zero",
        args        = {
            "number"    : {Integer,Floating,String,Block,Range,Dictionary,Null},
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            zero? 5-5           ; => true
            zero? 4             ; => false
            ..........
            zero? 1.0           ; => false
            zero? 0.0           ; => true
            ..........
            items: [1 2 3]
            zero? items         ; => false    

            items: []
            zero? items         ; => true

9a0029e701d517a3eb0548f8cf65e1ff1146aa6e

stale[bot] commented 2 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.