mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-13 09:49:11 +03:00
Add false lang interpreter example
This commit is contained in:
parent
1fe20b0422
commit
0cd288f623
1
examples/false-interpreter/.gitignore
vendored
Normal file
1
examples/false-interpreter/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
false
|
69
examples/false-interpreter/Context.roc
Normal file
69
examples/false-interpreter/Context.roc
Normal file
@ -0,0 +1,69 @@
|
||||
interface Context
|
||||
exposes [ Context, Data, with, getChar, Option, consumeChar, pushStack, popStack, toStr ]
|
||||
imports [ base.File, base.Task.{ Task }, Variable.{ Variable } ]
|
||||
|
||||
Option a : [ Some a, None ]
|
||||
|
||||
# The underlying context of the current location within the file
|
||||
|
||||
# I want to change Number to I32, but now that everything is built out, I run into errors when doing so.
|
||||
Data : [ Lambda (List U8), Number I64, Var Variable ]
|
||||
Context : { data: Option File.Handle, index: Nat, buf: List U8, stack: List Data, vars: List Data }
|
||||
|
||||
pushStack: Context, Data -> Context
|
||||
pushStack = \ctx, data ->
|
||||
{ctx & stack: List.append ctx.stack data}
|
||||
|
||||
# The compiler fails to type check if I uncomment this.
|
||||
#popStack: Context -> Result [T Context Data] [EmptyStack]*
|
||||
popStack = \ctx ->
|
||||
when List.last ctx.stack is
|
||||
Ok val ->
|
||||
# This is terrible, but is the simplest way to drop the last element that I can think of.
|
||||
# Long term it looks like there is a List.dropLast builtin.
|
||||
poppedCtx = { ctx & stack: List.reverse (List.drop (List.reverse ctx.stack) 1)}
|
||||
Ok (T poppedCtx val)
|
||||
Err ListWasEmpty ->
|
||||
Err EmptyStack
|
||||
|
||||
toStrData: Data -> Str
|
||||
toStrData = \data ->
|
||||
when data is
|
||||
Lambda _ -> "[]"
|
||||
Number n -> Str.fromInt n
|
||||
Var v -> Variable.toStr v
|
||||
|
||||
toStr: Context -> Str
|
||||
toStr = \{stack, vars} ->
|
||||
stackStr = Str.joinWith (List.map stack toStrData) " "
|
||||
varsStr = Str.joinWith (List.map vars toStrData) " "
|
||||
"\n============\nStack: [\(stackStr)]\nVars: [\(varsStr)]\n============\n"
|
||||
|
||||
# The compiler fails to type check if I uncomment this.
|
||||
# with : Str, (Context -> Task {} *) -> Task {} *
|
||||
with = \path, callback ->
|
||||
handle <- Task.await (File.open path)
|
||||
{} <- Task.await (callback { data: Some handle, index: 0, buf: [], stack: [], vars: (List.repeat Variable.totalCount (Number 0)) })
|
||||
File.close handle
|
||||
|
||||
# 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 ]*
|
||||
getChar = \ctx ->
|
||||
when List.get ctx.buf ctx.index is
|
||||
Ok val -> Task.succeed (T val ctx)
|
||||
Err OutOfBounds ->
|
||||
when ctx.data is
|
||||
Some h ->
|
||||
chunk <- Task.await (File.chunk h)
|
||||
bytes = Str.toUtf8 chunk
|
||||
when List.first bytes is
|
||||
Ok val ->
|
||||
Task.succeed (T val {ctx & buf: bytes, index: 0 })
|
||||
Err ListWasEmpty ->
|
||||
Task.fail EndOfData
|
||||
None ->
|
||||
Task.fail EndOfData
|
||||
|
||||
consumeChar: Context -> Context
|
||||
consumeChar = \ctx ->
|
||||
{ ctx & index: ctx.index + 1 }
|
443
examples/false-interpreter/False.roc
Normal file
443
examples/false-interpreter/False.roc
Normal file
@ -0,0 +1,443 @@
|
||||
#!/usr/bin/env roc
|
||||
app "false"
|
||||
packages { base: "platform" }
|
||||
imports [ base.Task.{ Task }, base.Stdout, base.Stdin, Context.{ Context }, Variable.{ Variable } ]
|
||||
provides [ main ] to base
|
||||
|
||||
# An interpreter for the False programming language: https://strlen.com/false-language/
|
||||
# This is just a silly example to test this variety of program.
|
||||
# In general think of this as a program that parses a number of files and prints some output.
|
||||
# It has some extra contraints:
|
||||
# 1) The input files are considered too large to just read in at once. Instead it is read via buffer or line.
|
||||
# 2) The output is also considered too large to generate in memory. It must be printed as we go via buffer or line.
|
||||
|
||||
main : List Str -> Task {} *
|
||||
main = \filenames ->
|
||||
filenames
|
||||
|> List.walk (\filename, task -> Task.await task (\_ -> (interpretFile filename))) (Task.succeed {})
|
||||
|> Task.await (\_ -> Stdout.line "Completed all tasks successfully")
|
||||
|> Task.onFail (\StringErr e -> Stdout.line "Ran into problem:\n\(e)\n")
|
||||
|
||||
|
||||
interpretFile : Str -> Task {} [ StringErr Str ]*
|
||||
interpretFile = \filename ->
|
||||
{} <- Task.await (Stdout.line "\nInterpreting file: \(filename)\n")
|
||||
ctx <- Context.with filename
|
||||
result <- Task.attempt (interpretContext ctx)
|
||||
when result is
|
||||
Ok _ ->
|
||||
Stdout.line "\n\nDone\n"
|
||||
Err BadUtf8 ->
|
||||
Task.fail (StringErr "Failed to convert string from Utf8 bytes")
|
||||
Err DivByZero ->
|
||||
Task.fail (StringErr "Division by zero")
|
||||
Err EmptyStack ->
|
||||
Task.fail (StringErr "Tried to pop a value off of the stack when it was empty")
|
||||
Err InvalidBooleanValue ->
|
||||
Task.fail (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)")
|
||||
Err (InvalidChar char) ->
|
||||
Task.fail (StringErr "Ran into an invalid character with ascii code: \(char)")
|
||||
Err NoLambdaOnStack ->
|
||||
Task.fail (StringErr "Tried to run a lambda when no lambda was on the stack")
|
||||
Err (NoNumberOnStack alt) ->
|
||||
when alt is
|
||||
Lambda _ ->
|
||||
Task.fail (StringErr "Tried to load a number when no number was on the stack instead got a lambda")
|
||||
Var _ ->
|
||||
Task.fail (StringErr "Tried to load a number when no number was on the stack instead got a variable")
|
||||
Number _ ->
|
||||
Task.fail (StringErr "Tried to load a number when no number was on the stack instead got a ?number?")
|
||||
Err NoVariableOnStack ->
|
||||
Task.fail (StringErr "Tried to load a variable when no variable was on the stack")
|
||||
Err OutOfBounds ->
|
||||
Task.fail (StringErr "Tried to load from an offset that was outside of the stack")
|
||||
Err UnexpectedEndOfData ->
|
||||
Task.fail (StringErr "Hit end of data while still parsing something")
|
||||
|
||||
isDigit : U8 -> Bool
|
||||
isDigit = \char ->
|
||||
char >= 0x30 # `0`
|
||||
&& char <= 0x39 # `0`
|
||||
|
||||
isWhitespace : U8 -> Bool
|
||||
isWhitespace = \char ->
|
||||
char == 0xA # new line
|
||||
|| char == 0xB # carriage return
|
||||
|| char == 0x20 # space
|
||||
|| char == 0x9 # tab
|
||||
|
||||
interpretContext : Context -> Task Context [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
interpretContext = \ctx ->
|
||||
# If I move this definition below interpretContext it breaks.
|
||||
runLambda = \baseCtx, bytes ->
|
||||
lambdaCtx = {baseCtx & data: None, index: 0, buf: bytes}
|
||||
afterLambdaCtx <- Task.await (interpretContext lambdaCtx)
|
||||
# Merge lambda changes back into ctx.
|
||||
Task.succeed {baseCtx & stack: afterLambdaCtx.stack, vars: afterLambdaCtx.vars}
|
||||
|
||||
runWhile = \baseCtx, cond, body ->
|
||||
afterCondCtx <- Task.await (runLambda baseCtx cond)
|
||||
when popNumber afterCondCtx is
|
||||
Ok (T popCtx 0) ->
|
||||
Task.succeed popCtx
|
||||
Ok (T popCtx _) ->
|
||||
afterBodyCtx <- Task.await (runLambda popCtx body)
|
||||
runWhile afterBodyCtx cond body
|
||||
Err e -> Task.fail e
|
||||
|
||||
result <- Task.attempt (Context.getChar ctx)
|
||||
#{} <- Task.await (Stdout.raw (Context.toStr ctx))
|
||||
when result is
|
||||
Ok (T val newCtx) ->
|
||||
when val is
|
||||
0x21 -> # `!` execute lambda
|
||||
# I couldn't seem to move this into it's own function otherwise the compiler would get angry.
|
||||
when popLambda (Context.consumeChar newCtx) is
|
||||
Ok (T popCtx bytes) ->
|
||||
afterLambdaCtx <- Task.await (runLambda popCtx bytes)
|
||||
interpretContext afterLambdaCtx
|
||||
Err e ->
|
||||
Task.fail e
|
||||
0x3F -> # `?` if
|
||||
result2 =
|
||||
(T popCtx1 bytes) <- Result.after (popLambda (Context.consumeChar newCtx))
|
||||
(T popCtx2 n1) <- Result.after (popNumber popCtx1)
|
||||
Ok (T3 popCtx2 n1 bytes)
|
||||
when result2 is
|
||||
Ok (T3 popCtx2 0 _) -> interpretContext popCtx2
|
||||
Ok (T3 popCtx2 _ bytes) ->
|
||||
afterLambdaCtx <- Task.await (runLambda popCtx2 bytes)
|
||||
interpretContext afterLambdaCtx
|
||||
Err e -> Task.fail e
|
||||
0x23 -> # `#` while
|
||||
result2 =
|
||||
(T popCtx1 body) <- Result.after (popLambda (Context.consumeChar newCtx))
|
||||
(T popCtx2 cond) <- Result.after (popLambda popCtx1)
|
||||
Ok (T3 popCtx2 cond body)
|
||||
when result2 is
|
||||
Ok (T3 popCtx2 cond body) ->
|
||||
afterWhileCtx <- Task.await (runWhile popCtx2 cond body)
|
||||
interpretContext afterWhileCtx
|
||||
Err e -> Task.fail e
|
||||
0x24 -> # `$` dup
|
||||
when List.last newCtx.stack is
|
||||
Ok dupItem ->
|
||||
interpretContext (Context.pushStack (Context.consumeChar newCtx) dupItem)
|
||||
_ ->
|
||||
Task.fail EmptyStack
|
||||
0x25 -> # `%` drop
|
||||
consumeCtx = (Context.consumeChar newCtx)
|
||||
when Context.popStack consumeCtx is
|
||||
# Dropping with an empyt stack, all results here are fine
|
||||
Ok (T popCtx _) ->
|
||||
interpretContext popCtx
|
||||
Err _ ->
|
||||
interpretContext consumeCtx
|
||||
0x5C -> # `\` swap
|
||||
result2 =
|
||||
(T popCtx1 n1) <- Result.after (Context.popStack (Context.consumeChar newCtx))
|
||||
(T popCtx2 n2) <- Result.after (Context.popStack popCtx1)
|
||||
Ok (Context.pushStack (Context.pushStack popCtx2 n1) n2)
|
||||
when result2 is
|
||||
Ok a -> interpretContext a
|
||||
# Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack
|
||||
Err EmptyStack -> Task.fail EmptyStack
|
||||
0x40 -> # `@` rot
|
||||
result2 =
|
||||
(T popCtx1 n1) <- Result.after (Context.popStack (Context.consumeChar newCtx))
|
||||
(T popCtx2 n2) <- Result.after (Context.popStack popCtx1)
|
||||
(T popCtx3 n3) <- Result.after (Context.popStack popCtx2)
|
||||
Ok (Context.pushStack (Context.pushStack (Context.pushStack popCtx3 n2) n1) n3)
|
||||
when result2 is
|
||||
Ok a -> interpretContext a
|
||||
# Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack
|
||||
Err EmptyStack -> Task.fail EmptyStack
|
||||
0xC3 -> # `ø` pick
|
||||
# this is actually 2 bytes, 0xC3 0xB8
|
||||
result2 <- Task.attempt (Context.getChar (Context.consumeChar newCtx))
|
||||
when result2 is
|
||||
Ok (T 0xB8 newCtx2) ->
|
||||
result3 =
|
||||
(T popCtx index) <- Result.after (popNumber (Context.consumeChar newCtx2))
|
||||
# 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 <- Result.after (List.get popCtx.stack (Num.intCast offset))
|
||||
Ok (Context.pushStack popCtx stackVal)
|
||||
else
|
||||
Err OutOfBounds
|
||||
when result3 is
|
||||
Ok a -> interpretContext a
|
||||
Err e -> Task.fail e
|
||||
Ok (T x _) ->
|
||||
data = Str.fromInt (Num.intCast x)
|
||||
Task.fail (InvalidChar data)
|
||||
Err EndOfData ->
|
||||
Task.fail UnexpectedEndOfData
|
||||
0x4F -> # `O` also treat this as pick for easier script writing
|
||||
result2 =
|
||||
(T popCtx index) <- Result.after (popNumber (Context.consumeChar 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
|
||||
if offset >= 0 then
|
||||
stackVal <- Result.after (List.get popCtx.stack (Num.intCast offset))
|
||||
Ok (Context.pushStack popCtx stackVal)
|
||||
else
|
||||
Err OutOfBounds
|
||||
when result2 is
|
||||
Ok a -> interpretContext a
|
||||
Err e -> Task.fail e
|
||||
0x27 -> # `'` load next char
|
||||
result2 <- Task.attempt (Context.getChar (Context.consumeChar newCtx))
|
||||
when result2 is
|
||||
Ok (T x newCtx2) ->
|
||||
interpretContext (Context.pushStack (Context.consumeChar newCtx2) (Number (Num.intCast x)))
|
||||
Err EndOfData ->
|
||||
Task.fail UnexpectedEndOfData
|
||||
0x2B -> # `+` add
|
||||
afterOpCtx <- Task.await (binaryOp (Context.consumeChar newCtx) Num.addWrap)
|
||||
interpretContext afterOpCtx
|
||||
0x2D -> # `-` sub
|
||||
afterOpCtx <- Task.await (binaryOp (Context.consumeChar newCtx) Num.subWrap)
|
||||
interpretContext afterOpCtx
|
||||
0x2A -> # `*` mul
|
||||
afterOpCtx <- Task.await (binaryOp (Context.consumeChar newCtx) Num.mulWrap)
|
||||
interpretContext afterOpCtx
|
||||
0x2F -> # `/` div
|
||||
# Due to possible division by zero error, this must be handled specially.
|
||||
divResult =
|
||||
(T popCtx1 numR) <- Result.after (popNumber (Context.consumeChar newCtx))
|
||||
(T popCtx2 numL) <- Result.after (popNumber popCtx1)
|
||||
res <- Result.after (Num.divFloor numL numR)
|
||||
Ok (Context.pushStack popCtx2 (Number res))
|
||||
when divResult is
|
||||
Ok resCtx ->
|
||||
interpretContext resCtx
|
||||
Err e ->
|
||||
Task.fail e
|
||||
0x26 -> # `&` bitwise and
|
||||
afterOpCtx <- Task.await (binaryOp (Context.consumeChar newCtx) Num.bitwiseAnd)
|
||||
interpretContext afterOpCtx
|
||||
0x7C -> # `|` bitwise or
|
||||
afterOpCtx <- Task.await (binaryOp (Context.consumeChar newCtx) Num.bitwiseOr)
|
||||
interpretContext afterOpCtx
|
||||
0x3D -> # `=` equals
|
||||
afterOpCtx <- Task.await (binaryOp (Context.consumeChar newCtx) (\a, b ->
|
||||
if a == b then
|
||||
-1
|
||||
else
|
||||
0
|
||||
))
|
||||
interpretContext afterOpCtx
|
||||
0x3E -> # `>` greater than
|
||||
afterOpCtx <- Task.await (binaryOp (Context.consumeChar newCtx) (\a, b ->
|
||||
if a > b then
|
||||
-1
|
||||
else
|
||||
0
|
||||
))
|
||||
interpretContext afterOpCtx
|
||||
0x5F -> # `_` negate
|
||||
afterOpCtx <- Task.await (unaryOp (Context.consumeChar newCtx) Num.neg)
|
||||
interpretContext afterOpCtx
|
||||
0x7E -> # `~` bitwise not
|
||||
afterOpCtx <- Task.await (unaryOp (Context.consumeChar newCtx) (\x -> Num.bitwiseXor x -1)) # xor with -1 should be bitwise not
|
||||
interpretContext afterOpCtx
|
||||
0x2C -> # `,` write char
|
||||
when popNumber (Context.consumeChar newCtx) is
|
||||
Ok (T popCtx num) ->
|
||||
when Str.fromUtf8 [Num.intCast num] is
|
||||
Ok str ->
|
||||
{} <- Task.await (Stdout.raw str)
|
||||
interpretContext popCtx
|
||||
Err _ ->
|
||||
Task.fail BadUtf8
|
||||
Err e ->
|
||||
Task.fail e
|
||||
0x2E -> # `.` write int
|
||||
when popNumber (Context.consumeChar newCtx) is
|
||||
Ok (T popCtx num) ->
|
||||
{} <- Task.await (Stdout.raw (Str.fromInt num))
|
||||
interpretContext popCtx
|
||||
Err e ->
|
||||
Task.fail e
|
||||
0x5E -> # `^` read char as int
|
||||
char <- Task.await Stdin.char
|
||||
interpretContext (Context.pushStack (Context.consumeChar newCtx) (Number (Num.intCast char)))
|
||||
0x3A -> # `:` store to variable
|
||||
result2 =
|
||||
(T popCtx1 var) <- Result.after (popVariable (Context.consumeChar newCtx))
|
||||
(T popCtx2 n1) <- Result.after (Context.popStack popCtx1)
|
||||
Ok {popCtx2 & vars: List.set popCtx2.vars (Variable.toIndex var) n1}
|
||||
when result2 is
|
||||
Ok a -> interpretContext a
|
||||
Err e -> Task.fail e
|
||||
0x3B -> # `;` load from variable
|
||||
result2 =
|
||||
(T popCtx var) <- Result.after (popVariable (Context.consumeChar newCtx))
|
||||
elem <- Result.after (List.get popCtx.vars (Variable.toIndex var))
|
||||
Ok (Context.pushStack popCtx elem)
|
||||
when result2 is
|
||||
Ok a -> interpretContext a
|
||||
Err e -> Task.fail e
|
||||
0x22 -> # `"` string start
|
||||
afterStringCtx <- Task.await (printString (Context.consumeChar newCtx))
|
||||
interpretContext afterStringCtx
|
||||
0x5B -> # `[` lambda start
|
||||
afterLambdaCtx <- Task.await (pushLambda (Context.consumeChar newCtx))
|
||||
interpretContext afterLambdaCtx
|
||||
0x7B -> # `{` comment start
|
||||
afterCommentCtx <- Task.await (consumeComment (Context.consumeChar newCtx))
|
||||
interpretContext afterCommentCtx
|
||||
0xE1 -> # `ß` flush
|
||||
# This is supposed to flush io buffers. We don't buffer, so it does nothing
|
||||
interpretContext (Context.consumeChar newCtx)
|
||||
x if isDigit x -> # number start
|
||||
afterNumberCtx <- Task.await (pushNumber newCtx)
|
||||
interpretContext afterNumberCtx
|
||||
x if isWhitespace x -> interpretContext (Context.consumeChar newCtx)
|
||||
x ->
|
||||
when (Variable.fromUtf8 x) is # letters are variable names
|
||||
Ok var ->
|
||||
interpretContext (Context.pushStack (Context.consumeChar newCtx) (Var var))
|
||||
Err _ ->
|
||||
data = Str.fromInt (Num.intCast x)
|
||||
Task.fail (InvalidChar data)
|
||||
Err EndOfData ->
|
||||
# Computation complete.
|
||||
Task.succeed ctx
|
||||
|
||||
unaryOp: Context, (I64 -> I64) -> Task Context [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
unaryOp = \ctx, op ->
|
||||
result =
|
||||
(T popCtx num) <- Result.after (popNumber ctx)
|
||||
Ok (Context.pushStack popCtx (Number (op num)))
|
||||
when result is
|
||||
Ok resCtx ->
|
||||
Task.succeed resCtx
|
||||
Err e ->
|
||||
Task.fail e
|
||||
|
||||
binaryOp: Context, (I64, I64 -> I64) -> Task Context [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
binaryOp = \ctx, op ->
|
||||
result =
|
||||
(T popCtx1 numR) <- Result.after (popNumber ctx)
|
||||
(T popCtx2 numL) <- Result.after (popNumber popCtx1)
|
||||
Ok (Context.pushStack popCtx2 (Number (op numL numR)))
|
||||
when result is
|
||||
Ok resCtx ->
|
||||
Task.succeed resCtx
|
||||
Err e ->
|
||||
Task.fail e
|
||||
|
||||
|
||||
popNumber: Context -> Result [T Context I64] [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
popNumber = \ctx ->
|
||||
when Context.popStack ctx is
|
||||
Ok (T popCtx (Number num)) ->
|
||||
Ok (T popCtx num)
|
||||
Ok (T _ alt) ->
|
||||
Err (NoNumberOnStack alt)
|
||||
Err EmptyStack ->
|
||||
Err EmptyStack
|
||||
|
||||
popLambda: Context -> Result [T Context (List U8)] [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
popLambda = \ctx ->
|
||||
when Context.popStack ctx is
|
||||
Ok (T popCtx (Lambda bytes)) ->
|
||||
Ok (T popCtx bytes)
|
||||
Ok _ ->
|
||||
Err NoLambdaOnStack
|
||||
Err EmptyStack ->
|
||||
Err EmptyStack
|
||||
|
||||
popVariable: Context -> Result [T Context Variable] [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
popVariable = \ctx ->
|
||||
when Context.popStack ctx is
|
||||
Ok (T popCtx (Var var)) ->
|
||||
Ok (T popCtx var)
|
||||
Ok _ ->
|
||||
Err NoVariableOnStack
|
||||
Err EmptyStack ->
|
||||
Err EmptyStack
|
||||
|
||||
# This has to be a bit more complex than other options because they can be nested.
|
||||
# Also, it puts the lambda on the stack
|
||||
pushLambda: Context -> Task Context [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
pushLambda = \ctx ->
|
||||
(T newCtx bytes) <- Task.await (pushLambdaHelper ctx [] 0)
|
||||
Task.succeed (Context.pushStack newCtx (Lambda bytes))
|
||||
|
||||
pushLambdaHelper: Context, List U8, U64 -> Task [ T Context (List U8) ] [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
pushLambdaHelper = \ctx, base, depth ->
|
||||
result <- Task.attempt (Context.getChar ctx)
|
||||
when result is
|
||||
Ok (T val newCtx) ->
|
||||
if val == 0x5B then # start of a nested lambda `[`
|
||||
pushLambdaHelper (Context.consumeChar newCtx) (List.append base val) (depth + 1)
|
||||
else if val != 0x5D then # not lambda end `]`
|
||||
pushLambdaHelper (Context.consumeChar newCtx) (List.append base val) depth
|
||||
else if depth == 0 then
|
||||
Task.succeed (T (Context.consumeChar newCtx) base)
|
||||
else # Still need to finish nested lambda
|
||||
pushLambdaHelper (Context.consumeChar newCtx) (List.append base val) (depth - 1)
|
||||
Err EndOfData ->
|
||||
Task.fail UnexpectedEndOfData
|
||||
|
||||
pushNumber: Context -> Task Context [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
pushNumber = \ctx ->
|
||||
pushNumberHelper ctx 0
|
||||
|
||||
pushNumberHelper: Context, I64 -> Task Context [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
pushNumberHelper = \ctx, accum ->
|
||||
result <- Task.attempt (Context.getChar ctx)
|
||||
when result is
|
||||
Ok (T val newCtx) ->
|
||||
if isDigit val then
|
||||
pushNumberHelper (Context.consumeChar newCtx) (accum * 10 + (Num.intCast (val - 0x30)))
|
||||
else
|
||||
# Push the number onto the stack.
|
||||
Task.succeed (Context.pushStack newCtx (Number accum))
|
||||
Err EndOfData ->
|
||||
# Unlike many other cases, this is valid.
|
||||
# Still push the number to the stack.
|
||||
Task.succeed (Context.pushStack ctx (Number accum))
|
||||
|
||||
|
||||
# It seems that I need to specify all error types in every function for some reason.
|
||||
# I feel like this should just need to say UnexpectedEndOfData.
|
||||
printString : Context -> Task Context [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
printString = \ctx ->
|
||||
(T afterStringCtx bytes) <- Task.await (printStringHelper ctx [])
|
||||
when Str.fromUtf8 bytes is
|
||||
Ok str ->
|
||||
{} <- Task.await (Stdout.raw str)
|
||||
Task.succeed afterStringCtx
|
||||
Err _ ->
|
||||
Task.fail BadUtf8
|
||||
|
||||
printStringHelper : Context, List U8 -> Task [ T Context (List U8) ] [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
printStringHelper = \ctx, base ->
|
||||
result <- Task.attempt (Context.getChar ctx)
|
||||
when result is
|
||||
Ok (T val newCtx) ->
|
||||
if val != 0x22 then # not string end `"`
|
||||
printStringHelper (Context.consumeChar newCtx) (List.append base val)
|
||||
else
|
||||
Task.succeed (T (Context.consumeChar newCtx) base )
|
||||
Err EndOfData ->
|
||||
Task.fail UnexpectedEndOfData
|
||||
|
||||
consumeComment : Context -> Task Context [ BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, NoLambdaOnStack, NoNumberOnStack Context.Data, NoVariableOnStack, OutOfBounds, UnexpectedEndOfData ]*
|
||||
consumeComment = \ctx ->
|
||||
result <- Task.attempt (Context.getChar ctx)
|
||||
when result is
|
||||
Ok (T val newCtx) ->
|
||||
if val != 0x7D then # not comment end `}`
|
||||
consumeComment (Context.consumeChar newCtx)
|
||||
else
|
||||
Task.succeed (Context.consumeChar newCtx)
|
||||
Err EndOfData ->
|
||||
Task.fail UnexpectedEndOfData
|
34
examples/false-interpreter/Variable.roc
Normal file
34
examples/false-interpreter/Variable.roc
Normal file
@ -0,0 +1,34 @@
|
||||
interface Variable
|
||||
exposes [ Variable, fromUtf8, toIndex, totalCount, toStr ]
|
||||
imports [ ]
|
||||
|
||||
# Variables in False can only be single letters. Thus, the valid variables are "a" to "z".
|
||||
# This opaque type deals with ensure we always have valid variables.
|
||||
|
||||
Variable : [ @Variable U8 ]
|
||||
|
||||
totalCount: Nat
|
||||
totalCount =
|
||||
0x7A # "z"
|
||||
- 0x61 # "a"
|
||||
+ 1
|
||||
|
||||
toStr : Variable -> Str
|
||||
toStr = \@Variable char ->
|
||||
when Str.fromUtf8 [char] is
|
||||
Ok str -> str
|
||||
_ -> "_"
|
||||
|
||||
fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ]
|
||||
fromUtf8 = \char ->
|
||||
if char >= 0x61 # "a"
|
||||
&& char <= 0x7A # "z"
|
||||
then
|
||||
Ok (@Variable char)
|
||||
else
|
||||
Err InvalidVariableUtf8
|
||||
|
||||
toIndex : Variable -> Nat
|
||||
toIndex = \@Variable char ->
|
||||
Num.intCast (char - 0x61) # "a"
|
||||
# List.first (Str.toUtf8 "a")
|
5
examples/false-interpreter/examples/bottles.false
Normal file
5
examples/false-interpreter/examples/bottles.false
Normal file
@ -0,0 +1,5 @@
|
||||
{ False version of 99 Bottles by Marcus Comstedt (marcus@lysator.liu.se) }
|
||||
[$0=["no more bottles"]?$1=["One bottle"]?$1>[$." bottles"]?%" of beer"]b:
|
||||
100[$0>][$b;!" on the wall, "$b;!".
|
||||
"1-"Take one down, pass it around, "$b;!" on the wall.
|
||||
"]#%
|
9
examples/false-interpreter/examples/crc32.false
Normal file
9
examples/false-interpreter/examples/crc32.false
Normal file
@ -0,0 +1,9 @@
|
||||
{ This currently doesn't run cause the interpreter will stack overflow, even in optimized build}
|
||||
|
||||
{ unix cksum, CRC32. -- Jonathan Neuschäfer <j.neuschaefer@gmx.net> }
|
||||
[[$0>][\2*\1-]#%]l:[0\128[$0>][$2O&0>[$@\64/x;!@@$g;*@x;!@@]?2/]#%%]h:
|
||||
[[$0>][\2/\1-]#%]r:[1O$8 28l;!1-&$@=~\24r;!\[128|]?x;!h;!\8l;!x;!]s:79764919g:
|
||||
[q1_0[\1+\^$1_>]s;#%@%\$@[1O0>][1O255&s;!\8r;!\]#~n;!32,%.10,]m:[$2O&@@|~|~]x:
|
||||
[$0\>\1O[$u;!]?\~[$.]?%]n:[h;y:[3+O]h:255[$0>][$y;!\1-]#m;!256[$0>][\%1-]#%]o:
|
||||
[1000$$**0@[$$0\>\4O\>~|][2O-\1+\]#\.\[10/$0>][\$2O/$.2O*-\]#%%]u: {width: 78}
|
||||
{ usage: run m for "main" or o for "optimized" (builds a lookup table) } o;!
|
11
examples/false-interpreter/examples/hello.false
Normal file
11
examples/false-interpreter/examples/hello.false
Normal file
@ -0,0 +1,11 @@
|
||||
{ This is a comment}
|
||||
|
||||
|
||||
{ White space doesn't matter}
|
||||
|
||||
|
||||
|
||||
{ Strings in False just automatically print...So, "Hello, World!", I guess }
|
||||
"Hello, World!
|
||||
"
|
||||
{ Note the new line created by the white space that matters in strings }
|
1
examples/false-interpreter/examples/in.txt
Normal file
1
examples/false-interpreter/examples/in.txt
Normal file
@ -0,0 +1 @@
|
||||
a
|
10
examples/false-interpreter/examples/odd_words.false
Normal file
10
examples/false-interpreter/examples/odd_words.false
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
Dijkstra's odd words Problem
|
||||
You'll need to enter a sentence in the Input box, ending with a period. Odd words are reversed.
|
||||
}
|
||||
|
||||
[[$' =][%^]#]b:
|
||||
[$$'.=\' =|~]w:
|
||||
[$'.=~[' ,]?]s:
|
||||
[w;![^o;!\,]?]o:
|
||||
^b;![$'.=~][w;[,^]#b;!s;!o;!b;!s;!]#,
|
2
examples/false-interpreter/examples/primes.false
Normal file
2
examples/false-interpreter/examples/primes.false
Normal file
@ -0,0 +1,2 @@
|
||||
{This prints all of the primes from 1 to 99}
|
||||
99 9[1-$][\$@$@$@$@\/*=[1-$$[%\1-$@]?0=[\$.' ,\]?]?]#
|
3
examples/false-interpreter/examples/sqrt.false
Normal file
3
examples/false-interpreter/examples/sqrt.false
Normal file
@ -0,0 +1,3 @@
|
||||
{This will roughly calculate the sqrt of the number pushed here}
|
||||
2000000
|
||||
[\$@$@\/+2/]r: [127r;!r;!r;!r;!r;!r;!r;!\%]s: s;!.
|
21
examples/false-interpreter/platform/Cargo.lock
generated
Normal file
21
examples/false-interpreter/platform/Cargo.lock
generated
Normal file
@ -0,0 +1,21 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"roc_std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
|
||||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
15
examples/false-interpreter/platform/Cargo.toml
Normal file
15
examples/false-interpreter/platform/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
roc_std = { path = "../../../roc_std" }
|
||||
libc = "0.2"
|
||||
|
||||
[workspace]
|
27
examples/false-interpreter/platform/File.roc
Normal file
27
examples/false-interpreter/platform/File.roc
Normal file
@ -0,0 +1,27 @@
|
||||
interface File
|
||||
exposes [ line, Handle, withOpen, open, close, chunk ]
|
||||
imports [ fx.Effect, Task.{ Task } ]
|
||||
|
||||
Handle: [ @Handle U64 ]
|
||||
|
||||
line : Handle -> Task.Task Str *
|
||||
line = \@Handle handle -> Effect.after (Effect.getFileLine handle) Task.succeed
|
||||
|
||||
chunk : Handle -> Task.Task Str *
|
||||
chunk = \@Handle handle -> Effect.after (Effect.getFileBytes handle) Task.succeed
|
||||
|
||||
open : Str -> Task.Task Handle *
|
||||
open = \path ->
|
||||
Effect.openFile path
|
||||
|> Effect.map (\id -> @Handle id)
|
||||
|> Effect.after Task.succeed
|
||||
|
||||
close : Handle -> Task.Task {} *
|
||||
close = \@Handle handle -> Effect.after (Effect.closeFile handle) Task.succeed
|
||||
|
||||
# The compiler fails to type check if I uncomment this.
|
||||
# withOpen : Str, (Handle -> Task {} *) -> Task {} *
|
||||
withOpen = \path, callback ->
|
||||
handle <- Task.await (open path)
|
||||
{} <- Task.await (callback handle)
|
||||
close handle
|
22
examples/false-interpreter/platform/Package-Config.roc
Normal file
22
examples/false-interpreter/platform/Package-Config.roc
Normal file
@ -0,0 +1,22 @@
|
||||
platform examples/cli
|
||||
requires {}{ main : List Str -> Task {} [] } # TODO FIXME
|
||||
exposes []
|
||||
packages {}
|
||||
imports [ Task.{ Task } ]
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect
|
||||
{
|
||||
openFile : Str -> Effect U64,
|
||||
closeFile : U64 -> Effect {},
|
||||
withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {},
|
||||
getFileLine : U64 -> Effect Str,
|
||||
getFileBytes : U64 -> Effect Str,
|
||||
putLine : Str -> Effect {},
|
||||
putRaw : Str -> Effect {},
|
||||
# Is there a limit to the number of effect, uncomment the next line and it crashes
|
||||
#getLine : Effect Str,
|
||||
getChar : Effect I8
|
||||
}
|
||||
|
||||
mainForHost : List Str -> Task {} [] as Fx
|
||||
mainForHost = \list -> main list
|
9
examples/false-interpreter/platform/Stdin.roc
Normal file
9
examples/false-interpreter/platform/Stdin.roc
Normal file
@ -0,0 +1,9 @@
|
||||
interface Stdin
|
||||
exposes [ char ]
|
||||
imports [ fx.Effect, Task ]
|
||||
|
||||
#line : Task.Task Str *
|
||||
#line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice
|
||||
|
||||
char : Task.Task I8 *
|
||||
char = Effect.after Effect.getChar Task.succeed # TODO FIXME Effect.getLine should suffice
|
9
examples/false-interpreter/platform/Stdout.roc
Normal file
9
examples/false-interpreter/platform/Stdout.roc
Normal file
@ -0,0 +1,9 @@
|
||||
interface Stdout
|
||||
exposes [ line, raw ]
|
||||
imports [ fx.Effect, Task.{ Task } ]
|
||||
|
||||
line : Str -> Task {} *
|
||||
line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {})
|
||||
|
||||
raw : Str -> Task {} *
|
||||
raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {})
|
44
examples/false-interpreter/platform/Task.roc
Normal file
44
examples/false-interpreter/platform/Task.roc
Normal file
@ -0,0 +1,44 @@
|
||||
interface Task
|
||||
exposes [ Task, succeed, fail, await, map, onFail, attempt ]
|
||||
imports [ fx.Effect ]
|
||||
|
||||
|
||||
Task ok err : Effect.Effect (Result ok err)
|
||||
|
||||
|
||||
succeed : val -> Task val *
|
||||
succeed = \val ->
|
||||
Effect.always (Ok val)
|
||||
|
||||
|
||||
fail : err -> Task * err
|
||||
fail = \val ->
|
||||
Effect.always (Err val)
|
||||
|
||||
attempt : Task a b, (Result a b -> Task c d) -> Task c d
|
||||
attempt = \effect, transform ->
|
||||
Effect.after effect \result ->
|
||||
when result is
|
||||
Ok ok -> transform (Ok ok)
|
||||
Err err -> transform (Err err)
|
||||
|
||||
await : Task a err, (a -> Task b err) -> Task b err
|
||||
await = \effect, transform ->
|
||||
Effect.after effect \result ->
|
||||
when result is
|
||||
Ok a -> transform a
|
||||
Err err -> Task.fail err
|
||||
|
||||
onFail : Task ok a, (a -> Task ok b) -> Task ok b
|
||||
onFail = \effect, transform ->
|
||||
Effect.after effect \result ->
|
||||
when result is
|
||||
Ok a -> Task.succeed a
|
||||
Err err -> transform err
|
||||
|
||||
map : Task a err, (a -> b) -> Task b err
|
||||
map = \effect, transform ->
|
||||
Effect.after effect \result ->
|
||||
when result is
|
||||
Ok a -> Task.succeed (transform a)
|
||||
Err err -> Task.fail err
|
7
examples/false-interpreter/platform/host.c
Normal file
7
examples/false-interpreter/platform/host.c
Normal file
@ -0,0 +1,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
extern int rust_main();
|
||||
|
||||
int main() {
|
||||
return rust_main();
|
||||
}
|
214
examples/false-interpreter/platform/src/lib.rs
Normal file
214
examples/false-interpreter/platform/src/lib.rs
Normal file
@ -0,0 +1,214 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use core::alloc::Layout;
|
||||
use core::ffi::c_void;
|
||||
use core::mem::MaybeUninit;
|
||||
use libc;
|
||||
use roc_std::{RocList, RocStr};
|
||||
use std::env;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "roc__mainForHost_1_exposed"]
|
||||
fn roc_main(args: RocList<RocStr>, output: *mut u8) -> ();
|
||||
|
||||
#[link_name = "roc__mainForHost_size"]
|
||||
fn roc_main_size() -> i64;
|
||||
|
||||
#[link_name = "roc__mainForHost_1_Fx_caller"]
|
||||
fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8) -> ();
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[link_name = "roc__mainForHost_1_Fx_size"]
|
||||
fn size_Fx() -> i64;
|
||||
|
||||
#[link_name = "roc__mainForHost_1_Fx_result_size"]
|
||||
fn size_Fx_result() -> i64;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
||||
libc::malloc(size)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_realloc(
|
||||
c_ptr: *mut c_void,
|
||||
new_size: usize,
|
||||
_old_size: usize,
|
||||
_alignment: u32,
|
||||
) -> *mut c_void {
|
||||
libc::realloc(c_ptr, new_size)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
||||
libc::free(c_ptr)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
|
||||
match tag_id {
|
||||
0 => {
|
||||
let slice = CStr::from_ptr(c_ptr as *const c_char);
|
||||
let string = slice.to_str().unwrap();
|
||||
eprintln!("Roc hit a panic: {}", string);
|
||||
std::process::exit(1);
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn rust_main() -> i32 {
|
||||
let args: Vec<RocStr> = env::args()
|
||||
.skip(1)
|
||||
.map(|arg| RocStr::from_slice(arg.as_bytes()))
|
||||
.collect();
|
||||
let args = RocList::<RocStr>::from_slice(&args);
|
||||
|
||||
let size = unsafe { roc_main_size() } as usize;
|
||||
let layout = Layout::array::<u8>(size).unwrap();
|
||||
|
||||
unsafe {
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
let buffer = std::alloc::alloc(layout);
|
||||
|
||||
roc_main(args, buffer);
|
||||
|
||||
let result = call_the_closure(buffer);
|
||||
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
// Exit code
|
||||
0
|
||||
}
|
||||
|
||||
unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
|
||||
let size = size_Fx_result() as usize;
|
||||
let layout = Layout::array::<u8>(size).unwrap();
|
||||
let buffer = std::alloc::alloc(layout) 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,
|
||||
);
|
||||
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_getLine() -> RocStr {
|
||||
use std::io::{self, BufRead};
|
||||
|
||||
let stdin = io::stdin();
|
||||
let line1 = stdin.lock().lines().next().unwrap().unwrap();
|
||||
|
||||
RocStr::from_slice(line1.as_bytes())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_getChar() -> u8 {
|
||||
use std::io::{self, BufRead};
|
||||
let mut buffer = [0];
|
||||
|
||||
if let Err(ioerr) = io::stdin().lock().read_exact(&mut buffer[..]) {
|
||||
if ioerr.kind() == io::ErrorKind::UnexpectedEof {
|
||||
u8::MAX
|
||||
} else {
|
||||
panic!("Got an unexpected error while reading char from stdin");
|
||||
}
|
||||
} else {
|
||||
buffer[0]
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_putLine(line: RocStr) -> () {
|
||||
let bytes = line.as_slice();
|
||||
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
|
||||
println!("{}", string);
|
||||
std::io::stdout().lock().flush();
|
||||
|
||||
// don't mess with the refcount!
|
||||
core::mem::forget(line);
|
||||
|
||||
()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_putRaw(line: RocStr) -> () {
|
||||
let bytes = line.as_slice();
|
||||
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
|
||||
print!("{}", string);
|
||||
std::io::stdout().lock().flush();
|
||||
|
||||
// don't mess with the refcount!
|
||||
core::mem::forget(line);
|
||||
|
||||
()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_getFileLine(br_ptr: *mut BufReader<File>) -> RocStr {
|
||||
let br = unsafe { &mut *br_ptr };
|
||||
let mut line1 = String::default();
|
||||
|
||||
br.read_line(&mut line1)
|
||||
.expect("Failed to read line from file");
|
||||
|
||||
RocStr::from_slice(line1.as_bytes())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_getFileBytes(br_ptr: *mut BufReader<File>) -> RocStr {
|
||||
let br = unsafe { &mut *br_ptr };
|
||||
let mut buffer = [0; 0x10 /* This is intentially small to ensure correct implementation */];
|
||||
|
||||
let count = br
|
||||
.read(&mut buffer[..])
|
||||
.expect("Failed to read bytes from file");
|
||||
|
||||
RocStr::from_slice(&buffer[..count])
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_closeFile(br_ptr: *mut BufReader<File>) -> () {
|
||||
unsafe {
|
||||
Box::from_raw(br_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_openFile(name: RocStr) -> *mut BufReader<File> {
|
||||
let f = File::open(name.as_str()).expect("Unable to open file");
|
||||
let br = BufReader::new(f);
|
||||
|
||||
Box::into_raw(Box::new(br))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn roc_fx_withFileOpen(name: RocStr, buffer: *const u8) -> () {
|
||||
// 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);
|
||||
// }
|
||||
|
||||
// // don't mess with the refcount!
|
||||
// core::mem::forget(name);
|
||||
|
||||
()
|
||||
}
|
Loading…
Reference in New Issue
Block a user