44
55import gleam/dynamic
66import gleam/dynamic/decode
7+ import gleam/int
78import gleam/list
9+ import gleam/option
810import gleam/result
911import gleam/string
1012
@@ -13,34 +15,210 @@ pub type Lua
1315
1416/// Represents the errors than can happend during the parsing and execution of Lua code
1517pub type LuaError {
16- /// There was an exception when compiling the Lua code.
17- LuaCompilerException ( messages : List ( String ) )
18+ /// The compilation process of the Lua code failed because of the presence of one or more compile errors .
19+ LuaCompileFailure ( errors : List ( LuaCompileError ) )
1820 /// The Lua environment threw an exception during code execution.
1921 LuaRuntimeException ( exception : LuaRuntimeExceptionKind , state : Lua )
2022 /// A certain key was not found in the Lua environment.
21- KeyNotFound
23+ KeyNotFound ( key : List ( String ) )
24+ /// A Lua source file was not found
25+ FileNotFound ( path : String )
2226 /// The value returned by the Lua environment could not be decoded using the provided decoder.
2327 UnexpectedResultType ( List ( decode . DecodeError ) )
2428 /// An error that could not be identified.
25- UnknownError
29+ UnknownError ( error : dynamic . Dynamic )
30+ }
31+
32+ /// Represents a Lua compilation error
33+ pub type LuaCompileError {
34+ LuaCompileError ( line : Int , kind : LuaCompileErrorKind , message : String )
35+ }
36+
37+ /// Represents the kind of a Lua compilation error
38+ pub type LuaCompileErrorKind {
39+ Parse
40+ Tokenize
2641}
2742
2843/// Represents the kind of exceptions that can happen at runtime during Lua code execution.
2944pub type LuaRuntimeExceptionKind {
3045 /// The exception that happens when trying to access an index that does not exists on a table (also happens when indexing non-table values).
31- IllegalIndex ( value : String , index : String )
46+ IllegalIndex ( index : String , value : String )
3247 /// The exception that happens when the `error` function is called.
33- ErrorCall ( messages : List ( String ) )
48+ ErrorCall ( message : String , level : option . Option ( Int ) )
3449 /// The exception that happens when trying to call a function that is not defined.
3550 UndefinedFunction ( value : String )
51+ /// The exception that happens when trying to call a method that is not defined for an object.
52+ UndefinedMethod ( object : String , method : String )
3653 /// The exception that happens when an invalid arithmetic operation is performed.
3754 BadArith ( operator : String , args : List ( String ) )
55+ /// The exception that happens when a function is called with incorrect arguments.
56+ Badarg ( function : String , args : List ( dynamic . Dynamic ) )
3857 /// The exception that happens when a call to assert is made passing a value that evalues to `false` as the first argument.
3958 AssertError ( message : String )
4059 /// An exception that could not be identified
4160 UnknownException
4261}
4362
63+ /// Turns a `glua.LuaError` value into a human-readable string
64+ ///
65+ /// ## Examples
66+ ///
67+ /// ```gleam
68+ /// let assert Error(e) = glua.eval(
69+ /// state: glua.new(),
70+ /// code: "if true end",
71+ /// using: decode.string
72+ /// )
73+ ///
74+ /// glua.format_error(e)
75+ /// // -> "Lua compile error: \n\nFailed to parse: error on line 1: syntax error before: 'end'"
76+ /// ```
77+ ///
78+ /// ```gleam
79+ /// let assert Error(e) = glua.eval(
80+ /// state: glua.new(),
81+ /// code: "local a = 1; local b = true; return a + b",
82+ /// using: decode.string
83+ /// )
84+ ///
85+ /// glua.format_error(e)
86+ /// // -> "Lua runtime exception: Bad arithmetic expression: 1 + true"
87+ /// ```
88+ ///
89+ /// ```gleam
90+ /// let assert Error(e) = glua.get(
91+ /// state: glua.new(),
92+ /// keys: ["a_value"],
93+ /// using: decode.string
94+ /// )
95+ ///
96+ /// glua.format_error(e)
97+ /// // -> "Key \"a_value\" not found"
98+ /// ```
99+ ///
100+ /// ```gleam
101+ /// let assert Error(e) = glua.eval_file(
102+ /// state: glua.new(),
103+ /// path: "my_lua_file.lua",
104+ /// using: decode.string
105+ /// )
106+ ///
107+ /// glua.format_error(e)
108+ /// // -> "Lua source file \"my_lua_file.lua\" not found"
109+ /// ```
110+ ///
111+ /// ```gleam
112+ /// let assert Error(e) = glua.eval(
113+ /// state: glua.new(),
114+ /// code: "return 1 + 1",
115+ /// using: decode.string
116+ /// )
117+ ///
118+ /// glua.format_error(e)
119+ /// // -> "Expected String, but found Int"
120+ /// ```
121+ pub fn format_error ( error : LuaError ) -> String {
122+ case error {
123+ LuaCompileFailure ( errors ) ->
124+ "Lua compile error: "
125+ <> "\n \n "
126+ <> string . join ( list . map ( errors , format_compile_error ) , with : "\n " )
127+ LuaRuntimeException ( exception , state ) -> {
128+ let base = "Lua runtime exception: " <> format_exception ( exception )
129+ let stacktrace = get_stacktrace ( state )
130+
131+ case stacktrace {
132+ "" -> base
133+ stacktrace -> base <> "\n \n " <> stacktrace
134+ }
135+ }
136+ KeyNotFound ( path ) ->
137+ "Key " <> "\" " <> string . join ( path , with : "." ) <> "\" " <> " not found"
138+ FileNotFound ( path ) ->
139+ "Lua source file " <> "\" " <> path <> "\" " <> " not found"
140+ UnexpectedResultType ( decode_errors ) ->
141+ list . map ( decode_errors , format_decode_error ) |> string . join ( with : "\n " )
142+ UnknownError ( error ) -> "Unknown error: " <> format_unknown_error ( error )
143+ }
144+ }
145+
146+ fn format_compile_error ( error : LuaCompileError ) -> String {
147+ let kind = case error . kind {
148+ Parse -> "parse"
149+ Tokenize -> "tokenize"
150+ }
151+
152+ "Failed to "
153+ <> kind
154+ <> ": error on line "
155+ <> int . to_string ( error . line )
156+ <> ": "
157+ <> error . message
158+ }
159+
160+ fn format_exception ( exception : LuaRuntimeExceptionKind ) -> String {
161+ case exception {
162+ IllegalIndex ( index , value ) ->
163+ "Invalid index "
164+ <> "\" "
165+ <> index
166+ <> "\" "
167+ <> " at object "
168+ <> "\" "
169+ <> value
170+ <> "\" "
171+ ErrorCall ( msg , level ) -> {
172+ let base = "Error call: " <> msg
173+
174+ case level {
175+ option . Some ( level ) -> base <> " at level " <> int . to_string ( level )
176+ option . None -> base
177+ }
178+ }
179+
180+ UndefinedFunction ( fun ) -> "Undefined function: " <> fun
181+ UndefinedMethod ( obj , method ) ->
182+ "Undefined method "
183+ <> "\" "
184+ <> method
185+ <> "\" "
186+ <> " for object: "
187+ <> "\" "
188+ <> obj
189+ <> "\" "
190+ BadArith ( operator , args ) ->
191+ "Bad arithmetic expression: "
192+ <> string . join ( args , with : " " <> operator <> " " )
193+
194+ Badarg ( function , args ) ->
195+ "Bad argument "
196+ <> string . join ( list . map ( args , format_lua_value ) , with : ", " )
197+ <> " for function "
198+ <> function
199+ AssertError ( msg ) -> "Assertion failed with message: " <> msg
200+ UnknownException -> "Unknown exception"
201+ }
202+ }
203+
204+ @ external ( erlang , "glua_ffi" , "get_stacktrace" )
205+ fn get_stacktrace ( state : Lua ) -> String
206+
207+ fn format_decode_error ( error : decode . DecodeError ) -> String {
208+ let base = "Expected " <> error . expected <> ", but found " <> error . found
209+
210+ case error . path {
211+ [ ] -> base
212+ path -> base <> " at " <> string . join ( path , with : "." )
213+ }
214+ }
215+
216+ @ external ( erlang , "luerl_lib" , "format_value" )
217+ fn format_lua_value ( v : anything) -> String
218+
219+ @ external ( erlang , "luerl_lib" , "format_error" )
220+ fn format_unknown_error ( error : dynamic . Dynamic ) -> String
221+
44222/// The exception that happens when a functi
45223/// Represents a chunk of Lua code that is already loaded into the Lua VM
46224pub type Chunk
@@ -235,7 +413,7 @@ fn sandbox_fun(msg: String) -> Value
235413///
236414/// ```gleam
237415/// glua.get(state: glua.new(), keys: ["non_existent"], using: decode.string)
238- /// // -> Error(glua.KeyNotFound)
416+ /// // -> Error(glua.KeyNotFound(["non_existent"]) )
239417/// ```
240418pub fn get (
241419 state lua : Lua ,
@@ -332,7 +510,7 @@ pub fn set(
332510 case do_ref_get ( lua , keys ) {
333511 Ok ( _ ) -> Ok ( # ( keys , lua ) )
334512
335- Error ( KeyNotFound ) -> {
513+ Error ( KeyNotFound ( _ ) ) -> {
336514 let # ( tbl , lua ) = alloc_table ( [ ] , lua )
337515 do_set ( lua , keys , tbl )
338516 |> result . map ( fn ( lua ) { # ( keys , lua ) } )
@@ -428,7 +606,7 @@ fn do_set_private(key: String, value: a, lua: Lua) -> Lua
428606///
429607/// assert glua.delete_private(lua, "my_value")
430608/// |> glua.get("my_value", decode.string)
431- /// == Error(glua.KeyNotFound)
609+ /// == Error(glua.KeyNotFound(["my_value"]) )
432610/// ```
433611pub fn delete_private ( state lua : Lua , key key : String ) -> Lua {
434612 do_delete_private ( key , lua )
@@ -604,6 +782,15 @@ fn do_ref_eval_chunk(
604782///
605783/// assert results == ["hello, world!"]
606784/// ```
785+ ///
786+ /// ```gleam
787+ /// glua.eval_file(
788+ /// state: glua.new(),
789+ /// path: "path/to/non/existent/file",
790+ /// using: decode.string
791+ /// )
792+ /// //-> Error(glua.FileNotFound(["path/to/non/existent/file"]))
793+ /// ```
607794pub fn eval_file (
608795 state lua : Lua ,
609796 path path : String ,
0 commit comments