A code generation library for Gleam
2

Configure Feed

Select the types of activity you want to include in your feed.

Replace phantom types with internal type system

Gears (Oct 1, 2025, 7:03 PM +0100) b694bc60 849842b7

+468 -161
+333 -152
src/trick.gleam
··· 1 1 import glam/doc.{type Document} 2 2 import gleam/bool 3 + import gleam/dict.{type Dict} 3 4 import gleam/float 4 5 import gleam/int 5 6 import gleam/list 6 7 import gleam/option.{type Option, None, Some} 8 + import gleam/result 7 9 import lazy_const 8 10 import splitter 9 11 10 - pub opaque type Expression(type_) { 11 - Expression(document: Document) 12 + pub opaque type Expression { 13 + Expression(compile: fn(State) -> Result(CompiledExpression, Error)) 14 + } 15 + 16 + pub type Error { 17 + TypeMismatch(expected: Type, got: Type) 18 + } 19 + 20 + type CompiledExpression { 21 + Compiled(document: Document, type_: Type, state: State) 22 + } 23 + 24 + pub type Type { 25 + Custom(module: String, name: String, generics: List(Type)) 26 + TypeVariable(id: Int) 27 + } 28 + 29 + type State { 30 + State(resolved_variables: Dict(Int, Type), type_variable_id: Int) 31 + } 32 + 33 + const type_int = Custom("gleam", "Int", []) 34 + 35 + const type_float = Custom("gleam", "Float", []) 36 + 37 + const type_string = Custom("gleam", "String", []) 38 + 39 + const type_bool = Custom("gleam", "Bool", []) 40 + 41 + const type_nil = Custom("gleam", "Nil", []) 42 + 43 + fn type_list(element: Type) -> Type { 44 + Custom("gleam", "List", [element]) 12 45 } 13 46 14 - pub opaque type Statement(type_) { 15 - Statement(document: Document) 47 + fn type_variable(state: State) -> #(State, Type) { 48 + let id = state.type_variable_id 49 + let type_ = TypeVariable(id:) 50 + let state = State(..state, type_variable_id: id + 1) 51 + #(state, type_) 52 + } 53 + 54 + fn compile( 55 + state: State, 56 + expression: Expression, 57 + continue: fn(State, CompiledExpression) -> Result(a, Error), 58 + ) -> Result(a, Error) { 59 + result.try(expression.compile(state), fn(compiled) { 60 + continue(compiled.state, compiled) 61 + }) 62 + } 63 + 64 + fn compile_statement( 65 + state: State, 66 + statement: Statement, 67 + continue: fn(State, CompiledExpression) -> Result(a, Error), 68 + ) -> Result(a, Error) { 69 + result.try(statement.compile(state), fn(compiled) { 70 + continue(compiled.state, compiled) 71 + }) 72 + } 73 + 74 + fn unify(state: State, a: Type, with b: Type) -> Result(#(State, Type), Error) { 75 + use <- bool.guard(a == b, Ok(#(state, a))) 76 + 77 + case a, b { 78 + TypeVariable(id:), other -> 79 + unify_type_variable(state, id, other, VariableFirst) 80 + other, TypeVariable(id:) -> unify_type_variable(state, id, other, TypeFirst) 81 + _, _ -> Error(TypeMismatch(expected: b, got: a)) 82 + } 83 + } 84 + 85 + type Order { 86 + VariableFirst 87 + TypeFirst 88 + } 89 + 90 + fn unify_type_variable( 91 + state: State, 92 + id: Int, 93 + type_: Type, 94 + order: Order, 95 + ) -> Result(#(State, Type), Error) { 96 + case dict.get(state.resolved_variables, id) { 97 + Ok(resolved_type) if order == VariableFirst -> 98 + unify(state, resolved_type, type_) 99 + Ok(resolved_type) -> unify(state, type_, resolved_type) 100 + Error(_) -> { 101 + let state = 102 + State( 103 + ..state, 104 + resolved_variables: dict.insert(state.resolved_variables, id, type_), 105 + ) 106 + Ok(#(state, type_)) 107 + } 108 + } 109 + } 110 + 111 + pub opaque type Statement { 112 + Statement(compile: fn(State) -> Result(CompiledExpression, Error)) 16 113 } 17 114 18 115 const width = 80 19 116 20 117 const indent = 2 21 118 22 - pub fn expression_to_string(expression: Expression(any)) -> String { 23 - doc.to_string(expression.document, width) 119 + pub fn expression_to_string(expression: Expression) -> Result(String, Error) { 120 + result.map(expression.compile(new_state()), fn(expression) { 121 + doc.to_string(expression.document, width) 122 + }) 123 + } 124 + 125 + fn new_state() -> State { 126 + State(dict.new(), 0) 127 + } 128 + 129 + pub fn int(value: Int) -> Expression { 130 + use state <- Expression 131 + value 132 + |> int.to_string 133 + |> doc.from_string 134 + |> Compiled(type_int, state) 135 + |> Ok 24 136 } 25 137 26 - pub fn int(value: Int) -> Expression(Int) { 27 - value |> int.to_string |> doc.from_string |> Expression 138 + pub fn float(value: Float) -> Expression { 139 + use state <- Expression 140 + value 141 + |> float.to_string 142 + |> doc.from_string 143 + |> Compiled(type_float, state) 144 + |> Ok 28 145 } 29 146 30 - pub fn float(value: Float) -> Expression(Float) { 31 - value |> float.to_string |> doc.from_string |> Expression 147 + pub fn string(value: String) -> Expression { 148 + use state <- Expression 149 + value 150 + |> escape_string_literal 151 + |> doc.from_string 152 + |> Compiled(type_string, state) 153 + |> Ok 32 154 } 33 155 34 - pub fn string(value: String) -> Expression(String) { 35 - value |> escape_string_literal |> doc.from_string |> Expression 156 + pub fn bool(value: Bool) -> Expression { 157 + use state <- Expression 158 + value 159 + |> bool.to_string 160 + |> doc.from_string 161 + |> Compiled(type_bool, state) 162 + |> Ok 36 163 } 37 164 38 - pub fn bool(value: Bool) -> Expression(Bool) { 39 - value |> bool.to_string |> doc.from_string |> Expression 165 + pub fn nil() -> Expression { 166 + use state <- Expression 167 + "Nil" 168 + |> doc.from_string 169 + |> Compiled(type_nil, state) 170 + |> Ok 40 171 } 41 172 42 173 fn escape_string_literal(string: String) -> String { ··· 65 196 } 66 197 67 198 fn binary_operator( 68 - left: Expression(a), 199 + left: Expression, 69 200 operator: String, 70 - right: Expression(a), 71 - ) -> Expression(b) { 201 + right: Expression, 202 + operand_type: Type, 203 + result_type: Type, 204 + ) -> Expression { 205 + Expression(do_binary_operator( 206 + _, 207 + left, 208 + operator, 209 + right, 210 + operand_type, 211 + result_type, 212 + )) 213 + } 214 + 215 + fn do_binary_operator( 216 + state: State, 217 + left: Expression, 218 + operator: String, 219 + right: Expression, 220 + operand_type: Type, 221 + result_type: Type, 222 + ) -> Result(CompiledExpression, Error) { 223 + use state, left <- compile(state, left) 224 + use state, right <- compile(state, right) 225 + use #(state, _) <- result.try(unify(state, left.type_, operand_type)) 226 + use #(state, _) <- result.try(unify(state, right.type_, operand_type)) 227 + 72 228 [left.document, doc.from_string(" " <> operator <> " "), right.document] 73 229 |> doc.concat 74 - |> Expression 230 + |> Compiled(result_type, state) 231 + |> Ok 75 232 } 76 233 77 - pub fn add(left: Expression(Int), right: Expression(Int)) -> Expression(Int) { 78 - binary_operator(left, "+", right) 234 + pub fn add(left: Expression, right: Expression) -> Expression { 235 + binary_operator(left, "+", right, type_int, type_int) 79 236 } 80 237 81 - pub fn add_float( 82 - left: Expression(Float), 83 - right: Expression(Float), 84 - ) -> Expression(Float) { 85 - binary_operator(left, "+.", right) 238 + pub fn add_float(left: Expression, right: Expression) -> Expression { 239 + binary_operator(left, "+.", right, type_float, type_float) 86 240 } 87 241 88 - pub fn subtract( 89 - left: Expression(Int), 90 - right: Expression(Int), 91 - ) -> Expression(Int) { 92 - binary_operator(left, "-", right) 242 + pub fn subtract(left: Expression, right: Expression) -> Expression { 243 + binary_operator(left, "-", right, type_int, type_int) 93 244 } 94 245 95 - pub fn subtract_float( 96 - left: Expression(Float), 97 - right: Expression(Float), 98 - ) -> Expression(Float) { 99 - binary_operator(left, "-.", right) 246 + pub fn subtract_float(left: Expression, right: Expression) -> Expression { 247 + binary_operator(left, "-.", right, type_float, type_float) 100 248 } 101 249 102 - pub fn multiply( 103 - left: Expression(Int), 104 - right: Expression(Int), 105 - ) -> Expression(Int) { 106 - binary_operator(left, "*", right) 250 + pub fn multiply(left: Expression, right: Expression) -> Expression { 251 + binary_operator(left, "*", right, type_int, type_int) 107 252 } 108 253 109 - pub fn multiply_float( 110 - left: Expression(Float), 111 - right: Expression(Float), 112 - ) -> Expression(Float) { 113 - binary_operator(left, "*.", right) 254 + pub fn multiply_float(left: Expression, right: Expression) -> Expression { 255 + binary_operator(left, "*.", right, type_float, type_float) 114 256 } 115 257 116 - pub fn divide(left: Expression(Int), right: Expression(Int)) -> Expression(Int) { 117 - binary_operator(left, "/", right) 258 + pub fn divide(left: Expression, right: Expression) -> Expression { 259 + binary_operator(left, "/", right, type_int, type_int) 118 260 } 119 261 120 - pub fn divide_float( 121 - left: Expression(Float), 122 - right: Expression(Float), 123 - ) -> Expression(Float) { 124 - binary_operator(left, "/.", right) 262 + pub fn divide_float(left: Expression, right: Expression) -> Expression { 263 + binary_operator(left, "/.", right, type_float, type_float) 125 264 } 126 265 127 - pub fn remainder( 128 - left: Expression(Int), 129 - right: Expression(Int), 130 - ) -> Expression(Int) { 131 - binary_operator(left, "%", right) 266 + pub fn remainder(left: Expression, right: Expression) -> Expression { 267 + binary_operator(left, "%", right, type_int, type_int) 132 268 } 133 269 134 - pub fn concatenate( 135 - left: Expression(String), 136 - right: Expression(String), 137 - ) -> Expression(String) { 138 - binary_operator(left, "<>", right) 270 + pub fn concatenate(left: Expression, right: Expression) -> Expression { 271 + binary_operator(left, "<>", right, type_string, type_string) 139 272 } 140 273 141 - pub fn and(left: Expression(Bool), right: Expression(Bool)) -> Expression(Bool) { 142 - binary_operator(left, "&&", right) 274 + pub fn and(left: Expression, right: Expression) -> Expression { 275 + binary_operator(left, "&&", right, type_bool, type_bool) 143 276 } 144 277 145 - pub fn or(left: Expression(Bool), right: Expression(Bool)) -> Expression(Bool) { 146 - binary_operator(left, "||", right) 278 + pub fn or(left: Expression, right: Expression) -> Expression { 279 + binary_operator(left, "||", right, type_bool, type_bool) 147 280 } 148 281 149 - pub fn equal(left: Expression(a), right: Expression(a)) -> Expression(Bool) { 150 - binary_operator(left, "==", right) 282 + pub fn equal(left: Expression, right: Expression) -> Expression { 283 + use state <- Expression 284 + let #(state, type_) = type_variable(state) 285 + do_binary_operator(state, left, "==", right, type_, type_bool) 151 286 } 152 287 153 - pub fn not_equal(left: Expression(a), right: Expression(a)) -> Expression(Bool) { 154 - binary_operator(left, "!=", right) 288 + pub fn not_equal(left: Expression, right: Expression) -> Expression { 289 + use state <- Expression 290 + let #(state, type_) = type_variable(state) 291 + do_binary_operator(state, left, "!=", right, type_, type_bool) 155 292 } 156 293 157 - pub fn less_than( 158 - left: Expression(Int), 159 - right: Expression(Int), 160 - ) -> Expression(Int) { 161 - binary_operator(left, "<", right) 294 + pub fn less_than(left: Expression, right: Expression) -> Expression { 295 + binary_operator(left, "<", right, type_int, type_bool) 162 296 } 163 297 164 - pub fn less_than_float( 165 - left: Expression(Float), 166 - right: Expression(Float), 167 - ) -> Expression(Float) { 168 - binary_operator(left, "<.", right) 298 + pub fn less_than_float(left: Expression, right: Expression) -> Expression { 299 + binary_operator(left, "<.", right, type_float, type_bool) 169 300 } 170 301 171 - pub fn less_than_or_equal( 172 - left: Expression(Int), 173 - right: Expression(Int), 174 - ) -> Expression(Int) { 175 - binary_operator(left, "<=", right) 302 + pub fn less_than_or_equal(left: Expression, right: Expression) -> Expression { 303 + binary_operator(left, "<=", right, type_int, type_bool) 176 304 } 177 305 178 306 pub fn less_than_or_equal_float( 179 - left: Expression(Float), 180 - right: Expression(Float), 181 - ) -> Expression(Float) { 182 - binary_operator(left, "<=.", right) 307 + left: Expression, 308 + right: Expression, 309 + ) -> Expression { 310 + binary_operator(left, "<=.", right, type_float, type_bool) 183 311 } 184 312 185 - pub fn greater_than( 186 - left: Expression(Int), 187 - right: Expression(Int), 188 - ) -> Expression(Int) { 189 - binary_operator(left, ">", right) 313 + pub fn greater_than(left: Expression, right: Expression) -> Expression { 314 + binary_operator(left, ">", right, type_int, type_bool) 190 315 } 191 316 192 - pub fn greater_than_float( 193 - left: Expression(Float), 194 - right: Expression(Float), 195 - ) -> Expression(Float) { 196 - binary_operator(left, ">.", right) 317 + pub fn greater_than_float(left: Expression, right: Expression) -> Expression { 318 + binary_operator(left, ">.", right, type_float, type_bool) 197 319 } 198 320 199 - pub fn greater_than_or_equal( 200 - left: Expression(Int), 201 - right: Expression(Int), 202 - ) -> Expression(Int) { 203 - binary_operator(left, ">=", right) 321 + pub fn greater_than_or_equal(left: Expression, right: Expression) -> Expression { 322 + binary_operator(left, ">=", right, type_int, type_bool) 204 323 } 205 324 206 325 pub fn greater_than_or_equal_float( 207 - left: Expression(Float), 208 - right: Expression(Float), 209 - ) -> Expression(Float) { 210 - binary_operator(left, ">=.", right) 326 + left: Expression, 327 + right: Expression, 328 + ) -> Expression { 329 + binary_operator(left, ">=.", right, type_float, type_bool) 211 330 } 212 331 213 - fn unary_operator(operator: String, value: Expression(a)) -> Expression(a) { 214 - [doc.from_string(operator), value.document] |> doc.concat |> Expression 332 + fn unary_operator( 333 + operator: String, 334 + value: Expression, 335 + type_: Type, 336 + ) -> Expression { 337 + use state <- Expression 338 + use state, value <- compile(state, value) 339 + use #(state, type_) <- result.try(unify(state, value.type_, type_)) 340 + [doc.from_string(operator), value.document] 341 + |> doc.concat 342 + |> Compiled(type_, state) 343 + |> Ok 215 344 } 216 345 217 - pub fn negate_int(value: Expression(Int)) -> Expression(Int) { 218 - unary_operator("-", value) 346 + pub fn negate_int(value: Expression) -> Expression { 347 + unary_operator("-", value, type_int) 219 348 } 220 349 221 - pub fn negate_bool(value: Expression(Bool)) -> Expression(Bool) { 222 - unary_operator("!", value) 350 + pub fn negate_bool(value: Expression) -> Expression { 351 + unary_operator("!", value, type_bool) 223 352 } 224 353 225 - pub fn list(values: List(Expression(a))) -> Expression(List(a)) { 354 + pub fn list(values: List(Expression)) -> Expression { 355 + use state <- Expression 356 + use values <- result.try( 357 + list.try_map(values, fn(value) { value.compile(state) }), 358 + ) 359 + 360 + use #(state, element_type) <- result.try( 361 + list.try_fold(values, type_variable(state), fn(acc, value) { 362 + let #(state, type_) = acc 363 + unify(state, value.type_, type_) 364 + }), 365 + ) 366 + 226 367 values 227 368 |> list.map(fn(value) { value.document }) 228 369 |> doc.join(doc.break(", ", ",")) ··· 231 372 |> doc.append(doc.break("", ", ")) 232 373 |> doc.append(doc.from_string("]")) 233 374 |> doc.group 234 - |> Expression 375 + |> Compiled(type_list(element_type), state) 376 + |> Ok 235 377 } 236 378 237 379 fn add_message(document: Document, message: Document) -> Document { ··· 244 386 |> grouped 245 387 } 246 388 247 - pub fn panic_(message: Option(Expression(String))) -> Expression(a) { 248 - Expression(case message { 249 - None -> doc.from_string("panic") 250 - Some(message) -> add_message(doc.from_string("panic"), message.document) 389 + pub fn panic_(message: Option(Expression)) -> Expression { 390 + use state <- Expression 391 + let #(state, type_) = type_variable(state) 392 + use #(state, doc) <- result.map(case message { 393 + None -> Ok(#(state, doc.from_string("panic"))) 394 + Some(message) -> { 395 + use state, message <- compile(state, message) 396 + use #(state, _) <- result.map(unify(state, message.type_, type_string)) 397 + #(state, add_message(doc.from_string("panic"), message.document)) 398 + } 251 399 }) 400 + Compiled(doc, type_, state) 252 401 } 253 402 254 - pub fn todo_(message: Option(Expression(String))) -> Expression(a) { 255 - Expression(case message { 256 - None -> doc.from_string("todo") 257 - Some(message) -> add_message(doc.from_string("todo"), message.document) 403 + pub fn todo_(message: Option(Expression)) -> Expression { 404 + use state <- Expression 405 + let #(state, type_) = type_variable(state) 406 + use #(state, doc) <- result.map(case message { 407 + None -> Ok(#(state, doc.from_string("todo"))) 408 + Some(message) -> { 409 + use state, message <- compile(state, message) 410 + use #(state, _) <- result.map(unify(state, message.type_, type_string)) 411 + #(state, add_message(doc.from_string("todo"), message.document)) 412 + } 258 413 }) 414 + Compiled(doc, type_, state) 259 415 } 260 416 261 - pub fn echo_( 262 - value: Expression(a), 263 - message: Option(Expression(String)), 264 - ) -> Expression(a) { 417 + pub fn echo_(value: Expression, message: Option(Expression)) -> Expression { 418 + use state <- Expression 419 + use state, value <- compile(state, value) 420 + 265 421 let echo_ = doc.prepend(doc.from_string("echo "), to: value.document) 266 422 267 - Expression(case message { 268 - None -> echo_ 269 - Some(message) -> add_message(echo_, message.document) 423 + use #(state, doc) <- result.map(case message { 424 + None -> Ok(#(state, echo_)) 425 + Some(message) -> { 426 + use state, message <- compile(state, message) 427 + use #(state, _) <- result.map(unify(state, message.type_, type_string)) 428 + #(state, add_message(echo_, message.document)) 429 + } 270 430 }) 431 + Compiled(doc, value.type_, state) 271 432 } 272 433 273 434 pub fn variable( 274 435 name: String, 275 - value: Expression(a), 276 - continue: fn(Expression(a)) -> Statement(b), 277 - ) -> Statement(b) { 436 + value: Expression, 437 + continue: fn(Expression) -> Statement, 438 + ) -> Statement { 439 + use state <- Statement 440 + use state, value <- compile(state, value) 441 + 278 442 let declaration = 279 443 [ 280 444 doc.from_string("let "), ··· 285 449 |> grouped 286 450 |> doc.append(doc.line) 287 451 288 - let variable_expression = Expression(doc.from_string(name)) 452 + let variable_expression = 453 + Expression(fn(state) { 454 + Ok(Compiled(doc.from_string(name), value.type_, state)) 455 + }) 289 456 290 457 let rest = continue(variable_expression) 458 + use rest <- result.map(rest.compile(state)) 291 459 292 - Statement(doc.prepend(declaration, to: rest.document)) 460 + Compiled(doc.prepend(declaration, to: rest.document), rest.type_, state) 293 461 } 294 462 295 463 fn grouped(documents: List(Document)) -> Document { 296 464 documents |> doc.concat |> doc.nest(indent) |> doc.group 297 465 } 298 466 299 - pub fn expression(expression: Expression(a)) -> Statement(a) { 300 - Statement(expression.document) 467 + pub fn expression(expression: Expression) -> Statement { 468 + Statement(expression.compile) 301 469 } 302 470 303 - pub fn discard( 304 - statement: Statement(a), 305 - continue: fn() -> Statement(b), 306 - ) -> Statement(b) { 471 + pub fn discard(statement: Statement, continue: fn() -> Statement) -> Statement { 472 + use state <- Statement 473 + use state, statement <- compile_statement(state, statement) 307 474 let rest = continue() 308 - Statement(doc.concat([statement.document, doc.line, rest.document])) 475 + use rest <- result.map(rest.compile(state)) 476 + Compiled( 477 + doc.concat([statement.document, doc.line, rest.document]), 478 + rest.type_, 479 + state, 480 + ) 309 481 } 310 482 311 - pub fn block(inner: Statement(a)) -> Expression(a) { 483 + pub fn block(inner: Statement) -> Expression { 484 + use state <- Expression 485 + use inner <- result.map(inner.compile(state)) 486 + 312 487 [ 313 488 doc.from_string("{"), 314 489 doc.line, ··· 318 493 |> doc.nest(indent) 319 494 |> doc.append(doc.line) 320 495 |> doc.append(doc.from_string("}")) 321 - |> Expression 496 + |> Compiled(inner.type_, state) 322 497 } 323 498 324 - pub fn assert_( 325 - condition: Expression(Bool), 326 - message: Option(Expression(String)), 327 - ) -> Statement(Nil) { 499 + pub fn assert_(condition: Expression, message: Option(Expression)) -> Statement { 500 + use state <- Statement 501 + use state, condition <- compile(state, condition) 502 + use #(state, _) <- result.try(unify(state, condition.type_, type_bool)) 503 + 328 504 let assert_ = doc.prepend(doc.from_string("assert "), to: condition.document) 329 505 330 - Statement(case message { 331 - None -> assert_ 332 - Some(message) -> add_message(assert_, message.document) 506 + use #(state, doc) <- result.map(case message { 507 + None -> Ok(#(state, assert_)) 508 + Some(message) -> { 509 + use state, message <- compile(state, message) 510 + use #(state, _) <- result.map(unify(state, message.type_, type_string)) 511 + #(state, add_message(assert_, message.document)) 512 + } 333 513 }) 514 + Compiled(doc, type_nil, state) 334 515 } 335 516 // TODO: 336 517 // BitString
+135 -9
test/trick_test.gleam
··· 8 8 gleeunit.main() 9 9 } 10 10 11 + fn unwrap(x: Result(a, b)) -> a { 12 + let assert Ok(x) = x 13 + x 14 + } 15 + 11 16 pub fn int_literal_test() { 12 - assert 53 |> trick.int |> trick.expression_to_string == "53" 17 + assert 53 |> trick.int |> trick.expression_to_string |> unwrap == "53" 13 18 } 14 19 15 20 pub fn float_literal_test() { 16 - assert 3.1415 |> trick.float |> trick.expression_to_string == "3.1415" 21 + assert 3.1415 |> trick.float |> trick.expression_to_string |> unwrap 22 + == "3.1415" 17 23 } 18 24 19 25 pub fn string_literal_test() { ··· 21 27 It has multiple lines,\nand even quotes: \". This is how you type a backslash: \\\\" 22 28 |> trick.string 23 29 |> trick.expression_to_string 30 + |> unwrap 24 31 |> birdie.snap("string_literal") 25 32 } 26 33 27 34 pub fn binary_operator_test() { 28 - assert trick.add(trick.int(10), trick.int(20)) |> trick.expression_to_string 35 + assert trick.add(trick.int(10), trick.int(20)) 36 + |> trick.expression_to_string 37 + |> unwrap 29 38 == "10 + 20" 30 39 31 40 assert trick.add_float(trick.float(3.14), trick.float(1.41)) 32 41 |> trick.expression_to_string 42 + |> unwrap 33 43 == "3.14 +. 1.41" 34 44 35 45 assert trick.subtract(trick.int(10), trick.int(20)) 36 46 |> trick.expression_to_string 47 + |> unwrap 37 48 == "10 - 20" 38 49 39 50 assert trick.subtract_float(trick.float(3.14), trick.float(1.41)) 40 51 |> trick.expression_to_string 52 + |> unwrap 41 53 == "3.14 -. 1.41" 42 54 43 55 assert trick.multiply(trick.int(10), trick.int(20)) 44 56 |> trick.expression_to_string 57 + |> unwrap 45 58 == "10 * 20" 46 59 47 60 assert trick.multiply_float(trick.float(3.14), trick.float(1.41)) 48 61 |> trick.expression_to_string 62 + |> unwrap 49 63 == "3.14 *. 1.41" 50 64 51 65 assert trick.divide(trick.int(10), trick.int(20)) 52 66 |> trick.expression_to_string 67 + |> unwrap 53 68 == "10 / 20" 54 69 55 70 assert trick.divide_float(trick.float(3.14), trick.float(1.41)) 56 71 |> trick.expression_to_string 72 + |> unwrap 57 73 == "3.14 /. 1.41" 58 74 59 75 assert trick.remainder(trick.int(10), trick.int(3)) 60 76 |> trick.expression_to_string 77 + |> unwrap 61 78 == "10 % 3" 62 79 63 80 assert trick.remainder(trick.int(10), trick.int(3)) 64 81 |> trick.expression_to_string 82 + |> unwrap 65 83 == "10 % 3" 66 84 67 85 assert trick.concatenate(trick.string("Hello,"), trick.string(" world!")) 68 86 |> trick.expression_to_string 87 + |> unwrap 69 88 == "\"Hello,\" <> \" world!\"" 70 89 71 90 assert trick.and(trick.bool(False), trick.bool(True)) 72 91 |> trick.expression_to_string 92 + |> unwrap 73 93 == "False && True" 74 94 75 95 assert trick.or(trick.bool(False), trick.bool(True)) 76 96 |> trick.expression_to_string 97 + |> unwrap 77 98 == "False || True" 78 99 79 100 assert trick.less_than(trick.int(10), trick.int(20)) 80 101 |> trick.expression_to_string 102 + |> unwrap 81 103 == "10 < 20" 82 104 83 105 assert trick.less_than_float(trick.float(3.14), trick.float(1.41)) 84 106 |> trick.expression_to_string 107 + |> unwrap 85 108 == "3.14 <. 1.41" 86 109 87 110 assert trick.less_than_or_equal(trick.int(10), trick.int(20)) 88 111 |> trick.expression_to_string 112 + |> unwrap 89 113 == "10 <= 20" 90 114 91 115 assert trick.less_than_or_equal_float(trick.float(3.14), trick.float(1.41)) 92 116 |> trick.expression_to_string 117 + |> unwrap 93 118 == "3.14 <=. 1.41" 94 119 95 120 assert trick.greater_than(trick.int(10), trick.int(20)) 96 121 |> trick.expression_to_string 122 + |> unwrap 97 123 == "10 > 20" 98 124 99 125 assert trick.greater_than_float(trick.float(3.14), trick.float(1.41)) 100 126 |> trick.expression_to_string 127 + |> unwrap 101 128 == "3.14 >. 1.41" 102 129 103 130 assert trick.greater_than_or_equal(trick.int(10), trick.int(20)) 104 131 |> trick.expression_to_string 132 + |> unwrap 105 133 == "10 >= 20" 106 134 107 135 assert trick.greater_than_or_equal_float(trick.float(3.14), trick.float(1.41)) 108 136 |> trick.expression_to_string 137 + |> unwrap 109 138 == "3.14 >=. 1.41" 110 139 111 140 assert trick.equal(trick.float(3.14), trick.float(1.41)) 112 141 |> trick.expression_to_string 142 + |> unwrap 113 143 == "3.14 == 1.41" 114 144 115 145 assert trick.equal(trick.int(23), trick.int(89)) 116 146 |> trick.expression_to_string 147 + |> unwrap 117 148 == "23 == 89" 118 149 119 150 assert trick.equal(trick.bool(False), trick.bool(False)) 120 151 |> trick.expression_to_string 152 + |> unwrap 121 153 == "False == False" 122 154 123 155 assert trick.not_equal(trick.float(3.14), trick.float(1.41)) 124 156 |> trick.expression_to_string 157 + |> unwrap 125 158 == "3.14 != 1.41" 126 159 127 160 assert trick.not_equal(trick.int(23), trick.int(89)) 128 161 |> trick.expression_to_string 162 + |> unwrap 129 163 == "23 != 89" 130 164 131 165 assert trick.not_equal(trick.bool(False), trick.bool(False)) 132 166 |> trick.expression_to_string 167 + |> unwrap 133 168 == "False != False" 134 169 } 135 170 171 + const type_int = trick.Custom("gleam", "Int", []) 172 + 173 + const type_float = trick.Custom("gleam", "Float", []) 174 + 175 + const type_string = trick.Custom("gleam", "String", []) 176 + 177 + const type_bool = trick.Custom("gleam", "Bool", []) 178 + 179 + const type_nil = trick.Custom("gleam", "Nil", []) 180 + 181 + pub fn binary_operator_type_mismatch_test() { 182 + let assert Error(error) = 183 + trick.add(trick.int(10), trick.float(20.0)) 184 + |> trick.expression_to_string 185 + assert error == trick.TypeMismatch(expected: type_int, got: type_float) 186 + 187 + let assert Error(error) = 188 + trick.add_float(trick.string("Hello"), trick.int(12)) 189 + |> trick.expression_to_string 190 + assert error == trick.TypeMismatch(expected: type_float, got: type_string) 191 + 192 + let assert Error(error) = 193 + trick.equal(trick.string("Hello"), trick.bool(True)) 194 + |> trick.expression_to_string 195 + assert error == trick.TypeMismatch(expected: type_string, got: type_bool) 196 + 197 + let assert Error(error) = 198 + trick.not_equal(trick.nil(), trick.int(23)) 199 + |> trick.expression_to_string 200 + assert error == trick.TypeMismatch(expected: type_nil, got: type_int) 201 + } 202 + 136 203 pub fn unary_operator_test() { 137 - assert 10 |> trick.int |> trick.negate_int |> trick.expression_to_string 204 + assert 10 205 + |> trick.int 206 + |> trick.negate_int 207 + |> trick.expression_to_string 208 + |> unwrap 138 209 == "-10" 139 210 140 - assert False |> trick.bool |> trick.negate_bool |> trick.expression_to_string 211 + assert False 212 + |> trick.bool 213 + |> trick.negate_bool 214 + |> trick.expression_to_string 215 + |> unwrap 141 216 == "!False" 142 217 } 143 218 219 + pub fn unary_operator_type_mismatch_test() { 220 + let assert Error(error) = 221 + trick.negate_int(trick.float(20.0)) 222 + |> trick.expression_to_string 223 + assert error == trick.TypeMismatch(expected: type_int, got: type_float) 224 + 225 + let assert Error(error) = 226 + trick.negate_bool(trick.nil()) 227 + |> trick.expression_to_string 228 + assert error == trick.TypeMismatch(expected: type_bool, got: type_nil) 229 + } 230 + 144 231 pub fn list_test() { 145 232 assert [1, 2, 3, 4] 146 233 |> list.map(trick.int) 147 234 |> trick.list 148 235 |> trick.expression_to_string 236 + |> unwrap 149 237 == "[1, 2, 3, 4]" 150 238 } 151 239 ··· 158 246 |> list.map(trick.string) 159 247 |> trick.list 160 248 |> trick.expression_to_string 249 + |> unwrap 161 250 |> birdie.snap("long_list") 162 251 } 163 252 253 + pub fn list_type_mismatch_test() { 254 + let assert Error(error) = 255 + trick.list([trick.int(10), trick.int(20), trick.float(30.0)]) 256 + |> trick.expression_to_string 257 + assert error == trick.TypeMismatch(expected: type_int, got: type_float) 258 + } 259 + 164 260 pub fn panic_no_message_test() { 165 - assert trick.panic_(None) |> trick.expression_to_string == "panic" 261 + assert trick.panic_(None) |> trick.expression_to_string |> unwrap == "panic" 166 262 } 167 263 168 264 pub fn panic_with_message_test() { ··· 171 267 |> Some 172 268 |> trick.panic_ 173 269 |> trick.expression_to_string 270 + |> unwrap 174 271 |> birdie.snap("panic_with_message") 175 272 } 176 273 ··· 180 277 |> Some 181 278 |> trick.panic_ 182 279 |> trick.expression_to_string 280 + |> unwrap 183 281 |> birdie.snap("panic_with_long_message") 184 282 } 185 283 186 284 pub fn todo_no_message_test() { 187 - assert trick.todo_(None) |> trick.expression_to_string == "todo" 285 + assert trick.todo_(None) |> trick.expression_to_string |> unwrap == "todo" 188 286 } 189 287 190 288 pub fn todo_with_message_test() { ··· 193 291 |> Some 194 292 |> trick.todo_ 195 293 |> trick.expression_to_string 294 + |> unwrap 196 295 |> birdie.snap("todo_with_message") 197 296 } 198 297 ··· 202 301 |> Some 203 302 |> trick.todo_ 204 303 |> trick.expression_to_string 304 + |> unwrap 205 305 |> birdie.snap("todo_with_long_message") 206 306 } 207 307 208 308 pub fn echo_test() { 209 - assert 15 |> trick.int |> trick.echo_(None) |> trick.expression_to_string 309 + assert 15 310 + |> trick.int 311 + |> trick.echo_(None) 312 + |> trick.expression_to_string 313 + |> unwrap 210 314 == "echo 15" 211 315 } 212 316 ··· 215 319 |> trick.float 216 320 |> trick.echo_(Some(trick.string("This is a pi!"))) 217 321 |> trick.expression_to_string 322 + |> unwrap 218 323 |> birdie.snap("echo_with_message") 219 324 } 220 325 ··· 227 332 )), 228 333 ) 229 334 |> trick.expression_to_string 335 + |> unwrap 230 336 |> birdie.snap("echo_with_long_message") 231 337 } 232 338 ··· 237 343 trick.expression(trick.add(a, b)) 238 344 } 239 345 240 - block |> trick.block |> trick.expression_to_string |> birdie.snap("block") 346 + block 347 + |> trick.block 348 + |> trick.expression_to_string 349 + |> unwrap 350 + |> birdie.snap("block") 241 351 } 242 352 243 353 pub fn block_with_discarded_expression_test() { ··· 251 361 block 252 362 |> trick.block 253 363 |> trick.expression_to_string 364 + |> unwrap 254 365 |> birdie.snap("block_with_discarded_expression") 255 366 } 256 367 368 + pub fn unified_variable_cannot_be_changed_test() { 369 + let assert Error(error) = 370 + { 371 + use x <- trick.variable("x", trick.todo_(None)) 372 + use <- trick.discard(trick.expression(trick.add(x, trick.int(1)))) 373 + trick.expression(trick.concatenate(x, trick.string("!"))) 374 + } 375 + |> trick.block 376 + |> trick.expression_to_string 377 + assert error == trick.TypeMismatch(expected: type_string, got: type_int) 378 + } 379 + 257 380 pub fn assert_test() { 258 381 trick.equal(trick.int(1), trick.int(2)) 259 382 |> trick.assert_(None) 260 383 |> trick.block 261 384 |> trick.expression_to_string 385 + |> unwrap 262 386 |> birdie.snap("assert") 263 387 } 264 388 ··· 267 391 |> trick.assert_(Some(trick.string("This is always going to fail"))) 268 392 |> trick.block 269 393 |> trick.expression_to_string 394 + |> unwrap 270 395 |> birdie.snap("assert_with_message") 271 396 } 272 397 ··· 279 404 ) 280 405 |> trick.block 281 406 |> trick.expression_to_string 407 + |> unwrap 282 408 |> birdie.snap("assert_with_long_message") 283 409 }