Page 2 of 2 First 12
  • Jump to page:
    #16
  1. Nihilistic Nerd
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2017
    Posts
    29
    Rep Power
    0
    Last night, I pondering about iterators and came up with this interesting idea for automatic memory management that simplifies the implementation of RAII.

    Every subroutine is a task, and can spawn several coroutines (subtasks). But every subtask must be complete before the task itself is completed (which means that return statements act as barriers).

    From this comes the idea of using generator coroutines to construct and destruct objects:

    Code:
    dup :: (oldstr: str) -> maybe(str)... {
        newstr: str = alloc(len(oldstr) + 1);
        if newstr {
            cpy(newstr, oldstr);
            yield newstr;
            free(newstr);
        }
    }
    The interesting bit here is 'dup' owns the memory for 'newstr' entirely. It only lets the caller borrow that memory by yielding it, but then 'dup' itself frees that memory once the coroutine is resumed; which is guaranteed to happen when the caller returns. Inlining can allow for 0-overhead, and because the function is a generator, you can use it in a for loop like an iterator to bind the lifetime of an object to a lexical scope:

    Code:
    for dup("hello there") {
        if (type_of(it) != void)
            print(it);
    }
    Identical to how the 'using' statement in C# or the 'with' statement in Python works. An additional benefit to this though is that exceptions are unnecessary because functions can return composite types (eg. 'both' or 'either') which can signify a failure, and the caller isn't responsible for cleanup.


    EDIT:

    One rule I forgot to mention is that coroutines must yield a value upon every iteration. Because the second iteration frees the memory and returns nothing (and because the first iteration can fail and return nothing), the return type of 'dup' must be 'maybe(str)' instead of just 'str'.
    Last edited by Wajideus; July 17th, 2017 at 06:06 PM.
  2. #17
  3. Nihilistic Nerd
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2017
    Posts
    29
    Rep Power
    0
    Lot's of good ideas today, but I'm going to start with some code:

    Code:
    struct either($t1, $t2) {
        var as: overlay {
            var t1: t1
            var t2: t2
        }
        var is: struct {
            var t1: bool1
            var t2: bool1
        }
    }
    
    func new_sound() -> either(error, sound) {
        ...
    }
    As trivial as it seems, there's actually a lot of magic going on here.

    Firstly, the syntax of the language itself makes disambiguation between types and identifiers extremely easy, so the standard naming convention purposefully uses lowercase names for types.

    Secondly, the reason why lowercase names for types is actually used is that any argument of a function that doesn't have a type will infer it's typename to be the same as it's identifier. eg. `func getScreen(display)` would become `func getScreen(display: display)`.

    Thirdly, an optional parameter list before the braces in a struct definition is used for struct initialization.

    Fourthly, a '$' in a parameter list signifies a scoped pattern substitution (identical to using macro substitution in the C preprocessor). However, substitution is performed contextually. If `$t1` is an identifier, only identifiers called `t1` will be substituted. If `$t1` is a typename, only typenames called `t1` will be substituted. The magic works here because `either($t1, $t2)` becomes `either($t1: $t1, $t2: $t2)` before substitution is performed.

    Fifthly, the 'overlay' (short for 'overlay struct') type is the equivalent of the 'union' type in C.

    Lastly, the primitive types `bool`, `int`, `uint`, and `float` are arbitrarily sized. That is to say that the language reserves all identifiers that begin with these words followed by sequence of digits. Digits after the name (like 'int32') specify the number of bits that the type has for storage. If this matches a register size on the target machine, then the arithmetic is hardware accelerated. Otherwise, the compiler will use a larger sized register to do the arithmetic and truncate the result or it'll inline calls to a library for arbitrary precision (bignum) arithmetic.


    Another idea I had is something called a 'scatter' (short for 'scatter struct'). A scatter causes the members of the structure to scatter into separate planes when the structure is stored in an array; which can boost performance by improving cache efficiency depending on the access patterns of your data.
  4. #18
  5. Nihilistic Nerd
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2017
    Posts
    29
    Rep Power
    0
    Another few cool ideas I just had:

    Code:
    struct either($t1, $t2) {
        var as value: overlay {
            var t1: t1
            var t2: t2
        }
        var is type: enum {
            case t1
            case t2
        }
    }
    
    var err_or_snd: either(error, sound)
    if (err_or_snd is error) {
        print(err_or_snd as error)
    }
    In the above code, 'as' and 'is' are something I call 'infix labels'. This means that 'as' will get substituted with '.value.' and 'is' will get substituted with '.type.' when either of them appear after an `either($t1, $t2)` instance.

    A lone underscore is a reserved blank label that causes the entire namespace of overlay, scatter, or struct to be imported. eg.

    Code:
    struct vec2 {
        var x, y: float
    }
    
    struct vec3 {
        var _ vec2: vec2
        var z: float
    }
    In the above code, the blank infix label causes the namespace of 'vec2' to be imported into 'vec3', so that 'vec3.x' is actually 'vec3.vec2.x' and 'vec3.y' is actually 'vec3.vec2.y'.


    Additionally, enum values have all possible states as const members which a boolean values. eg.

    Code:
    enum heat {
        case hot
        case cold
    }
    
    var myheat = heat.hot               // type-inference
    
    switch (myheat) {
    case .hot:
        // this gets executed because myheat.hot is true
        break
    case .cold:
        // this doesn't get executed because myheat.cold is false
        break
    }
    So with the 'either($t1, $t2)' example, `if (err_or_snd is error)` would become `if (err_or_snd .type. error)` which would evaluate to 'if (true)' if the type is an error and 'if (false)' if the type is a sound.
  6. #19
  7. Nihilistic Nerd
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2017
    Posts
    29
    Rep Power
    0
    UFCS (Uniform Function Calling Syntax) + Closure + Variadic Argument Pattern Substitution == YES.

    Code:
    typedef input: func(get bytes: [uint8]);
    
    func bind(call: func($t1, $...) -> $t2, to object: t1) -> func(...) -> t2 {
        return func(...) -> t2 {
            return call(object, ...);
        }
    }
    
    func fileinput(file: file, get bytes: [uint8]);
    
    func read(bytes: [uint8], from input: input);
    
    
    var input = fileinput.bind(to: file);
    var bytes: [uint8](10);
    bytes.read(from: input);
    Last edited by Wajideus; August 13th, 2017 at 08:29 AM.
  8. #20
  9. Nihilistic Nerd
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2017
    Posts
    29
    Rep Power
    0
    The syntax has been becoming a lot more stable lately, so I spent some time today working on a language support plugin for vscode.

    THcJMlu.png

    The code here doesn't really do anything practical, it's mainly just a showcase of some of the features that I've decided on for certainty; like having modules, using blank infix labels in structs for polymorphism, using pattern substitution for overloading, compile-time execution and unary/binary compiler directives for metainformation, uniform function call syntax, parameter labeling, and having 'func f()' and 'struct s' be equivalent to 'const f: func()' and 'const s: struct'. There's also having '\*' mean the same thing as '*/ //', but that's just a creature-comfort for OCD people like myself.

    As far as the compiler is concerned, I'm currently working on the semantic analysis (struggling a little bit with scoping due to overloading and nested functions) and the interface for an intermediate object-file format for dumping bytecode.
    Last edited by Wajideus; August 20th, 2017 at 08:54 PM.
Page 2 of 2 First 12
  • Jump to page:

IMN logo majestic logo threadwatch logo seochat tools logo