red-langred-system

How to pass value from Red/System to Red?


I need to pass the value that I generate in Red/System to Red. I discovered docs but did not find an example of how to use it. Here is my code:

Red []

#system [   
    data!: alias struct! [
        a   [integer!]
        b   [c-string!]
    ] 

    data: declare data!

    _foo: func [return: [data!]]
    [
        data/a: 123
        data/b: "Hello"
        return data
    ]

]

sqlite: context
 [

    my-red-block: []; I want to place here: 123 "Hello"

    foo: routine [
        /local
        x [data!]
    ]
    [
        x: _foo
        ; next line do now work
        ; push my-red-block x/a
    ]
 ]

view [button "Select" [sqlite/foo]] 

my-red-block here is Red block that I want to fill with data from Red/System part.

https://github.com/meijeru/red.specs-public/blob/master/specs.adoc#routine-type


Solution

  • Intro

    Red uses data stack to pass arguments and return the result. Each value on the stack is a boxed structure 4 platform pointers in size and may contain references to external buffers; this means that you need to construct them and push them on a stack, although some primitive Red/System types (like e.g. logic! or integer!) are promoted automatically if you return them.

    In your case, however, usage of the stack is not necessary, as you want to allocate values directly in a block. Experience with low-level programming and knowledge of Red/System with Red runtime API are the essential prerequisites for this task. So let's take your example and go through it step by step.

    Unpacking

    1. You have a block and you want to append two values to it, 123 and "Hello". Suppose you want to do that from Red/System. For that, we need to write a routine.
      list: []
      foo: routine [][...]
      
    2. Inside this routine, you need to get hold of the block referenced by list word. The hard way to do that is to instantiate a symbol and look up the value in global context by its ID:

      list: []
      
      foo: routine [
          /local
              blk [red-block!]
      ][
          blk: as red-block! _context/get-global symbol/make "list"
      ]
      

      Passing list as an argument would be more reasonable, but I'll keep it as-is for educational purposes.

    3. Now we want to append 123 to this block. There's block/rs-append function that does exactly that, but it accepts a boxed argument. So we need to box 123 ourselves first.

      1. This is how boxed integer looks like; as you can see, it's simply 32-bit 123 value + slot header and padding. We can construct and initialize such structure ourselves:
        int: stack/push*         ; allocate slot on data stack
        int/header: TYPE_INTEGER ; set datatype
        int/value: 123           ; set value
        
        Fortunately, Red runtime already covers that with integer/box function that takes a Red/System integer! and returns a boxed red-integer! struct:
        integer/box 123
        
      2. Now we need to append this boxed integer to a block. Intuitively, we can check block.reds definitions and find block/rs-append that matches our requirements:
        block/rs-append blk as red-value! integer/box 123
        
        At the end of this step, we have:
      list: []
      
      foo: routine [
          /local
              blk [red-block!]
      ][
          blk: as red-block! _context/get-global symbol/make "list"
          block/rs-append blk as red-value! integer/box 123
      ]
      
    4. Now we want to append a "Hello" string, but first we need to construct it. Red strings support UTF-8 and use fixed-size internal encoding (1, 2 or 4 bytes per character, depending on the maximum codepoint size); that's a lot of details to get right manually, so the typical way of constructing such string is by converting it from c-string!.

      list: []
      
      foo: routine [
          /local
              blk [red-block!]
              str [c-string!]
      ][
          blk: as red-block! _context/get-global symbol/make "list"
          block/rs-append blk as red-value! integer/box 123
          str: "Hello"
      ]
      

      Examining string! datatype runtime definitions you will notice some handy wrappers prefixed with load; this is a convention indicating that such function can be used to construct (i.e. "load") high-level Red value from low-level Red/System parts, in our case red-string! from c-string!. Since we want to construct it at the tail of a block, we can use string/load-in:

      str: "Hello"
      string/load-in str length? str blk UTF-8
      

      Note that I use length? instead of size? to exclude NUL-terminated byte.

    Conclusion

    This is it. At the end of the day we can tidy the code a little bit and check if it works at all:

    Red [Note: "compile in release mode (-r flag)"]
    
    list: []
    
    foo: routine [
        /local
            blk [red-block!]
            int [integer!]
            str [c-string!]
    ][
        blk: as red-block! _context/get-global symbol/make "list"
        int: 123
        str: "Hello"
    
        block/rs-append blk as red-value! integer/box int
        string/load-in str length? str blk UTF-8
    ]
    
    foo
    probe list
    

    Compiling this script in release mode and executing the resulting binary from the shell gives us the expected result:

    [123 "Hello"]
    

    Needless to say, this all might look quite overwhelming to newcomers: while both Red and Red/System have decent documentation and learning resources, their bridging via runtime interaction is uncharted territory. The reason for that is because the project is evolving and the API is not yet stabilized, so, at the moment, it's not the right time to document it and cast the design decisions in stone. Experienced developers can get their bearings pretty quickly though, but that requires a solid conceptual understanding of Red's evaluation model -- these basics are what you need to master first.

    There's also a plethora of library bindings that you can learn from -- judging by the original example, you are trying to make a CRUD View interface on top of SQLite.