30093bc412cc — Leonard Ritter 26 days ago
* native I/O support removes overhead between main loop and framework
* `do` supports simpler `fn` syntax
4 files changed, 343 insertions(+), 231 deletions(-)

M lib/tukan/uvm.sc
M testing/test_compiler.tuk
M testing/test_screen.tuk
M testing/tuk_interpreter.sc
M lib/tukan/uvm.sc +23 -1
@@ 1392,6 1392,10 @@ global tok_wildcard : Atom =
     Atom '*
 global tok_let : Atom =
     Atom 'let
+global tok_fn : Atom =
+    Atom 'fn
+global tok_macro : Atom =
+    Atom 'macro
 global tok_= : Atom =
     Atom '=
 global tok_do : Atom =

          
@@ 1471,7 1475,8 @@ struct CachedEval
 
     fn... eval (self, env : Atom, expr : Atom)
         returning (uniqueof Atom -1)
-        #'uncached_eval self env expr
+        'uncached_eval self env expr
+    #
         let key = (tupleof (view env) (view expr))
         try
             let result = (copy ('get self.cache key))

          
@@ 1526,6 1531,23 @@ struct CachedEval
                             env =
                                 'set env (copy k)
                                     'eval-do self (copy env) triplet 2
+                        elseif (((tok == tok_fn) or (tok == tok_macro))
+                            and (('kind ('get-index triplet 1)) == Atom.Kind.Symbol))
+                            let k = ('get-index triplet 1)
+                            local newexpr = (Cell.new (copy tok))
+                            call
+                                Cell.gen-each-index
+                                    inline (index node newexpr)
+                                        if (index >= 2)
+                                            newexpr =
+                                                'set newexpr (index - 1) (copy node)
+                                \ triplet newexpr
+                            #report "eval" ('tostring (Atom (copy newexpr)))
+                            #if true
+                                exit 0
+                            env =
+                                'set env (copy k)
+                                    'eval self (copy env) newexpr
                         elseif (tok == tok_=)
                             let key = ('get-index triplet 1)
                             let outv = ('eval-do self (copy env) triplet 2)

          
M testing/test_compiler.tuk +44 -42
@@ 2,6 2,19 @@ 
 let source
     quote
         do
+            fn clamp (x mn mx)
+                cond (< x mn) mn
+                    cond (> x mx) mx x
+            fn abs (x)
+                cond (< x 0) (- 0 x) x
+            fn rgba (r g b a)
+                tou32
+                    |
+                        |
+                            | (floor (* (clamp r 0 1) 255))
+                                * (floor (* (clamp g 0 1) 255)) 256
+                            * (floor (* (clamp b 0 1) 255)) 65536
+                        * (floor (* (clamp a 0 1) 255)) 16777216
             = title
                 .. "test_screen "
                     ..

          
@@ 10,51 23,42 @@ let source
                             "x"
                         totext (get (get io 'screen-size) 1)
             = screen
+                let sz
+                    get io 'screen-size
                 let w
-                    // (get (get io 'screen-size) 0) 4
+                    // (get sz 0) 4
                 let h
-                    // (get (get io 'screen-size) 1) 4
-                let clamp
-                    fn (x mn mx)
-                        cond (< x mn) mn
-                            cond (> x mx) mx x
-                let abs
-                    fn (x)
-                        cond (< x 0) (- 0 x) x
-                let rgba
-                    fn (r g b a)
-                        tou32
-                            |
-                                |
-                                    | (floor (* (clamp r 0 1) 255))
-                                        * (floor (* (clamp g 0 1) 255)) 256
-                                    * (floor (* (clamp b 0 1) 255)) 65536
-                                * (floor (* (clamp a 0 1) 255)) 16777216
-                let shader
-                    fn (i)
-                        let x (/ (% i w) w)
-                        let y (/ (// i w) h)
-                        let x (* (- (* 2 x) 1) (/ w h))
-                        let y (- (* 2 y) 1)
-                        let d
-                            *
-                                abs (- (sqrt (+ (* x x) (* y y))) 0.9)
-                                * h 0.5
-                        rgba d d d 0
+                    // (get sz 1) 4
+                fn shader (i)
+                    let x (/ (% i w) w)
+                    let y (/ (// i w) h)
+                    let x (* (- (* 2 x) 1) (/ w h))
+                    let y (- (* 2 y) 1)
+                    let d
+                        *
+                            abs
+                                - (sqrt (+ (* x x) (* y y)))
+                                    + 0.5
+                                        * 0.4 (sin (* 0.01 (get io 'iteration)))
+                            * h 0.5
+                    rgba d d d 0
                 maptext (* w h) shader
 let program
     quote
-        fn (io)
-            global frame = 0
-            frame += 1
-            let ssz = (('get io 'screen-size) as Cell)
-            let w h = (('get ssz 0) as Number as integer) (('get ssz 1) as Number as integer)
-            let w = (w // 4)
-            let h = (h // 4)
-            local s : String
-            'resize s (w * h * 4)
+        fn (ins outs)
+            let required-flags =
+                | InputFlags.ScreenSize InputFlags.Iteration
+            if ((ins.flags & required-flags) != required-flags)
+                return;
+            let ssz = ins.screen-size
+            let w h = (unpack ssz)
+            let w = (w // 1)
+            let h = (h // 1)
+            outs.flags = OutputFlags.Screen
+            'resize outs.screen (w * h * 4)
+            let s = outs.screen
             let ptr = ((& (s @ 0)) as (mutable @u32))
-            if ((frame & 1) == 0)
+            if ((ins.iteration & 1) == 0)
                 for y in (range h)
                     for x in (range w)
                         ptr @ (y * w + x) = 0xffff0000 as u32

          
@@ 62,8 66,7 @@ let program
                 for y in (range h)
                     for x in (range w)
                         ptr @ (y * w + x) = 0xff0000ff as u32
-            Cell.new
-                screen = s
+= native-program (all (get io 'setup) program)
 dump
     do
         let vecsize (nextindex (cell 1 2 3 4))

          
@@ 79,4 82,3 @@ dump
                             * element 2
                         in
                 get s 0
-= native-program (all (get io 'setup) program)

          
M testing/test_screen.tuk +26 -30
@@ 1,3 1,16 @@ 
+fn clamp (x mn mx)
+    cond (< x mn) mn
+        cond (> x mx) mx x
+fn abs (x)
+    cond (< x 0) (- 0 x) x
+fn rgba (r g b a)
+    tou32
+        |
+            |
+                | (floor (* (clamp r 0 1) 255))
+                    * (floor (* (clamp g 0 1) 255)) 256
+                * (floor (* (clamp b 0 1) 255)) 65536
+            * (floor (* (clamp a 0 1) 255)) 16777216
 = title
     .. "test_screen "
         ..

          
@@ 12,34 25,17 @@ 
         // (get sz 0) 4
     let h
         // (get sz 1) 4
-    let clamp
-        fn (x mn mx)
-            cond (< x mn) mn
-                cond (> x mx) mx x
-    let abs
-        fn (x)
-            cond (< x 0) (- 0 x) x
-    let rgba
-        fn (r g b a)
-            tou32
-                |
-                    |
-                        | (floor (* (clamp r 0 1) 255))
-                            * (floor (* (clamp g 0 1) 255)) 256
-                        * (floor (* (clamp b 0 1) 255)) 65536
-                    * (floor (* (clamp a 0 1) 255)) 16777216
-    let shader
-        fn (i)
-            let x (/ (% i w) w)
-            let y (/ (// i w) h)
-            let x (* (- (* 2 x) 1) (/ w h))
-            let y (- (* 2 y) 1)
-            let d
-                *
-                    abs
-                        - (sqrt (+ (* x x) (* y y)))
-                            + 0.5
-                                * 0.4 (sin (* 0.01 (get io 'iteration)))
-                    * h 0.5
-            rgba d d d 0
+    fn shader (i)
+        let x (/ (% i w) w)
+        let y (/ (// i w) h)
+        let x (* (- (* 2 x) 1) (/ w h))
+        let y (- (* 2 y) 1)
+        let d
+            *
+                abs
+                    - (sqrt (+ (* x x) (* y y)))
+                        + 0.5
+                            * 0.4 (sin (* 0.01 (get io 'iteration)))
+                * h 0.5
+        rgba d d d 0
     maptext (* w h) shader

          
M testing/tuk_interpreter.sc +250 -158
@@ 24,6 24,23 @@ struct Options
     debug = false
     statepath : string = ""
 
+global KEY_SETUP = (Atom 'setup)
+global KEY_SCREENSIZE = (Atom 'screen-size)
+global KEY_ITERATION = (Atom 'iteration)
+global KEY_STATE = (Atom 'state)
+global KEY_IO = (Atom 'io)
+global KEY_STDOUT = (Atom 'stdout)
+global KEY_BLOCKBREAK = (Atom 'block-break)
+global KEY_BREAK = (Atom 'break)
+global KEY_BLOCKCLOSE = (Atom 'block-close)
+global KEY_CLOSE = (Atom 'close)
+global KEY_SCREEN = (Atom 'screen)
+global KEY_TITLE = (Atom 'title)
+global KEY_NATIVE_PROGRAM = (Atom 'native-program)
+global KEY_PROMPT = (Atom 'prompt)
+global KEY_EXIT = (Atom 'exit)
+global KEY_READLINE = (Atom 'readline)
+
 fn run (argc argv program opts)
     #
         BASIC

          
@@ 92,15 109,6 @@ fn run (argc argv program opts)
 
     let env = ((global-environment) as Cell)
 
-    let tuk-globals = (globals)
-    let tuk-globals = ('bind tuk-globals 'Atom Atom)
-    let tuk-globals = ('bind tuk-globals 'Cell Cell)
-    let tuk-globals = ('bind tuk-globals 'Number Number)
-    let tuk-globals = ('bind tuk-globals 'String String)
-    let tuk-globals = ('bind tuk-globals 'quote sugar-quote)
-
-    let io = (Cell)
-
     inline debugprint (...)
         if opts.debug
             print ...

          
@@ 125,18 133,69 @@ fn run (argc argv program opts)
         out_Color = (texture smp (deref uv.in))
         return;
 
+    enum InputFlags : u64
+        Setup = (1 << 0)
+        ScreenSize = (1 << 1)
+        Iteration = (1 << 2)
+        State = (1 << 3)
+        Break = (1 << 4)
+        Close = (1 << 5)
+        Readline = (1 << 6)
+
+    enum OutputFlags : u64
+        State = (1 << 0)
+        Stdout = (1 << 1)
+        BlockBreak = (1 << 2)
+        BlockClose = (1 << 3)
+        Screen = (1 << 4)
+        Title = (1 << 5)
+        NativeProgram = (1 << 6)
+        Prompt = (1 << 7)
+        Exit = (1 << 8)
+
+    struct InputVars
+        flags : u64
+        readline : String
+        state : Atom
+        screen-size : ivec2
+        iteration : u32
+
+    struct OutputVars
+        flags : u64
+        stdout : String
+        prompt : String
+        screen : String
+        title : String
+        state : Atom
+        native-program : Atom
+        exit : i32
+
+    let tuk-globals = (globals)
+    let tuk-globals = ('bind tuk-globals 'Atom Atom)
+    let tuk-globals = ('bind tuk-globals 'Cell Cell)
+    let tuk-globals = ('bind tuk-globals 'Number Number)
+    let tuk-globals = ('bind tuk-globals 'String String)
+    let tuk-globals = ('bind tuk-globals 'InputFlags InputFlags)
+    let tuk-globals = ('bind tuk-globals 'OutputFlags OutputFlags)
+    let tuk-globals = ('bind tuk-globals 'InputVars InputVars)
+    let tuk-globals = ('bind tuk-globals 'OutputVars OutputVars)
+    let tuk-globals = ('bind tuk-globals 'quote sugar-quote)
+
+    local ins : InputVars
+    local outs : OutputVars
+
     let NativeFunctionType =
-        pointer (function (uniqueof Cell -1) (viewof Cell 1))
+        pointer (function void (viewof &InputVars 1) (viewof (mutable &OutputVars) 2))
 
     struct System
         break? = false
         close? = false
         gfx : (Option Graphics)
         screensize = (ivec2 640 360)
-        title = (Atom "UVM")
-        screen = (Atom)
+        title : String = "UVM"
+        screen : String
         native_function : NativeFunctionType = null
-        iteration = 0
+        iteration = 0:u32
     local sys : System
 
     @@ 'on GLMain.on-draw

          
@@ 156,173 215,206 @@ fn run (argc argv program opts)
 
     local ce : CachedEval
 
-    let
-        KEY_SETUP = (Atom 'setup)
-        KEY_SCREENSIZE = (Atom 'screen-size)
-        KEY_ITERATION = (Atom 'iteration)
-        KEY_STATE = (Atom 'state)
-        KEY_IO = (Atom 'io)
-        KEY_STDOUT = (Atom 'stdout)
-        KEY_BLOCKBREAK = (Atom 'block-break)
-        KEY_BREAK = (Atom 'break)
-        KEY_BLOCKCLOSE = (Atom 'block-close)
-        KEY_CLOSE = (Atom 'close)
-        KEY_SCREEN = (Atom 'screen)
-        KEY_TITLE = (Atom 'title)
-        KEY_NATIVE_PROGRAM = (Atom 'native-program)
-        KEY_PROMPT = (Atom 'prompt)
-        KEY_EXIT = (Atom 'exit)
-        KEY_READLINE = (Atom 'readline)
+    fn inputs->cell (inp)
+        let io = (Cell)
+        let flags = (deref inp.flags)
+        let io =
+            if (flags & InputFlags.Setup) ('set io KEY_SETUP true)
+            else io
+        let io =
+            if (flags & InputFlags.Break) ('set io KEY_BREAK true)
+            else io
+        let io =
+            if (flags & InputFlags.Close) ('set io KEY_CLOSE true)
+            else io
+        let io =
+            if (flags & InputFlags.Readline) ('set io KEY_READLINE (copy inp.readline))
+            else io
+        let io =
+            if (flags & InputFlags.State) ('set io KEY_STATE (copy inp.state))
+            else io
+        let io =
+            if (flags & InputFlags.ScreenSize) ('set io KEY_SCREENSIZE
+                (ivec2->cell inp.screen-size))
+            else io
+        let io =
+            if (flags & InputFlags.Iteration) ('set io KEY_ITERATION inp.iteration)
+            else io
+        io
 
-    vvv bind init
+    fn cell->outputs (cell outs)
+        let flags = outs.flags
+        flags = 0
+        inline transfer (key flag member fconvert)
+            let val = ('get cell key)
+            if (not ('none? val))
+                flags |= flag
+                static-if (not (none? member))
+                    (getattr outs member) =
+                        do
+                            static-if (none? fconvert) val
+                            else (fconvert val)
+
+        inline ->string (s)
+            copy (s as String)
+        inline ->i32 (s)
+            copy (s as Number as i32)
+
+        transfer KEY_BLOCKBREAK OutputFlags.BlockBreak
+        transfer KEY_BLOCKCLOSE OutputFlags.BlockClose
+        transfer KEY_STDOUT OutputFlags.Stdout 'stdout ->string
+        transfer KEY_PROMPT OutputFlags.Prompt 'prompt ->string
+        transfer KEY_SCREEN OutputFlags.Screen 'screen ->string
+        transfer KEY_TITLE OutputFlags.Title 'title ->string
+        transfer KEY_STATE OutputFlags.State 'state
+        transfer KEY_NATIVE_PROGRAM OutputFlags.NativeProgram 'native-program
+        transfer KEY_EXIT OutputFlags.Exit 'exit ->i32
+
     do
-        let io = ('set io KEY_SETUP true)
-        let io = ('set io KEY_SCREENSIZE (ivec2->cell sys.screensize))
-        let io = ('set io KEY_ITERATION sys.iteration)
-        let io =
-            if (sc_is_file opts.statepath)
-                debugprint "loading state from" opts.statepath
-                let file =
-                    try (File.open opts.statepath "rb")
-                    else
-                        error
-                            .. "opening " opts.statepath " failed"
-                'set io KEY_STATE (unpickle file)
-            else io
+        ins.flags =
+            | InputFlags.Setup InputFlags.ScreenSize InputFlags.Iteration
+        ins.screen-size = sys.screensize
+        ins.iteration = sys.iteration
+        if (sc_is_file opts.statepath)
+            debugprint "loading state from" opts.statepath
+            let file =
+                try (File.open opts.statepath "rb")
+                else
+                    error
+                        .. "opening " opts.statepath " failed"
+            ins.state = (unpickle file)
+            ins.flags |= InputFlags.State
+        let io = (inputs->cell ins)
+        debugprint "setup IO ->" ('tostring (Atom (copy io)))
         let env = ('set env KEY_IO io)
-        'eval ce env program
-    #
-            global glmain =
-        GLMain
-            title = "OpenGL Compute"
-            hidden = true
-
-    if ('none? init)
-        print "error: setup unhandled"
-        return 255
-    vvv bind result state
-    loop (state = init)
+        let state = ('eval ce env program)
+        if ('none? state)
+            print "error: setup unhandled"
+            return 255
         if (('kind state) != Atom.Kind.Cell)
             print "error: main function must return cell"
-            break 255 (Atom)
-        debugprint "IO <-" ('tostring (Atom state))
-        let cstate = (state as Cell)
-        let stateval = ('get cstate KEY_STATE)
-        let stdoutval = ('get cstate KEY_STDOUT)
-        if (not ('none? stdoutval))
-            io-write! (stdoutval as String as string)
-        let io = (copy io)
+            return 255
+        debugprint "setup IO <-" ('tostring (Atom state))
+        cell->outputs (state as Cell) outs
+
+    vvv bind result
+    loop ()
+        let inflags = ins.flags
+        inflags = 0
+        let flags = (deref outs.flags)
+        let state? = ((flags & OutputFlags.State) != 0)
+        if state?
+            inflags |= InputFlags.State
+            ins.state = (copy outs.state)
+        if (flags & OutputFlags.Stdout)
+            io-write! (outs.stdout as string)
         if sys.break?
             sys.break? = false
-            let blockbreak = ('get cstate KEY_BLOCKBREAK)
-            if ('none? blockbreak)
+            if ((flags & OutputFlags.BlockBreak) == 0)
                 debugprint "breaking"
-                break 255 stateval
+                break 255
         if sys.close?
             sys.close? = false
-            let blockclose = ('get cstate KEY_BLOCKCLOSE)
-            if ('none? blockclose)
+            if ((flags & OutputFlags.BlockClose) == 0)
                 debugprint "closing"
-                break 0 stateval
-        #let screensize = ('get cstate KEY_SCREENSIZE)
-        let screen = ('get cstate KEY_SCREEN)
-        let io =
-            if (not ('none? screen))
-                # ensure GL subsystem is running
-                let titleval = ('get cstate KEY_TITLE)
-                if (not sys.gfx)
-                    if (not ('none? titleval))
-                        if (('kind titleval) == Atom.Kind.Text)
-                            sys.title = (copy titleval)
-                    debugprint "starting graphics system"
-                    sys.gfx =
-                        Graphics
-                            glmain =
-                                GLMain
-                                    title = (sys.title as String)
-                                    width = sys.screensize.x
-                                    height = sys.screensize.y
-                                    resizable = true
-                            shader =
-                                do
-                                    let pg = (GL.Program)
-                                    call
-                                        attach-shaders pg
-                                            vertex = vertex-program
-                                            fragment = fragment-program
-                                    pg
-                let gfx = ('force-unwrap sys.gfx)
-                let glmain = gfx.glmain
-                if (not ('none? titleval))
-                    if (('kind titleval) == Atom.Kind.Text)
-                        glmain.title = (titleval as String)
-                    sys.title = titleval
-                if (('kind screen) == Atom.Kind.Text)
-                    let w h = (unpack sys.screensize)
-                    let bufferdata = (screen as String)
-                    let buffersize = (countof bufferdata)
-                    for scale in (range 1 5)
-                        let requiredsize = ((w // scale) * (h // scale) * 4)
-                        if (buffersize == requiredsize)
-                            GL.BindTexture GL.TEXTURE_2D gfx.tx_screen
-                            GL.TexImage2D GL.TEXTURE_2D 0 GL.RGBA8 (w // scale) (h // scale) 0 GL.RGBA \
-                                GL.UNSIGNED_BYTE (& (bufferdata @ 0))
-                            GL.TexParameteri GL.TEXTURE_2D GL.TEXTURE_WRAP_S GL.CLAMP_TO_EDGE
-                            GL.TexParameteri GL.TEXTURE_2D GL.TEXTURE_WRAP_T GL.CLAMP_TO_EDGE
-                            GL.TexParameteri GL.TEXTURE_2D GL.TEXTURE_MIN_FILTER GL.LINEAR
-                            GL.TexParameteri GL.TEXTURE_2D GL.TEXTURE_MAG_FILTER GL.NEAREST
-                            GL.BindTexture GL.TEXTURE_2D 0
-                            break;
-                    else
-                        debugprint "screen buffersize mismatch" buffersize "!=" (w * h * 4)
-                let continue? = ('step glmain)
-                sys.screensize = (ivec2 ('size glmain))
-                if continue? io
-                else
-                    sys.close? = true
-                    'set io KEY_CLOSE true
-            else io
-        let exitval = ('get cstate KEY_EXIT)
-        if (not ('none? exitval))
-            break (exitval as Number as integer) stateval
+                break 0
+        if (flags & OutputFlags.Screen)
+            # ensure GL subsystem is running
+            let title? = ((flags & OutputFlags.Title) != 0)
+            if (not sys.gfx)
+                if title?
+                    sys.title = (copy outs.title)
+                debugprint "starting graphics system"
+                sys.gfx =
+                    Graphics
+                        glmain =
+                            GLMain
+                                title = sys.title
+                                width = sys.screensize.x
+                                height = sys.screensize.y
+                                resizable = true
+                        shader =
+                            do
+                                let pg = (GL.Program)
+                                call
+                                    attach-shaders pg
+                                        vertex = vertex-program
+                                        fragment = fragment-program
+                                pg
+            let gfx = ('force-unwrap sys.gfx)
+            let glmain = gfx.glmain
+            if title?
+                glmain.title = outs.title
+                sys.title = (copy outs.title)
+            let w h = (unpack sys.screensize)
+            let bufferdata = outs.screen
+            let buffersize = (countof bufferdata)
+            for scale in (range 1 5)
+                let requiredsize = ((w // scale) * (h // scale) * 4)
+                if (buffersize == requiredsize)
+                    GL.BindTexture GL.TEXTURE_2D gfx.tx_screen
+                    GL.TexImage2D GL.TEXTURE_2D 0 GL.RGBA8 (w // scale) (h // scale) 0 GL.RGBA \
+                        GL.UNSIGNED_BYTE (& (bufferdata @ 0))
+                    GL.TexParameteri GL.TEXTURE_2D GL.TEXTURE_WRAP_S GL.CLAMP_TO_EDGE
+                    GL.TexParameteri GL.TEXTURE_2D GL.TEXTURE_WRAP_T GL.CLAMP_TO_EDGE
+                    GL.TexParameteri GL.TEXTURE_2D GL.TEXTURE_MIN_FILTER GL.LINEAR
+                    GL.TexParameteri GL.TEXTURE_2D GL.TEXTURE_MAG_FILTER GL.NEAREST
+                    GL.BindTexture GL.TEXTURE_2D 0
+                    break;
+            else
+                debugprint "screen buffersize mismatch" buffersize "!=" (w * h * 4)
+            let continue? = ('step glmain)
+            sys.screensize = (ivec2 ('size glmain))
+            if (not continue?)
+                sys.close? = true
+                inflags |= InputFlags.Close
+        if (flags & OutputFlags.Exit)
+            break (copy outs.exit)
         local repeat? : bool =
-            sys.gfx | (not ('none? stateval))
-        let runscopesval = ('get cstate KEY_NATIVE_PROGRAM)
-        if (not ('none? runscopesval))
+            sys.gfx | ((flags & OutputFlags.State) != 0)
+        if (flags & OutputFlags.NativeProgram)
             repeat? = true
-            let expr = ('to-value runscopesval)
+            let expr = ('to-value outs.native-program)
             if (('typeof expr) == list)
                 let f = (sc_expand (expr as list) '() tuk-globals)
-                let types = (alloca-array type 1)
-                types @ 0 = Cell
-                let f = (sc_typify_template f 1 types)
+                let types = (alloca-array type 2)
+                types @ 0 = &InputVars
+                types @ 1 = (mutable &OutputVars)
+                let f = (sc_typify_template f 2 types)
                 let f = (sc_compile f compile-flag-cache)
                 let f = (f as NativeFunctionType)
                 sys.native_function = f
-        let promptval = ('get cstate KEY_PROMPT)
-        let io =
-            if (not ('none? promptval))
-                repeat? = true
-                let ok? line = (sc_prompt (promptval as String as string) "")
-                if ok?
-                    'set io KEY_READLINE (.. line "\n")
-                else
-                    sys.break? = true
-                    'set io KEY_BREAK true
-            else io
+        if (flags & OutputFlags.Prompt)
+            repeat? = true
+            let ok? line = (sc_prompt (outs.prompt as string) "")
+            if ok?
+                inflags |= InputFlags.Readline
+                ins.readline = line
+                'append ins.readline "\n"
+            else
+                sys.break? = true
+                inflags |= InputFlags.Break
         if (not repeat?)
-            break 0 stateval
-        let io = ('set io KEY_SCREENSIZE (ivec2->cell sys.screensize))
+            break 0
+        inflags |=
+            | InputFlags.ScreenSize InputFlags.Iteration
+        ins.screen-size = sys.screensize
         sys.iteration += 1
-        let io = ('set io KEY_ITERATION sys.iteration)
-        let io = ('set io KEY_STATE stateval)
-        debugprint "IO ->" ('tostring (Atom (copy io)))
+        ins.iteration = sys.iteration
         if (sys.native_function != null)
-            Atom (sys.native_function io)
+            sys.native_function ins outs
         else
+            let io = (inputs->cell ins)
+            debugprint "IO ->" ('tostring (Atom (copy io)))
             let env = ('set (copy env) KEY_IO io)
-            'eval ce (copy env) (copy program)
-    if ((not (empty? opts.statepath)) and (not ('none? state)))
+            let state = ('eval ce (copy env) (copy program))
+            if (('kind state) != Atom.Kind.Cell)
+                print "error: main function must return cell"
+                break 255
+            debugprint "IO <-" ('tostring (Atom state))
+            cell->outputs (state as Cell) outs
+
+    if ((not (empty? opts.statepath)) and ((ins.flags & InputFlags.State) != 0))
         using import tukan.File
         let testfilepath =
             .. module-dir "/test.uvm"

          
@@ 333,7 425,7 @@ fn run (argc argv program opts)
                     error
                         .. "creating " opts.statepath " failed"
             debugprint "saving state to" opts.statepath
-            pickle file state
+            pickle file ins.state
             drop file
     debugprint "exiting with code" result
     debugprint "cache stats:" ce.hits "hits," ce.misses "misses"