diff --git a/crates/cli/tests/test-projects/false-interpreter/Context.roc b/crates/cli/tests/test-projects/false-interpreter/Context.roc index b1eec7541b..99a4af99c8 100644 --- a/crates/cli/tests/test-projects/false-interpreter/Context.roc +++ b/crates/cli/tests/test-projects/false-interpreter/Context.roc @@ -1,4 +1,4 @@ -module [Context, Data, with, getChar, Option, pushStack, popStack, toStr, inWhileScope] +module [Context, Data, with!, getChar!, Option, pushStack, popStack, toStr, inWhileScope] import pf.File import Variable exposing [Variable] @@ -20,13 +20,13 @@ pushStack = \ctx, data -> # I think an open tag union should just work here. # Instead at a call sites, I need to match on the error and then return the same error. # Otherwise it hits unreachable code in ir.rs -popStack : Context -> Result [T Context Data] [EmptyStack] +popStack : Context -> Result (Context, Data) [EmptyStack] popStack = \ctx -> when List.last ctx.stack is Ok val -> poppedCtx = { ctx & stack: List.dropAt ctx.stack (List.len ctx.stack - 1) } - Ok (T poppedCtx val) + Ok (poppedCtx, val) Err ListWasEmpty -> Err EmptyStack @@ -58,30 +58,30 @@ toStr = \{ scopes, stack, state, vars } -> "\n============\nDepth: $(depth)\nState: $(stateStr)\nStack: [$(stackStr)]\nVars: [$(varsStr)]\n============\n" -with : Str, (Context -> Task {} a) -> Task {} a -with = \path, callback -> - File.withOpen path \handle -> +with! : Str, (Context => a) => a +with! = \path, callback! -> + File.withOpen! path \handle -> # I cant define scope here and put it in the list in callback. It breaks alias anaysis. # Instead I have to inline this. # root_scope = { data: Some handle, index: 0, buf: [], whileInfo: None } - callback { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount } + callback! { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount } # I am pretty sure there is a syntax to destructure and keep a reference to the whole, but Im not sure what it is. -getChar : Context -> Task [T U8 Context] [EndOfData, NoScope] -getChar = \ctx -> +getChar! : Context => Result (U8, Context) [EndOfData, NoScope] +getChar! = \ctx -> when List.last ctx.scopes is Ok scope -> - (T val newScope) = getCharScope! scope - Task.ok (T val { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope }) + (val, newScope) = getCharScope!? scope + Ok (val, { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope }) Err ListWasEmpty -> - Task.err NoScope + Err NoScope -getCharScope : Scope -> Task [T U8 Scope] [EndOfData, NoScope] -getCharScope = \scope -> +getCharScope! : Scope => Result (U8, Scope) [EndOfData, NoScope] +getCharScope! = \scope -> when List.get scope.buf scope.index is Ok val -> - Task.ok (T val { scope & index: scope.index + 1 }) + Ok (val, { scope & index: scope.index + 1 }) Err OutOfBounds -> when scope.data is @@ -90,13 +90,13 @@ getCharScope = \scope -> when List.first bytes is Ok val -> # This starts at 1 because the first character is already being returned. - Task.ok (T val { scope & buf: bytes, index: 1 }) + Ok (val, { scope & buf: bytes, index: 1 }) Err ListWasEmpty -> - Task.err EndOfData + Err EndOfData None -> - Task.err EndOfData + Err EndOfData inWhileScope : Context -> Bool inWhileScope = \ctx -> diff --git a/crates/cli/tests/test-projects/false-interpreter/main.roc b/crates/cli/tests/test-projects/false-interpreter/main.roc index 9114c51886..6b2a29c11f 100644 --- a/crates/cli/tests/test-projects/false-interpreter/main.roc +++ b/crates/cli/tests/test-projects/false-interpreter/main.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "platform/main.roc" } +app [main!] { pf: platform "platform/main.roc" } import pf.Stdout import pf.Stdin @@ -20,54 +20,58 @@ import Variable exposing [Variable] # I assume all of the Task.awaits are the cause of this, but I am not 100% sure. InterpreterErrors : [BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, MaxInputNumber, NoLambdaOnStack, NoNumberOnStack, NoVariableOnStack, NoScope, OutOfBounds, UnexpectedEndOfData] -main : Str -> Task {} [] -main = \filename -> - interpretFile filename - |> Task.onErr \StringErr e -> Stdout.line "Ran into problem:\n$(e)\n" +main! : Str => {} +main! = \filename -> + when interpretFile! filename is + Ok {} -> + {} -interpretFile : Str -> Task {} [StringErr Str] -interpretFile = \filename -> - Context.with filename \ctx -> - result = interpretCtx ctx |> Task.result! + Err (StringErr e) -> + Stdout.line! "Ran into problem:\n$(e)\n" + +interpretFile! : Str => Result {} [StringErr Str] +interpretFile! = \filename -> + Context.with! filename \ctx -> + result = interpretCtx! ctx when result is Ok _ -> - Task.ok {} + Ok {} Err BadUtf8 -> - Task.err (StringErr "Failed to convert string from Utf8 bytes") + Err (StringErr "Failed to convert string from Utf8 bytes") Err DivByZero -> - Task.err (StringErr "Division by zero") + Err (StringErr "Division by zero") Err EmptyStack -> - Task.err (StringErr "Tried to pop a value off of the stack when it was empty") + Err (StringErr "Tried to pop a value off of the stack when it was empty") Err InvalidBooleanValue -> - Task.err (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)") + Err (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)") Err (InvalidChar char) -> - Task.err (StringErr "Ran into an invalid character with ascii code: $(char)") + Err (StringErr "Ran into an invalid character with ascii code: $(char)") Err MaxInputNumber -> - Task.err (StringErr "Like the original false compiler, the max input number is 320,000") + Err (StringErr "Like the original false compiler, the max input number is 320,000") Err NoLambdaOnStack -> - Task.err (StringErr "Tried to run a lambda when no lambda was on the stack") + Err (StringErr "Tried to run a lambda when no lambda was on the stack") Err NoNumberOnStack -> - Task.err (StringErr "Tried to run a number when no number was on the stack") + Err (StringErr "Tried to run a number when no number was on the stack") Err NoVariableOnStack -> - Task.err (StringErr "Tried to load a variable when no variable was on the stack") + Err (StringErr "Tried to load a variable when no variable was on the stack") Err NoScope -> - Task.err (StringErr "Tried to run code when not in any scope") + Err (StringErr "Tried to run code when not in any scope") Err OutOfBounds -> - Task.err (StringErr "Tried to load from an offset that was outside of the stack") + Err (StringErr "Tried to load from an offset that was outside of the stack") Err UnexpectedEndOfData -> - Task.err (StringErr "Hit end of data while still parsing something") + Err (StringErr "Hit end of data while still parsing something") isDigit : U8 -> Bool isDigit = \char -> @@ -75,6 +79,7 @@ isDigit = \char -> >= 0x30 # `0` && char <= 0x39 # `0` + isWhitespace : U8 -> Bool isWhitespace = \char -> char @@ -85,12 +90,21 @@ isWhitespace = \char -> == 0x20 # space || char == 0x9 # tab -interpretCtx : Context -> Task Context InterpreterErrors -interpretCtx = \ctx -> - Task.loop ctx interpretCtxLoop -interpretCtxLoop : Context -> Task [Step Context, Done Context] InterpreterErrors -interpretCtxLoop = \ctx -> +interpretCtx! : Context => Result Context InterpreterErrors +interpretCtx! = \ctx -> + when interpretCtxLoop! ctx is + Ok (Step next) -> + interpretCtx! next + + Ok (Done next) -> + Ok next + + Err e -> + Err e + +interpretCtxLoop! : Context => Result [Step Context, Done Context] InterpreterErrors +interpretCtxLoop! = \ctx -> when ctx.state is Executing if Context.inWhileScope ctx -> # Deal with the current while loop potentially looping. @@ -102,41 +116,41 @@ interpretCtxLoop = \ctx -> Some { state: InCond, body, cond } -> # Just ran condition. Check the top of stack to see if body should run. when popNumber ctx is - Ok (T popCtx n) -> + Ok (popCtx, n) -> if n == 0 then newScope = { scope & whileInfo: None } - Task.ok (Step { popCtx & scopes: List.set ctx.scopes last newScope }) + Ok (Step { popCtx & scopes: List.set ctx.scopes last newScope }) else newScope = { scope & whileInfo: Some { state: InBody, body, cond } } - Task.ok (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) + Ok (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) Err e -> - Task.err e + Err e Some { state: InBody, body, cond } -> # Just rand the body. Run the condition again. newScope = { scope & whileInfo: Some { state: InCond, body, cond } } - Task.ok (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) + Ok (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) None -> - Task.err NoScope + Err NoScope Err OutOfBounds -> - Task.err NoScope + Err NoScope Executing -> # Stdout.line! (Context.toStr ctx) - result = Context.getChar ctx |> Task.result! + result = Context.getChar! ctx when result is - Ok (T val newCtx) -> - execCtx = stepExecCtx! newCtx val - Task.ok (Step execCtx) + Ok (val, newCtx) -> + execCtx = stepExecCtx!? newCtx val + Ok (Step execCtx) Err NoScope -> - Task.err NoScope + Err NoScope Err EndOfData -> # Computation complete for this scope. @@ -145,30 +159,30 @@ interpretCtxLoop = \ctx -> # If no scopes left, all execution complete. if List.isEmpty dropCtx.scopes then - Task.ok (Done dropCtx) + Ok (Done dropCtx) else - Task.ok (Step dropCtx) + Ok (Step dropCtx) InComment -> - result = Context.getChar ctx |> Task.result! + result = Context.getChar! ctx when result is - Ok (T val newCtx) -> + Ok (val, newCtx) -> if val == 0x7D then # `}` end of comment - Task.ok (Step { newCtx & state: Executing }) + Ok (Step { newCtx & state: Executing }) else - Task.ok (Step { newCtx & state: InComment }) + Ok (Step { newCtx & state: InComment }) Err NoScope -> - Task.err NoScope + Err NoScope Err EndOfData -> - Task.err UnexpectedEndOfData + Err UnexpectedEndOfData InNumber accum -> - result = Context.getChar ctx |> Task.result! + result = Context.getChar! ctx when result is - Ok (T val newCtx) -> + Ok (val, newCtx) -> if isDigit val then # still in the number # i32 multiplication is kinda broken because it implicitly seems to want to upcast to i64. @@ -176,72 +190,72 @@ interpretCtxLoop = \ctx -> # so this is make i64 mul by 10 then convert back to i32. nextAccum = (10 * Num.intCast accum) + Num.intCast (val - 0x30) - Task.ok (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) + Ok (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) else # outside of number now, this needs to be executed. pushCtx = Context.pushStack newCtx (Number accum) - execCtx = stepExecCtx! { pushCtx & state: Executing } val - Task.ok (Step execCtx) + execCtx = stepExecCtx!? { pushCtx & state: Executing } val + Ok (Step execCtx) Err NoScope -> - Task.err NoScope + Err NoScope Err EndOfData -> - Task.err UnexpectedEndOfData + Err UnexpectedEndOfData InString bytes -> - result = Context.getChar ctx |> Task.result! + result = Context.getChar! ctx when result is - Ok (T val newCtx) -> + Ok (val, newCtx) -> if val == 0x22 then # `"` end of string when Str.fromUtf8 bytes is Ok str -> Stdout.raw! str - Task.ok (Step { newCtx & state: Executing }) + Ok (Step { newCtx & state: Executing }) Err _ -> - Task.err BadUtf8 + Err BadUtf8 else - Task.ok (Step { newCtx & state: InString (List.append bytes val) }) + Ok (Step { newCtx & state: InString (List.append bytes val) }) Err NoScope -> - Task.err NoScope + Err NoScope Err EndOfData -> - Task.err UnexpectedEndOfData + Err UnexpectedEndOfData InLambda depth bytes -> - result = Context.getChar ctx |> Task.result! + result = Context.getChar! ctx when result is - Ok (T val newCtx) -> + Ok (val, newCtx) -> if val == 0x5B then # start of a nested lambda `[` - Task.ok (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) + Ok (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) else if val == 0x5D then # `]` end of current lambda if depth == 0 then # end of all lambdas - Task.ok (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) + Ok (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) else # end of nested lambda - Task.ok (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) + Ok (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) else - Task.ok (Step { newCtx & state: InLambda depth (List.append bytes val) }) + Ok (Step { newCtx & state: InLambda depth (List.append bytes val) }) Err NoScope -> - Task.err NoScope + Err NoScope Err EndOfData -> - Task.err UnexpectedEndOfData + Err UnexpectedEndOfData InSpecialChar -> - result = Context.getChar { ctx & state: Executing } |> Task.result! + result = Context.getChar! { ctx & state: Executing } when result is - Ok (T 0xB8 newCtx) -> + Ok (0xB8, newCtx) -> result2 = - (T popCtx index) = popNumber? newCtx + (popCtx, index) = popNumber? newCtx # I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers. size = List.len popCtx.stack - 1 offset = Num.intCast size - index @@ -253,329 +267,302 @@ interpretCtxLoop = \ctx -> Err OutOfBounds when result2 is - Ok a -> Task.ok (Step a) - Err e -> Task.err e + Ok a -> Ok (Step a) + Err e -> Err e - Ok (T 0x9F newCtx) -> + Ok (0x9F, newCtx) -> # This is supposed to flush io buffers. We don't buffer, so it does nothing - Task.ok (Step newCtx) + Ok (Step newCtx) - Ok (T x _) -> + Ok (x, _) -> data = Num.toStr (Num.intCast x) - Task.err (InvalidChar data) + Err (InvalidChar data) Err NoScope -> - Task.err NoScope + Err NoScope Err EndOfData -> - Task.err UnexpectedEndOfData + Err UnexpectedEndOfData LoadChar -> - result = Context.getChar { ctx & state: Executing } |> Task.result! + result = Context.getChar! { ctx & state: Executing } when result is - Ok (T x newCtx) -> - Task.ok (Step (Context.pushStack newCtx (Number (Num.intCast x)))) + Ok (x, newCtx) -> + Ok (Step (Context.pushStack newCtx (Number (Num.intCast x)))) Err NoScope -> - Task.err NoScope + Err NoScope Err EndOfData -> - Task.err UnexpectedEndOfData + Err UnexpectedEndOfData # If it weren't for reading stdin or writing to stdout, this could return a result. -stepExecCtx : Context, U8 -> Task Context InterpreterErrors -stepExecCtx = \ctx, char -> +stepExecCtx! : Context, U8 => Result Context InterpreterErrors +stepExecCtx! = \ctx, char -> when char is 0x21 -> # `!` execute lambda - Task.fromResult - ( - (T popCtx bytes) = popLambda? ctx - Ok { popCtx & scopes: List.append popCtx.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } - ) + (popCtx, bytes) = popLambda? ctx + Ok { popCtx & scopes: List.append popCtx.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } 0x3F -> # `?` if - Task.fromResult - ( - (T popCtx1 bytes) = popLambda? ctx - (T popCtx2 n1) = popNumber? popCtx1 - if n1 == 0 then - Ok popCtx2 - else - Ok { popCtx2 & scopes: List.append popCtx2.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } - ) + (popCtx1, bytes) = popLambda? ctx + (popCtx2, n1) = popNumber? popCtx1 + if n1 == 0 then + Ok popCtx2 + else + Ok { popCtx2 & scopes: List.append popCtx2.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } 0x23 -> # `#` while - Task.fromResult - ( - (T popCtx1 body) = popLambda? ctx - (T popCtx2 cond) = popLambda? popCtx1 - last = (List.len popCtx2.scopes - 1) + (popCtx1, body) = popLambda? ctx + (popCtx2, cond) = popLambda? popCtx1 + last = (List.len popCtx2.scopes - 1) - when List.get popCtx2.scopes last is - Ok scope -> - # set the current scope to be in a while loop. - scopes = List.set popCtx2.scopes last { scope & whileInfo: Some { cond: cond, body: body, state: InCond } } + when List.get popCtx2.scopes last is + Ok scope -> + # set the current scope to be in a while loop. + scopes = List.set popCtx2.scopes last { scope & whileInfo: Some { cond: cond, body: body, state: InCond } } - # push a scope to execute the condition. - Ok { popCtx2 & scopes: List.append scopes { data: None, buf: cond, index: 0, whileInfo: None } } + # push a scope to execute the condition. + Ok { popCtx2 & scopes: List.append scopes { data: None, buf: cond, index: 0, whileInfo: None } } - Err OutOfBounds -> - Err NoScope - ) + Err OutOfBounds -> + Err NoScope 0x24 -> # `$` dup # Switching this to List.last and changing the error to ListWasEmpty leads to a compiler bug. # Complains about the types eq not matching. when List.get ctx.stack (List.len ctx.stack - 1) is - Ok dupItem -> Task.ok (Context.pushStack ctx dupItem) - Err OutOfBounds -> Task.err EmptyStack + Ok dupItem -> Ok (Context.pushStack ctx dupItem) + Err OutOfBounds -> Err EmptyStack 0x25 -> # `%` drop when Context.popStack ctx is # Dropping with an empty stack, all results here are fine - Ok (T popCtx _) -> Task.ok popCtx - Err _ -> Task.ok ctx + Ok (popCtx, _) -> Ok popCtx + Err _ -> Ok ctx 0x5C -> # `\` swap result2 = - (T popCtx1 n1) = Context.popStack? ctx - (T popCtx2 n2) = Context.popStack? popCtx1 + (popCtx1, n1) = Context.popStack? ctx + (popCtx2, n2) = Context.popStack? popCtx1 Ok (Context.pushStack (Context.pushStack popCtx2 n1) n2) when result2 is Ok a -> - Task.ok a + Ok a # Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack Err EmptyStack -> - Task.err EmptyStack + Err EmptyStack 0x40 -> # `@` rot result2 = - (T popCtx1 n1) = Context.popStack? ctx - (T popCtx2 n2) = Context.popStack? popCtx1 - (T popCtx3 n3) = Context.popStack? popCtx2 + (popCtx1, n1) = Context.popStack? ctx + (popCtx2, n2) = Context.popStack? popCtx1 + (popCtx3, n3) = Context.popStack? popCtx2 Ok (Context.pushStack (Context.pushStack (Context.pushStack popCtx3 n2) n1) n3) when result2 is Ok a -> - Task.ok a + Ok a # Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack Err EmptyStack -> - Task.err EmptyStack + Err EmptyStack 0xC3 -> # `ø` pick or `ß` flush # these are actually 2 bytes, 0xC3 0xB8 or 0xC3 0x9F # requires special parsing - Task.ok { ctx & state: InSpecialChar } + Ok { ctx & state: InSpecialChar } 0x4F -> # `O` also treat this as pick for easier script writing - Task.fromResult - ( - (T popCtx index) = popNumber? ctx - # I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers. - size = List.len popCtx.stack - 1 - offset = Num.intCast size - index + (popCtx, index) = popNumber? ctx + # I think Num.abs is too restrictive, it should be able to produce a natural number, but it seem to be restricted to signed numbers. + size = List.len popCtx.stack - 1 + offset = Num.intCast size - index - if offset >= 0 then - stackVal = List.get? popCtx.stack (Num.intCast offset) - Ok (Context.pushStack popCtx stackVal) - else - Err OutOfBounds - ) + if offset >= 0 then + stackVal = List.get? popCtx.stack (Num.intCast offset) + Ok (Context.pushStack popCtx stackVal) + else + Err OutOfBounds 0x42 -> # `B` also treat this as flush for easier script writing # This is supposed to flush io buffers. We don't buffer, so it does nothing - Task.ok ctx + Ok ctx 0x27 -> # `'` load next char - Task.ok { ctx & state: LoadChar } + Ok { ctx & state: LoadChar } 0x2B -> # `+` add - Task.fromResult (binaryOp ctx Num.addWrap) + binaryOp ctx Num.addWrap 0x2D -> # `-` sub - Task.fromResult (binaryOp ctx Num.subWrap) + binaryOp ctx Num.subWrap 0x2A -> # `*` mul - Task.fromResult (binaryOp ctx Num.mulWrap) + binaryOp ctx Num.mulWrap 0x2F -> # `/` div # Due to possible division by zero error, this must be handled specially. - Task.fromResult - ( - (T popCtx1 numR) = popNumber? ctx - (T popCtx2 numL) = popNumber? popCtx1 - res = Num.divTruncChecked? numL numR - Ok (Context.pushStack popCtx2 (Number res)) - ) + (popCtx1, numR) = popNumber? ctx + (popCtx2, numL) = popNumber? popCtx1 + res = Num.divTruncChecked? numL numR + Ok (Context.pushStack popCtx2 (Number res)) 0x26 -> # `&` bitwise and - Task.fromResult (binaryOp ctx Num.bitwiseAnd) + binaryOp ctx Num.bitwiseAnd 0x7C -> # `|` bitwise or - Task.fromResult (binaryOp ctx Num.bitwiseOr) + binaryOp ctx Num.bitwiseOr 0x3D -> # `=` equals - Task.fromResult - ( - binaryOp ctx \a, b -> - if a == b then - -1 - else - 0 - ) + binaryOp ctx \a, b -> + if a == b then + -1 + else + 0 0x3E -> # `>` greater than - Task.fromResult - ( - binaryOp ctx \a, b -> - if a > b then - -1 - else - 0 - ) + binaryOp ctx \a, b -> + if a > b then + -1 + else + 0 0x5F -> # `_` negate - Task.fromResult (unaryOp ctx Num.neg) + unaryOp ctx Num.neg 0x7E -> # `~` bitwise not - Task.fromResult (unaryOp ctx (\x -> Num.bitwiseXor x -1)) # xor with -1 should be bitwise not + unaryOp ctx (\x -> Num.bitwiseXor x -1) # xor with -1 should be bitwise not 0x2C -> # `,` write char when popNumber ctx is - Ok (T popCtx num) -> + Ok (popCtx, num) -> when Str.fromUtf8 [Num.intCast num] is Ok str -> Stdout.raw! str - Task.ok popCtx + Ok popCtx Err _ -> - Task.err BadUtf8 + Err BadUtf8 Err e -> - Task.err e + Err e 0x2E -> # `.` write int when popNumber ctx is - Ok (T popCtx num) -> + Ok (popCtx, num) -> Stdout.raw! (Num.toStr (Num.intCast num)) - Task.ok popCtx + Ok popCtx Err e -> - Task.err e + Err e 0x5E -> # `^` read char as int in = Stdin.char! {} if in == 255 then # max char sent on EOF. Change to -1 - Task.ok (Context.pushStack ctx (Number -1)) + Ok (Context.pushStack ctx (Number -1)) else - Task.ok (Context.pushStack ctx (Number (Num.intCast in))) + Ok (Context.pushStack ctx (Number (Num.intCast in))) 0x3A -> # `:` store to variable - Task.fromResult - ( - (T popCtx1 var) = popVariable? ctx - # The Result.mapErr on the next line maps from EmptyStack in Context.roc to the full InterpreterErrors union here. - (T popCtx2 n1) = Result.mapErr? (Context.popStack popCtx1) (\EmptyStack -> EmptyStack) - Ok { popCtx2 & vars: List.set popCtx2.vars (Variable.toIndex var) n1 } - ) + (popCtx1, var) = popVariable? ctx + # The Result.mapErr on the next line maps from EmptyStack in Context.roc to the full InterpreterErrors union here. + (popCtx2, n1) = Result.mapErr? (Context.popStack popCtx1) (\EmptyStack -> EmptyStack) + Ok { popCtx2 & vars: List.set popCtx2.vars (Variable.toIndex var) n1 } 0x3B -> # `;` load from variable - Task.fromResult - ( - (T popCtx var) = popVariable? ctx - elem = List.get? popCtx.vars (Variable.toIndex var) - Ok (Context.pushStack popCtx elem) - ) + (popCtx, var) = popVariable? ctx + elem = List.get? popCtx.vars (Variable.toIndex var) + Ok (Context.pushStack popCtx elem) 0x22 -> # `"` string start - Task.ok { ctx & state: InString [] } + Ok { ctx & state: InString [] } 0x5B -> # `"` string start - Task.ok { ctx & state: InLambda 0 [] } + Ok { ctx & state: InLambda 0 [] } 0x7B -> # `{` comment start - Task.ok { ctx & state: InComment } + Ok { ctx & state: InComment } x if isDigit x -> # number start - Task.ok { ctx & state: InNumber (Num.intCast (x - 0x30)) } + Ok { ctx & state: InNumber (Num.intCast (x - 0x30)) } x if isWhitespace x -> - Task.ok ctx + Ok ctx x -> when Variable.fromUtf8 x is # letters are variable names Ok var -> - Task.ok (Context.pushStack ctx (Var var)) + Ok (Context.pushStack ctx (Var var)) Err _ -> data = Num.toStr (Num.intCast x) - Task.err (InvalidChar data) + Err (InvalidChar data) unaryOp : Context, (I32 -> I32) -> Result Context InterpreterErrors unaryOp = \ctx, op -> - (T popCtx num) = popNumber? ctx + (popCtx, num) = popNumber? ctx Ok (Context.pushStack popCtx (Number (op num))) binaryOp : Context, (I32, I32 -> I32) -> Result Context InterpreterErrors binaryOp = \ctx, op -> - (T popCtx1 numR) = popNumber? ctx - (T popCtx2 numL) = popNumber? popCtx1 + (popCtx1, numR) = popNumber? ctx + (popCtx2, numL) = popNumber? popCtx1 Ok (Context.pushStack popCtx2 (Number (op numL numR))) -popNumber : Context -> Result [T Context I32] InterpreterErrors +popNumber : Context -> Result (Context, I32) InterpreterErrors popNumber = \ctx -> when Context.popStack ctx is - Ok (T popCtx (Number num)) -> Ok (T popCtx num) + Ok (popCtx, Number num) -> Ok (popCtx, num) Ok _ -> Err (NoNumberOnStack) Err EmptyStack -> Err EmptyStack -popLambda : Context -> Result [T Context (List U8)] InterpreterErrors +popLambda : Context -> Result (Context, List U8) InterpreterErrors popLambda = \ctx -> when Context.popStack ctx is - Ok (T popCtx (Lambda bytes)) -> Ok (T popCtx bytes) + Ok (popCtx, Lambda bytes) -> Ok (popCtx, bytes) Ok _ -> Err NoLambdaOnStack Err EmptyStack -> Err EmptyStack -popVariable : Context -> Result [T Context Variable] InterpreterErrors +popVariable : Context -> Result (Context, Variable) InterpreterErrors popVariable = \ctx -> when Context.popStack ctx is - Ok (T popCtx (Var var)) -> Ok (T popCtx var) + Ok (popCtx, Var var) -> Ok (popCtx, var) Ok _ -> Err NoVariableOnStack Err EmptyStack -> Err EmptyStack diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/File.roc b/crates/cli/tests/test-projects/false-interpreter/platform/File.roc index f17ecce8e7..ccb0cffe12 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/File.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/File.roc @@ -1,34 +1,30 @@ -module [line, withOpen, chunk, Handle] +module [line!, withOpen!, chunk!, Handle] -import pf.PlatformTasks +import pf.Host Handle := U64 -line : Handle -> Task Str * -line = \@Handle handle -> - PlatformTasks.getFileLine handle - |> Task.mapErr \_ -> crash "unreachable File.line" +line! : Handle => Str +line! = \@Handle handle -> + Host.getFileLine! handle -chunk : Handle -> Task (List U8) * -chunk = \@Handle handle -> - PlatformTasks.getFileBytes handle - |> Task.mapErr \_ -> crash "unreachable File.chunk" +chunk! : Handle => List U8 +chunk! = \@Handle handle -> + Host.getFileBytes! handle -open : Str -> Task Handle * -open = \path -> - PlatformTasks.openFile path - |> Task.mapErr \_ -> crash "unreachable File.open" - |> Task.map @Handle +open! : Str => Handle +open! = \path -> + Host.openFile! path + |> @Handle -close : Handle -> Task.Task {} * -close = \@Handle handle -> - PlatformTasks.closeFile handle - |> Task.mapErr \_ -> crash "unreachable File.close" +close! : Handle => {} +close! = \@Handle handle -> + Host.closeFile! handle -withOpen : Str, (Handle -> Task {} a) -> Task {} a -withOpen = \path, callback -> +withOpen! : Str, (Handle => a) => a +withOpen! = \path, callback! -> handle = open! path - result = callback handle |> Task.result! + result = callback! handle close! handle - Task.fromResult result + result diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc new file mode 100644 index 0000000000..c1662d998c --- /dev/null +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc @@ -0,0 +1,19 @@ +hosted Host + exposes [openFile!, closeFile!, getFileLine!, getFileBytes!, putLine!, putRaw!, getLine!, getChar!] + imports [] + +openFile! : Str => U64 + +closeFile! : U64 => {} + +getFileLine! : U64 => Str + +getFileBytes! : U64 => List U8 + +putLine! : Str => {} + +putRaw! : Str => {} + +getLine! : {} => Str + +getChar! : {} => U8 diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/PlatformTasks.roc b/crates/cli/tests/test-projects/false-interpreter/platform/PlatformTasks.roc deleted file mode 100644 index ada0ab7fb9..0000000000 --- a/crates/cli/tests/test-projects/false-interpreter/platform/PlatformTasks.roc +++ /dev/null @@ -1,21 +0,0 @@ -hosted PlatformTasks - exposes [openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getLine, getChar] - imports [] - -openFile : Str -> Task U64 {} - -closeFile : U64 -> Task {} {} - -withFileOpen : Str, (U64 -> Task ok err) -> Task {} {} - -getFileLine : U64 -> Task Str {} - -getFileBytes : U64 -> Task (List U8) {} - -putLine : Str -> Task {} {} - -putRaw : Str -> Task {} {} - -getLine : Task Str {} - -getChar : Task U8 {} diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc index c139724399..fc33e16ad6 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc @@ -1,16 +1,11 @@ -module [ - line, - char, -] +module [line!, char!] -import pf.PlatformTasks +import pf.Host -line : {} -> Task Str * -line = \{} -> - PlatformTasks.getLine - |> Task.mapErr \_ -> crash "unreachable Stdin.line" +line! : {} => Str +line! = \{} -> + Host.getLine! {} -char : {} -> Task U8 * -char = \{} -> - PlatformTasks.getChar - |> Task.mapErr \_ -> crash "unreachable Stdin.char" +char! : {} => U8 +char! = \{} -> + Host.getChar! {} diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc index 723dcd606e..9e56cc6ae1 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc @@ -1,13 +1,11 @@ -module [line, raw] +module [line!, raw!] -import pf.PlatformTasks +import pf.Host -line : Str -> Task {} * -line = \text -> - PlatformTasks.putLine text - |> Task.mapErr \_ -> crash "unreachable Stdout.line" +line! : Str => {} +line! = \text -> + Host.putLine! text -raw : Str -> Task {} * -raw = \text -> - PlatformTasks.putRaw text - |> Task.mapErr \_ -> crash "unreachable Stdout.raw" +raw! : Str => {} +raw! = \text -> + Host.putRaw! text diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/main.roc b/crates/cli/tests/test-projects/false-interpreter/platform/main.roc index 1fe4a8baee..62d643ed6d 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/main.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/main.roc @@ -1,9 +1,9 @@ platform "false-interpreter" - requires {} { main : Str -> Task {} [] } + requires {} { main! : Str => {} } exposes [] packages {} imports [] - provides [mainForHost] + provides [mainForHost!] -mainForHost : Str -> Task {} [] -mainForHost = \file -> main file +mainForHost! : Str => {} +mainForHost! = \file -> main! file diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/src/lib.rs b/crates/cli/tests/test-projects/false-interpreter/platform/src/lib.rs index 693e2d0815..416608eced 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/src/lib.rs +++ b/crates/cli/tests/test-projects/false-interpreter/platform/src/lib.rs @@ -20,20 +20,8 @@ fn file_handles() -> &'static Mutex>> { } extern "C" { - #[link_name = "roc__mainForHost_1_exposed_generic"] - fn roc_main(output: *mut u8, args: &RocStr); - - #[link_name = "roc__mainForHost_1_exposed_size"] - fn roc_main_size() -> i64; - - #[link_name = "roc__mainForHost_0_caller"] - fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); - - #[link_name = "roc__mainForHost_0_size"] - fn size_Fx() -> i64; - - #[link_name = "roc__mainForHost_0_result_size"] - fn size_Fx_result() -> i64; + #[link_name = "roc__mainForHost_1_exposed"] + fn roc_main(args: &RocStr); } #[no_mangle] @@ -116,86 +104,51 @@ pub extern "C" fn rust_main() -> i32 { .expect("Please pass a .false file as a command-line argument to the false interpreter!"); let arg = RocStr::from(arg.as_str()); - let size = unsafe { roc_main_size() } as usize; - - unsafe { - let buffer = roc_alloc(size, 1) as *mut u8; - - roc_main(buffer, &arg); - - // arg has been passed to roc now, and it assumes ownership. - // so we must not touch its refcount now - std::mem::forget(arg); - - let result = call_the_closure(buffer); - - roc_dealloc(buffer as _, 1); - - result - }; + roc_main(&arg); // Exit code 0 } -unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { - let size = size_Fx_result() as usize; - let buffer = roc_alloc(size, 1) as *mut u8; - - call_Fx( - // This flags pointer will never get dereferenced - MaybeUninit::uninit().as_ptr(), - closure_data_ptr as *const u8, - buffer as *mut u8, - ); - - roc_dealloc(buffer as _, 1); - 0 -} - #[no_mangle] -pub extern "C" fn roc_fx_getLine() -> RocResult { +pub extern "C" fn roc_fx_getLine() -> RocStr { let stdin = std::io::stdin(); let line1 = stdin.lock().lines().next().unwrap().unwrap(); - RocResult::ok(RocStr::from(line1.as_str())) + RocStr::from(line1.as_str()) } #[no_mangle] -pub extern "C" fn roc_fx_getChar() -> RocResult { +pub extern "C" fn roc_fx_getChar() -> u8 { let mut buffer = [0]; if let Err(ioerr) = std::io::stdin().lock().read_exact(&mut buffer[..]) { if ioerr.kind() == std::io::ErrorKind::UnexpectedEof { - RocResult::ok(u8::MAX) + u8::MAX } else { panic!("Got an unexpected error while reading char from stdin"); } } else { - RocResult::ok(buffer[0]) + buffer[0] } } #[no_mangle] -pub extern "C" fn roc_fx_putLine(line: &RocStr) -> RocResult<(), ()> { +pub extern "C" fn roc_fx_putLine(line: &RocStr) { let string = line.as_str(); println!("{}", string); let _ = std::io::stdout().lock().flush(); - - RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_putRaw(line: &RocStr) -> RocResult<(), ()> { +pub extern "C" fn roc_fx_putRaw(line: &RocStr) { let string = line.as_str(); print!("{}", string); let _ = std::io::stdout().lock().flush(); - - RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocResult { +pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocStr { let mut br_map = file_handles().lock().unwrap(); let br = br_map.get_mut(&br_id).unwrap(); let mut line1 = String::default(); @@ -203,11 +156,11 @@ pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocResult { br.read_line(&mut line1) .expect("Failed to read line from file"); - RocResult::ok(RocStr::from(line1.as_str())) + RocStr::from(line1.as_str()) } #[no_mangle] -pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocResult, ()> { +pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocList { let mut br_map = file_handles().lock().unwrap(); let br = br_map.get_mut(&br_id).unwrap(); let mut buffer = [0; 0x10 /* This is intentionally small to ensure correct implementation */]; @@ -216,18 +169,16 @@ pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocResult, ()> .read(&mut buffer[..]) .expect("Failed to read bytes from file"); - RocResult::ok(RocList::from_slice(&buffer[..count])) + RocList::from_slice(&buffer[..count]) } #[no_mangle] -pub extern "C" fn roc_fx_closeFile(br_id: u64) -> RocResult<(), ()> { +pub extern "C" fn roc_fx_closeFile(br_id: u64) { file_handles().lock().unwrap().remove(&br_id); - - RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult { +pub extern "C" fn roc_fx_openFile(name: &RocStr) -> u64 { let string = name.as_str(); match File::open(string) { Ok(f) => { @@ -236,7 +187,7 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult { file_handles().lock().unwrap().insert(br_id, br); - RocResult::ok(br_id) + br_id } Err(_) => { panic!( @@ -246,17 +197,3 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult { } } } - -#[no_mangle] -pub extern "C" fn roc_fx_withFileOpen(_name: &RocStr, _buffer: *const u8) -> RocResult<(), ()> { - // TODO: figure out accepting a closure in an fx and passing data to it. - // let f = File::open(name.as_str()).expect("Unable to open file"); - // let mut br = BufReader::new(f); - - // unsafe { - // let closure_data_ptr = buffer.offset(8); - // call_the_closure(closure_data_ptr); - // } - - RocResult::ok(()) -}