arturo-lang / arturo

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

[Types\define] add options for automated functions? #1409

Closed github-actions[bot] closed 9 months ago

github-actions[bot] commented 10 months ago

[Types\define] add options for automated functions? Initially, I had thought of adding a .having: option that would automatically create an init method, simply assigning all arguments to this - but this is achievable, for simple functions, through the main block. That's what we're doing sort-of with .sortable, albeit I'm not even sure I like this one, or that it's that practical. Let's see...

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

# (c) 2019-2023 Yanis Zafirópulos
#
# @file: library/Types.nim
#=======================================================

## The main Types module 
## (part of the standard library)

#=======================================
# Pragmas
#=======================================

{.used.}

#=======================================
# Libraries
#=======================================

when not defined(WEB):
    import oids

import sequtils, strutils
import sugar, tables, unicode

import helpers/conversion
import helpers/objects
import helpers/ranges

import vm/lib
import vm/[errors, exec]

#=======================================
# Definitions
#=======================================

proc defineLibrary*() =

    #----------------------------
    # Functions
    #----------------------------

    builtin "constructor",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "create a type constructor method automatically using given arguments",
        args        = {
            "arguments"     : {Literal, Block}
        },
        attrs       = NoAttrs,
        returns     = {Method},
        # TODO(Types\constructor) add documentation example
        #  labels: library, documentation, easy
        example     = """
        """:
            #=======================================================
            var args: ValueArray

            if xKind == Literal: args = @[x]
            else: args = x.a

            if (let constructorMethod = generatedConstructor(args); not constructorMethod.isNil):
                push(constructorMethod)
            else:
                # TODO(Types\constructor) should show error if the constructor cannot be generated
                #  labels: library, oop, error handling
                discard

    # TODO(Types\define) add options for automated functions?
    #  Initially, I had thought of adding a `.having:` option that would
    #  automatically create an `init` method, simply assigning all arguments
    #  to `this` - but this is achievable, for simple functions, through the main
    #  block. That's what we're doing sort-of with `.sortable`, albeit I'm not
    #  even sure I like this one, or that it's that practical. Let's see...
    #  labels: library, enhancement, open discussion
    builtin "define",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "define new type with given prototype",
        args        = {
            "type"          : {Type},
            "prototype"     : {Block, Dictionary, Type}
        },
        attrs       = NoAttrs,
        returns     = {Nothing},
        # TODO(Types\define) update documentation example
        #  to reflect changes to OOP aspects of Arturo in general
        #  and the `define` function in particular
        #  labels: library, documentation, easy
        example     = """
            define :person [name surname age][

                ; magic method to be executed
                ; after a new object has been created
                init: [
                    this\name: capitalize this\name
                ]

                ; magic method to be executed
                ; when the object is about to be printed
                print: [
                    render "NAME: |this\name|, SURNAME: |this\surname|, AGE: |this\age|"
                ]

                ; magic method to be used
                ; when comparing objects (e.g. when sorting)
                compare: [
                    if this\age = that\age -> return 0
                    if this\age < that\age -> return neg 1
                    if this\age > that\age -> return 1
                ]
            ]

            sayHello: function [this][
                ensure -> is? :person this
                print ["Hello" this\name]
            ]

            a: to :person ["John" "Doe" 35]
            b: to :person ["jane" "Doe" 33]

            print a
            ; NAME: John, SURNAME: Doe, AGE: 35
            print b
            ; NAME: Jane, SURNAME: Doe, AGE: 33

            sayHello a
            ; Hello John

            a > b
            ; => true (a\age > b\age)

            print join.with:"\n" sort @[a b]
            ; NAME: Jane, SURNAME: Doe, AGE: 33
            ; NAME: John, SURNAME: Doe, AGE: 35

            print join.with:"\n" sort.descending @[a b]
            ; NAME: John, SURNAME: Doe, AGE: 35
            ; NAME: Jane, SURNAME: Doe, AGE: 33
        """:
            #=======================================================
            var definitions: ValueDict = newOrderedTable[string,Value]()
            var inherits: Value = VNULL
            var super: ValueDict = newOrderedTable[string,Value]()

            if y.kind == Block:
                if (let constructorMethod = generatedConstructor(y.a); not constructorMethod.isNil):
                    definitions[$ConstructorM] = constructorMethod
                else:
                    for k,v in newDictionary(execDictionary(y)).d:
                        definitions[k] = v
            elif y.kind == Dictionary:
                for k,v in y.d:
                    definitions[k] = copyValue(v)
            else:
                if y.tpKind == UserType:
                    if (let yproto = getType(y.tid); not yproto.isNil):
                        inherits = yproto.inherits
                        super = yproto.super
                        for k,v in yproto.content:
                            definitions[k] = copyValue(v)
                    else:
                        RuntimeError_UsingUndefinedType(y.tid)
                else:
                    # TODO(Types\define) check if inherited type is a BuiltinType
                    #  how do we handle this?
                    #  labels: error handling, enhancement
                    discard

            # Get fields
            let fieldTable = getFieldTable(definitions)
            setType(x.tid, newPrototype(x.tid, definitions, inherits, fieldTable, super))

            # Debugging!!
            # push newDictionary({
            #     "name": newString(x.tid),
            #     "definitions": newDictionary(definitions),
            #     "inherits": inherits,
            #     "fields": newDictionary(fieldTable)
            # }.toOrderedTable)

    builtin "is",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "get derivative type with given prototype",
        args        = {
            "type"          : {Type},
            "prototype"     : {Block,Dictionary}
        },
        attrs       = NoAttrs,
        returns     = {Type},
        # TODO(Types\is) add documentation example
        #  labels: library, documentation, easy
        example     = """
        """:
            #=======================================================
            # Get our defined fields & methods
            # as a dictionary
            var definitions: ValueDict = newOrderedTable[string,Value]()
            var extra: ValueDict = newOrderedTable[string,Value]()
            var inherits: Value = VNULL

            var super = newOrderedTable[string,Value]()

            if x.tpKind == UserType:
                if (let xproto = getType(x.tid); not xproto.isNil):
                    inherits = x

                    for k,v in xproto.content:
                        if v.kind == Method:
                            super[k] = v.uninjectingThis()

                        definitions[k] = copyValue(v)
                else:
                    RuntimeError_UsingUndefinedType(x.tid)
            else:
                # DRAFT:
                # if x.t in {Integer, Floating, Rational, Complex, Quantity}:
                #     for k,v in newDictionary(execDictionary(doParse(GenerateNumericSubtype.replace("%TYPE%",":" & ($(x.t)).toLowerAscii()), isFile=false))).d:
                #         super[k] = v.uninjectingThis()
                #         definitions[k] = copyValue(v)
                # else:
                RuntimeError_UnsupportedParentType(($(x.t)).toLowerAscii())

            if y.kind == Block:
                if (let constructorMethod = generatedConstructor(y.a); not constructorMethod.isNil):
                    extra[$ConstructorM] = constructorMethod
                else:
                    for k,v in newDictionary(execDictionary(y)).d:
                        extra[k] = v
            else:
                for k,v in y.d:
                    extra[k] = copyValue(v)

            for k,v in extra:
                if v.kind == Method:
                    if (let superF = super.getOrDefault(k, nil); not superF.isNil):
                        definitions[k] = v.injectingSuper(superF)
                    else:
                        definitions[k] = copyValue(v)

                    definitions[k].injectThis()
                else:
                    definitions[k] = v

            let tmpTid = x.tid & "_" & $(genOid())
            setType(tmpTid, newPrototype("_" & x.tid, definitions, inherits, super))

            push newUserType(tmpTid)

    builtin "sortable",
        alias       = unaliased,
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "create a sort descriptor method automatically using given type field",
        args        = {
            "field"     : {Literal}
        },
        attrs       = NoAttrs,
        returns     = {Method},
        # TODO(Types\sortable) add documentation example
        #  labels: library, documentation, easy
        example     = """
        """:
            #=======================================================
            push(generatedCompare(x))

    # TODO(Types\to) revise attributes
    #  the attributes to this function seem to me a bit confusing. I mean, `to` is
    #  supposed to convert a value to a given type. Obviously, if we convert a Block
    #  value e.g. to a Color, we may need to check whether the contained values are HSL
    #  or HSV or whatever, but having 2 extra options, for one type only (that on top of
    #  it, I haven't ever used even myself - not once!), in a function that does 1 million
    #  other things, seems like not a good idea. Also, if we start sticking options here
    #  for different things (e.g. why not take all options from `dictionary` and include them
    #  all here as well?), this is going to end up being monstrous...
    #  labels: library, enhancement, cleanup, open discussion

    # TODO(Types\to) `.format` could become a distinct function?
    #  having a `format` function (in the Strings module?) would make a lot of sense
    #  actually, plus it would help us to start getting rid of things that don't belong to `to`
    #  leaving the function cleaner and much more to-the-point
    #  labels: library, enhancement, cleanup, open discussion
    builtin "to",
        alias       = unaliased,
        op          = opTo,
        rule        = PrefixPrecedence,
        description = "convert value to given type",
        args        = {
            "type"  : {Type,Block},
            "value" : {Any}
        },
        attrs       = {
            "format"    : ({String},"use given format (for dates or floating-point numbers)"),
            "unit"      : ({String,Literal},"use given unit (for quantities)"),
            "intrepid"  : ({Logical},"convert to bytecode without error-line tracking"),
            "hsl"       : ({Logical},"convert HSL block to color"),
            "hsv"       : ({Logical},"convert HSV block to color")
        },
        returns     = {Any},
        example     = """
            to :integer "2020"            ; 2020

            to :integer `A`               ; 65
            to :char 65                   ; `A`

            to :integer 4.3               ; 4
            to :floating 4                ; 4.0

            to :complex [1 2]             ; 1.0+2.0i

            ; make sure you're using the `array` (`@`) converter here, since `neg` must be evaluated first
            to :complex @[2.3 neg 4.5]    ; 2.3-4.5i

            to :rational [1 2]            ; 1/2
            to :rational @[neg 3 5]       ; -3/5

            to :boolean 0                 ; false
            to :boolean 1                 ; true
            to :boolean "true"            ; true

            to :literal "symbol"          ; 'symbol
            ..........
            to :string 2020               ; "2020"
            to :string 'symbol            ; "symbol"
            to :string :word              ; "word"

            to :string .format:"dd/MM/yy" now
            ; 22/03/21

            to :string .format:".2f" 123.12345
            ; 123.12
            ..........
            to :block "one two three"       ; [one two three]

            do to :block "print 123"        ; 123
            ..........
            to :date 0          ; => 1970-01-01T01:00:00+01:00

            print now           ; 2021-05-22T07:39:10+02:00
            to :integer now     ; => 1621661950

            to :date .format:"dd/MM/yyyy" "22/03/2021"
            ; 2021-03-22T00:00:00+01:00
            ..........
            to [:string] [1 2 3 4]
            ; ["1" "2" "3" "4"]

            to [:char] "hello"
            ; [`h` `e` `l` `l` `o`]
            ..........
            define :person [name surname age][]

            to :person ["John" "Doe" 35]
            ; [name:John surname:Doe age:35]
            ..........
            to :color [255 0 10]
            ; => #FF000A

            to :color .hsl [255 0.2 0.4]
            ; => #5C527A
        """:
            #=======================================================
            if xKind==Type:
                let tp = x.t
                push convertedValueToType(x, y, tp, popAttr("format"))
            else:
                var ret: ValueArray
                let elem {.cursor.} = x.a[0]
                requireValue(elem, {Type})
                let tp = elem.t

                if yKind==String:
                    ret = toSeq(runes(y.s)).map((c) => newChar(c))
                else:
                    let aFormat = popAttr("format")
                    if yKind == Block:
                        for item in y.a:
                            ret.add(convertedValueToType(elem, item, tp, aFormat))
                    else:
                        for item in items(y.rng):
                            ret.add(convertedValueToType(elem, item, tp, aFormat))

                push newBlock(ret)

    builtin "type",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "get type of given value",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Type},
        example     = """
            print type 18966          ; :integer
            print type "hello world"  ; :string
        """:
            #=======================================================
            if xKind != Object:
                push(newType(xKind))
            else:
                push(newUserType(x.proto.name))

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

    builtin "attribute?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :attribute",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            attribute? first [.something x]
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Attribute))

    builtin "attributeLabel?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :attributeLabel",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            attributeLabel? first [.something: x]
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==AttributeLabel))

    builtin "binary?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :binary",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            binary? to :binary "string"
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Binary))

    builtin "block?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :block",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print block? [1 2 3]            ; true
            print block? #[name: "John"]    ; false
            print block? "hello"            ; false
            print block? 123                ; false
        """:
            #=======================================================
            push(newLogical(xKind==Block))

    builtin "bytecode?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :bytecode",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            code: [print 1 + 2]
            bcode: to :bytecode code

            print bytecode? bcode      ; true
            print bytecode? code       ; false
        """:
            #=======================================================
            push(newLogical(xKind==Bytecode))

    builtin "char?",
        alias       = unaliased,
        op          = opNop, 
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :char",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print char? `a`         ; true
            print char? 123         ; false
        """:
            #=======================================================
            push(newLogical(xKind==Char))

    builtin "color?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :color",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print color? #FF0000        ; true
            print color? #green         ; true

            print color? 123            ; false
        """:
            #=======================================================
            push(newLogical(xKind==Color))

    builtin "complex?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :complex",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            c: to :complex [1 2]
            print complex? c            ; true

            print complex? 123          ; false
        """:
            #=======================================================
            push(newLogical(xKind==Complex))

    builtin "database?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :database",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            database? open "my.db"
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Database))

    builtin "date?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :date",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print date? now             ; true
            print date? "hello"         ; false
        """:
            #=======================================================
            push(newLogical(xKind==Date))

    builtin "defined?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given type is defined",
        args        = {
            "type" : {Type, String, Literal, Word}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        # TODO(Types\defined?) add documentation example
        #  labels: library, documentation, easy
        example     = """
        """:
            #=======================================================
            if xKind == Type:
                if x.tpKind == BuiltinType:
                    push(VTRUE)
                else:
                    push(newLogical(not getType(x.tid).isNil))
            else:
                try:
                    discard parseEnum[ValueKind](x.s.capitalizeAscii())
                    push(VTRUE)
                except:
                    let tp = getType(x.s, safe=true)
                    push(newLogical(not (tp.isNil or tp == NoPrototypeFound)))

    builtin "dictionary?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :dictionary",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print dictionary? #[name: "John"]   ; true
            print dictionary? 123               ; false
        """:
            #=======================================================
            push(newLogical(xKind==Dictionary))

    builtin "error?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :error",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            error? try -> throw "Some Error"
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Error))

    builtin "errorKind?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :errorKind",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            errorKind? to :errorKind "Some error kind"
            ; => true
            errorKind? genericError
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Errorkind))

    builtin "inline?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :inline",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            inline? first [(something) x]
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Inline))

    builtin "integer?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :integer",
        args        = {
            "value" : {Any}
        },
        attrs       = {
            "big"   : ({Logical},"check if, internally, it's a bignum")
        },
        returns     = {Logical},
        example     = """
            print integer? 123                  ; true
            print integer? "hello"              ; false
            ..........
            integer?.big 123                    ; => false
            integer?.big 12345678901234567890   ; => true
        """:
            #=======================================================
            if (hadAttr("big")):
                push(newLogical(xKind==Integer and x.iKind==BigInteger))
            else:
                push(newLogical(xKind==Integer))

    # TODO(Types\is?) should add `.strict` option for Object values?
    #  in that case, it would return true only if the object's type
    #  is the given one. If it inherits the given type - including
    #  something like `is? :object someObj` - would return false
    #  labels: library, enhancement, open discussion
    builtin "is?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check whether value is of given type",
        args        = {
            "type"  : {Type,Block},
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            is? :string "hello"       ; => true
            is? :block [1 2 3]        ; => true
            is? :integer "boom"       ; => false

            is? [:string] ["one" "two"]     ; => true
            is? [:integer] [1 "two]         ; => false
        """:
            #=======================================================
            if yKind != Object:
                if xKind == Type:
                    if x.t == Any:
                        push(VTRUE)
                    else:
                        push(newLogical(x.t == yKind))
                else:
                    let elem {.cursor.} = x.a[0]
                    requireValue(elem, {Type})

                    let tp = elem.t
                    var res = true
                    if tp != Any:
                        if yKind != Block: 
                            res = false
                        else:
                            if y.a.len==0: 
                                res = false
                            else:
                                for item in y.a:
                                    if tp != item.kind:
                                        res = false
                                        break
                    push newLogical(res)
            else:
                if x.t in {Object,Any} and x.tpKind == BuiltinType:
                    push(VTRUE)
                else:
                    if x.tpKind == BuiltinType:
                        push(VFALSE)
                    else:
                        let givenPrototype = getType(x.tid, safe=true)
                        if givenPrototype == NoPrototypeFound:
                            push(VFALSE)
                        else:
                            var found = false
                            var currentPrototype = y.proto
                            while true:
                                # TODO(Types\is?) better inheritance identification needed
                                #  right now, we're merely comparing the names of the prototypes
                                #  but what if the prototype has been redefined in the meantime?
                                #  we actually have to implement a proper `==` overload for 
                                #  Prototype values!
                                #  labels: bug, values
                                if currentPrototype.name == givenPrototype.name:
                                    found = true
                                    break

                                if y.proto.inherits == VNULL: break
                                if (let newProto = getType(y.proto.inherits.tid, safe=true); newProto != NoPrototypeFound):
                                    currentPrototype = newProto
                                else:
                                    break

                            push(newLogical(found))

    builtin "floating?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :floating",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print floating? 3.14        ; true
            print floating? 123         ; false
            print floating? "hello"     ; false
        """:
            #=======================================================
            push(newLogical(xKind==Floating))

    builtin "function?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :function",
        args        = {
            "value" : {Any}
        },
        attrs       = {
            "builtin"   : ({Logical},"check if, internally, it's a built-in")
        },
        returns     = {Logical},
        example     = """
            print function? $[x][2*x]       ; true
            print function? var 'print      ; true
            print function? "print"         ; false
            print function? 123             ; false
            ..........
            f: function [x][x+2]

            function? var'f                 ; => true
            function? var'print             ; => true
            function?.builtin var'f         ; => false
            function?.builtin var'print     ; => true
        """:
            #=======================================================
            if (hadAttr("builtin")):
                push(newLogical(xKind==Function and x.fnKind==BuiltinFunction))
            else:
                push(newLogical(xKind==Function))

    builtin "label?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :label",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            label? first [something: x]
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Label))

    builtin "literal?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :literal",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print literal? 'x           ; true
            print literal? "x"          ; false
            print literal? 123          ; false
        """:
            #=======================================================
            push(newLogical(xKind==Literal))

    builtin "logical?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :logical",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print logical? true         ; true
            print logical? false        ; true
            print logical? maybe        ; true
            ..........
            print logical? 1=1          ; true
            print logical? 123          ; false
        """:
            #=======================================================
            push(newLogical(xKind==Logical))

    builtin "method?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :method",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        # TODO(Types\method?) add documentation example
        #  labels: library, documentation, easy
        example     = """
        """:
            #=======================================================
            push(newLogical(xKind == Method))

    builtin "null?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :null",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print null? null            ; true
            print null? ø               ; true

            print null? 123             ; false
        """:
            #=======================================================
            push(newLogical(xKind==Null))

    builtin "object?",
        alias       = unaliased, 
        op          = opNop, 
        rule        = PrefixPrecedence,
        description = "checks if given value is a custom-type object",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            define :person [name,surname][]

            x: to :person ["John","Doe"]

            print object? x             ; true
            print object? "hello"       ; false
        """:
            #=======================================================
            push(newLogical(xKind==Object))

    builtin "path?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :path",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            path? first [a\b\c x]
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Path))

    builtin "pathLabel?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :pathLabel",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            pathLabel? first [a\b\c: x]
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==PathLabel))

    builtin "pathLiteral?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :pathLiteral",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            pathLiteral? 'a\b\c
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==PathLiteral))

    builtin "quantity?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :quantity",
        args        = {
            "value" : {Any}
        },
        attrs       = {
            "big"   : ({Logical},"check if, internally, it's a bignum")
        },
        returns     = {Logical},
        example     = """
            print quantity? 1:m         ; true
            print quantity? 2:yd2       ; true    

            print quantity? 3           ; false 
        """:
            #=======================================================
            if (hadAttr("big")):
                push(newLogical(xKind==Quantity and x.q.original.rKind==BigRational))
            else:
                push(newLogical(xKind==Quantity))

    builtin "range?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :range",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            r: 1..3                     ; r: [1 2 3]

            print range? r              ; true
            print range? [1 2 3]        ; false
        """:
            #=======================================================
            push(newLogical(xKind==Range))

    builtin "rational?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :rational",
        args        = {
            "value" : {Any}
        },
        attrs       = {
            "big"   : ({Logical},"check if, internally, it's a bignum")
        },
        returns     = {Logical},
        example     = """
            r: to :rational 3.14        ; r: 157/50

            print rational? r           ; true
            print rational? 3.14        ; false
        """:
            #=======================================================
            if (hadAttr("big")):
                push(newLogical(xKind==Rational and x.rat.rKind==BigRational))
            else:
                push(newLogical(xKind==Rational))

    builtin "regex?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :regex",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print regex? {/[a-z]+/}     ; true
            print regex? "[a-z]+"       ; false
            print regex? 123            ; false
        """:
            #=======================================================
            push(newLogical(xKind==Regex))

    builtin "set?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "check if given variable is defined",
        args        = {
            "symbol"    : {String,Literal}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            boom: 12
            print set? 'boom          ; true

            print set? 'zoom          ; false
        """:
            #=======================================================
            push(newLogical(SymExists(x.s)))

    builtin "socket?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :socket",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            server: listen 18966
            socket? server
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Socket))

    builtin "store?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :store",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            store? config
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Store))

    builtin "string?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :string",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print string? "x"           ; true
            print string? 'x            ; false
            print string? 123           ; false
        """:
            #=======================================================
            push(newLogical(xKind==String))

    builtin "symbol?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :symbol",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            symbol? first [+ x]
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Symbol))

    builtin "symbolLiteral?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :symbolLiteral",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            symbolLiteral? '++
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==SymbolLiteral))

    builtin "type?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :type",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print type? :string         ; true
            print type? "string"        ; false
            print type? 123             ; false
        """:
            #=======================================================
            push(newLogical(xKind==Type))

    builtin "unit?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :unit",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            unit? `m
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Unit))

    builtin "version?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :version",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            print version? 1.0.2        ; true
            print version? "1.0.2"      ; false
        """:
            #=======================================================
            push(newLogical(xKind==Version))

    builtin "word?",
        alias       = unaliased, 
        op          = opNop,
        rule        = PrefixPrecedence,
        description = "checks if given value is of type :word",
        args        = {
            "value" : {Any}
        },
        attrs       = NoAttrs,
        returns     = {Logical},
        example     = """
            word? first [something x]
            ; => true
        """:
            #=======================================================
            push(newLogical(xKind==Word))

#=======================================
# Add Library
#=======================================

Libraries.add(defineLibrary)
 No newline at end of file
ndex ee3b1567fd..8fe9b44c2b 100644
++ b/src/library/Ui.nim

ea55935f908bba08047340f5e2bb0cbf3bc38327

github-actions[bot] commented 9 months ago

Closed in 564d547378d3897c1853e5466dd1b202f583245a