├── .gitignore ├── LICENSE ├── Makefile ├── README.rst ├── examples ├── calls.ifx ├── datatypes.ifx ├── do.ifx ├── fors.ifx ├── guards.ifx ├── ifis.ifx ├── lamdas.ifx ├── maps.ifx ├── receive.ifx ├── records.ifx ├── samples.ifx ├── tlfn.ifx ├── try.ifx └── whenex.ifx ├── include └── ifix.hrl ├── rebar.config ├── rebar.lock └── src ├── ifix_lexer.xrl ├── ifix_parser.yrl ├── ifix_to_erl.erl ├── interfix.app.src └── interfix.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | _rel 15 | _deps 16 | _plugins 17 | _tdeps 18 | logs 19 | _build 20 | src/ifix_parser.erl 21 | src/ifix_lexer.erl 22 | 23 | interfix 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Mariano Guerra . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | * The names of its contributors may not be used to endorse or promote 16 | products derived from this software without specific prior written 17 | permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: examples 2 | 3 | script: 4 | rebar3 escriptize 5 | 6 | examples: 7 | for d in examples/*.ifx; do echo $$d; echo; ./interfix erl $$d; done 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | interfix 2 | ======== 3 | 4 | An experimential programming language for the Erlang VM. 5 | 6 | prefix? infix? postfix? none, interfix 7 | 8 | underscores? camel case? pascal case? none, interfix 9 | 10 | interfix is an experiment to break with some common "expectations" in 11 | programming languages and provide a simpler yet expressive programming language 12 | that is actually useful. 13 | 14 | First some working examples: 15 | 16 | If 17 | -- 18 | 19 | .. code-block:: ruby 20 | 21 | fn main: 22 | when A < 10: 23 | do something with A 24 | something else; 25 | when A < 20: 26 | log warning "A is ~p" [A]; 27 | else: 28 | log warn "wat". 29 | . 30 | 31 | 32 | Compiles to: 33 | 34 | .. code-block:: erlang 35 | 36 | main() -> 37 | if A < 10 -> do_something_with(A), something_else(); 38 | A < 20 -> log_warning("A is ~p", [A]); 39 | true -> log_warn("wat") 40 | end. 41 | 42 | Misc 43 | ---- 44 | 45 | .. code-block:: ruby 46 | 47 | fn divide A by 0: 48 | #[:error :division_by_zero]; 49 | fn divide A by B: 50 | #[:ok (A / B)]. 51 | 52 | fn format Str with Args: 53 | io :: format Str Args. 54 | 55 | fn+ export this one 0: 56 | 0; 57 | fn export this one A: 58 | A + 1. 59 | 60 | fn+ main: 61 | _ is #[1 1.5 :foo [] [:bar #[]]] 62 | other module :: multiply 3 by 7 63 | format "value is ~p" with [C] 64 | C is (divide 42 by 2). 65 | 66 | Compiles to: 67 | 68 | .. code-block:: erlang 69 | 70 | divide_O_by(A, 0) -> {error, division_by_zero}; 71 | divide_O_by(A, B) -> {ok, A / B}. 72 | 73 | format_O_with(Str, Args) -> io:format(Str, Args). 74 | 75 | export_this_one(0) -> 0; 76 | export_this_one(A) -> A + 1. 77 | 78 | main() -> 79 | _ = {1, 1.5, foo, [], [bar, {}]}, 80 | other_module:multiply_O_by(3, 7), 81 | format_O_with("value is ~p", [C]), 82 | C = divide_O_by(42, 2). 83 | 84 | 85 | Case Of 86 | ------- 87 | 88 | .. code-block:: ruby 89 | 90 | fn+ check if A: 91 | if A is 12: print 12. 92 | . 93 | 94 | fn+ check if else A: 95 | if A is 12: print 12; 96 | else: print :other. 97 | . 98 | 99 | fn+ check if 1 else it A: 100 | if A is 12: print 12; 101 | if it is 32: print 32; 102 | else: print :other. 103 | . 104 | 105 | fn+ check if 2 else it A: 106 | if A is 12: print 12; 107 | if it is 32: print 32; 108 | if it is 33: print 32; 109 | else: print :other. 110 | . 111 | 112 | fn+ check all A: 113 | if A is 12: print 12; 114 | if it is 32: print 32; 115 | else: print :other. 116 | . 117 | 118 | Compiles to: 119 | 120 | .. code-block:: erlang 121 | 122 | check_if(A) -> case A of 12 -> print(12) end. 123 | 124 | check_if_else(A) -> 125 | case A of 126 | 12 -> print(12); 127 | _ -> print(other) 128 | end. 129 | 130 | check_if_O_else_it(1, A) -> 131 | case A of 132 | 12 -> print(12); 133 | 32 -> print(32); 134 | _ -> print(other) 135 | end. 136 | 137 | check_if_O_else_it(2, A) -> 138 | case A of 139 | 12 -> print(12); 140 | 32 -> print(32); 141 | 33 -> print(32); 142 | _ -> print(other) 143 | end. 144 | 145 | check_all(A) -> 146 | case A of 147 | 12 -> print(12); 148 | 32 -> print(32); 149 | _ -> print(other) 150 | end. 151 | 152 | Receive After 153 | ------------- 154 | 155 | .. code-block:: ruby 156 | 157 | fn+ receive one: 158 | on message 43: do something here. 159 | . 160 | 161 | fn+ receive two: 162 | on message 43: do something here; 163 | on message :a: something else. 164 | . 165 | 166 | fn+ receive two and timeout: 167 | on message 43: do something here; 168 | on message :a: something else; 169 | after 50 milliseconds: do timeout thing. 170 | . 171 | 172 | Compiles to: 173 | 174 | .. code-block:: erlang 175 | 176 | receive_one() -> receive 43 -> do_something_here() end. 177 | 178 | receive_two() -> 179 | receive 180 | 43 -> do_something_here(); 181 | a -> something_else() 182 | end. 183 | 184 | receive_two_and_timeout() -> 185 | receive 186 | 43 -> do_something_here(); 187 | a -> something_else() 188 | after 50 -> do_timeout_thing() 189 | end. 190 | 191 | Try Catch Finally 192 | ------------------ 193 | 194 | .. code-block:: ruby 195 | 196 | fn+ try always: 197 | try: 198 | something that may break 199 | something else; 200 | always: 201 | try to recover 202 | and cleanup. 203 | . 204 | 205 | fn+ try catch: 206 | try: 207 | something that may break 208 | something else; 209 | 210 | catch throw T: handle throw T; 211 | catch error E: handle error E; 212 | catch exit Ex: handle exit Ex; 213 | catch Type E: handle Type E. 214 | . 215 | 216 | fn+ try catch always: 217 | try: 218 | something that may break 219 | something else; 220 | 221 | catch throw T: handle throw T; 222 | catch error E: handle error E; 223 | catch exit Ex: handle exit Ex; 224 | catch Type E: handle Type E; 225 | 226 | always: 227 | try to recover 228 | and cleanup. 229 | . 230 | 231 | Compiles to: 232 | 233 | .. code-block:: erlang 234 | 235 | try_always() -> 236 | try something_that_may_break(), something_else() after 237 | try_to_recover(), and_cleanup() 238 | end. 239 | 240 | try_catch() -> 241 | try something_that_may_break(), something_else() catch 242 | T -> handle_throw(T); 243 | error:E -> handle_error(E); 244 | exit:Ex -> handle_exit(Ex); 245 | Type:E -> handle(Type, E) 246 | end. 247 | 248 | try_catch_always() -> 249 | try something_that_may_break(), something_else() catch 250 | T -> handle_throw(T); 251 | error:E -> handle_error(E); 252 | exit:Ex -> handle_exit(Ex); 253 | Type:E -> handle(Type, E) 254 | after 255 | try_to_recover(), and_cleanup() 256 | end. 257 | 258 | Begin End 259 | --------- 260 | 261 | .. code-block:: ruby 262 | 263 | fn+ simple do with A: 264 | do: 265 | some stuff with A 266 | and some other stuff 267 | A + 2. 268 | . 269 | 270 | fn+ do with A as value: 271 | with result of do (do: 272 | some stuff with A 273 | and some other stuff 274 | A + 2). 275 | 276 | Compiles to: 277 | 278 | .. code-block:: erlang 279 | 280 | simple_do_with(A) -> 281 | begin 282 | some_stuff_with(A), and_some_other_stuff(), A + 2 283 | end. 284 | 285 | do_with_O_as_value(A) -> 286 | with_result_of_do(begin 287 | some_stuff_with(A), and_some_other_stuff(), A + 2 288 | end). 289 | 290 | Data Types 291 | ---------- 292 | 293 | .. code-block:: ruby 294 | 295 | fn+ data types examples: 296 | an int 42 297 | a float 1.5 298 | an atom :foo 299 | nil is :nil 300 | booleans are atoms too :true and :false 301 | an empty list [] 302 | a list with some items [1 2.5 :true :nil [] [:bar]] 303 | an empty tuple #[] 304 | a tuple with some items #[1 2.5 :true :nil [] [:bar]] 305 | a cons (cons 1 []) 306 | improper list (cons 1 2) 307 | nested conses (cons 1 (cons 2 [])) 308 | a list string "hi there" 309 | a binary string 'hi there too' 310 | function reference (fn ref divideby 2) 311 | function reference (fn ref `other_module` divideby 2) 312 | fun ref by name (fn ref divide _ by _) 313 | fun ref by name (fn ref other module :: divide _ by _). 314 | 315 | Compiles to: 316 | 317 | .. code-block:: erlang 318 | 319 | data_types_examples() -> 320 | an_int(42), 321 | a_float(1.5), 322 | an_atom(foo), 323 | nil_is(nil), 324 | booleans_are_atoms_too_O_and(true, false), 325 | an_empty_list([]), 326 | a_list_with_some_items([1, 2.5, true, nil, [], [bar]]), 327 | an_empty_tuple({}), 328 | a_tuple_with_some_items({1, 2.5, true, nil, [], [bar]}), 329 | a_cons([1]), 330 | improper_list([1 | 2]), 331 | nested_conses([1, 2]), 332 | a_list_string("hi there"), 333 | a_binary_string(<<"hi there too">>), 334 | function_reference(fun divideby/2), 335 | function_reference(fun other_module:divideby/2), 336 | fun_ref_by_name(fun divide_O_by/2), 337 | fun_ref_by_name(fun other_module:divide_O_by/2). 338 | 339 | Anonymous Functions 340 | ------------------- 341 | 342 | .. code-block:: ruby 343 | 344 | fn+ lambda 0: 345 | fn: :ok. 346 | . 347 | 348 | fn+ lambda 1: 349 | fn A: A + 1. 350 | . 351 | 352 | fn+ lambda 2: 353 | fn A B: A + B. 354 | . 355 | 356 | fn+ lambda 3: 357 | fn A :minus B: 358 | log info "is minus ~p ~p" [A B] 359 | A - B. 360 | . 361 | 362 | fn+ lambda multiple clauses: 363 | fn A :divided :by 0: #[:error :divide_by_zero]; 364 | fn A :divided :by B: #[:ok (A / B)]. 365 | . 366 | 367 | Compiles to: 368 | 369 | .. code-block:: erlang 370 | 371 | lambda(0) -> fun () -> ok end. 372 | 373 | lambda(1) -> fun (A) -> A + 1 end. 374 | 375 | lambda(2) -> fun (A, B) -> A + B end. 376 | 377 | lambda(3) -> 378 | fun (A, minus, B) -> 379 | log_info("is minus ~p ~p", [A, B]), A - B 380 | end. 381 | 382 | lambda_multiple_clauses() -> 383 | fun (A, divided, by, 0) -> {error, divide_by_zero}; 384 | (A, divided, by, B) -> {ok, A / B} 385 | end. 386 | 387 | As you can see there are no commas, no parenthesis, no reserved keywords and 388 | functions receive parameter "interfixed" between function name tokens, this 389 | allows things like: 390 | 391 | .. code-block:: ruby 392 | 393 | divide 10 by 2 394 | other module :: multiply 3 by 7 395 | format "value is ~p" with [C] 396 | C is (divide 42 by 2). 397 | 398 | Build 399 | ----- 400 | 401 | :: 402 | 403 | rebar3 escriptize 404 | ln -s _build/default/bin/interfix 405 | 406 | Run 407 | --- 408 | 409 | :: 410 | 411 | ./interfix erl examples/tlfn.ifx 412 | 413 | Compile Stuff 414 | ------------- 415 | 416 | To compile to bytecode run "interfix bean ":: 417 | 418 | $ ./interfix beam examples/samples.ifx . 419 | [{warnings,[]},{module_name,samples}] 420 | 421 | A file called samples.beam should be in your current directory, now you 422 | can use if from the erlang shell:: 423 | 424 | $ erl 425 | Erlang/OTP 18 [erts-7.0] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false] 426 | 427 | Eshell V7.0 (abort with ^G) 428 | 429 | 1> samples:say_hello_to("mariano"). 430 | hello mariano! 431 | ok 432 | 433 | 2> q(). 434 | ok 435 | 436 | CLI Reference 437 | ------------- 438 | 439 | The CLI tool has the following general syntax:: 440 | 441 | interfix [] 442 | 443 | Here are the allowed commands and the arguments they take: 444 | 445 | interfix lex 446 | prints the lexer output of the interfix source file located at 447 | 448 | interfix ast 449 | prints the ast output of the interfix source file located at 450 | 451 | interfix erl 452 | prints the equivalent erlang source code of the interfix source file located at 453 | 454 | interfix erlast 455 | prints the equivalent erlang ast of the interfix source file located at 456 | 457 | interfix mod 458 | prints the equivalent erlang ast with module information of the interfix source file located at 459 | 460 | interfix beam 461 | compiles interfix module at to bytecode and writes the beam file 462 | at 463 | 464 | Status 465 | ------ 466 | 467 | Works 468 | ..... 469 | 470 | * multi clause functions 471 | * anonymous functions 472 | * if expression (when in interfix) 473 | * case .. of 474 | * receive/after 475 | * try/catch/finally 476 | * function calls, local and to other modules 477 | * erlang interop 478 | * ints, floats, atoms, strings, binary strings 479 | * lists, tuples, cons lists 480 | * bin, arithmetic, bool, comparisson operations 481 | 482 | Missing 483 | ....... 484 | 485 | * list comprehension 486 | * record support (need to think of syntax) 487 | * other stuff 488 | -------------------------------------------------------------------------------- /examples/calls.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ call examples Mod Fun: 3 | "not implemented: module name :: Fun 42" 4 | :ok 42 5 | :error :bad_value 42 6 | :error (:bad_value 42) 7 | :a bigger than :b 8 | Fun 42 :true 9 | Mod :: Fun 42 :true. 10 | 11 | -------------------------------------------------------------------------------- /examples/datatypes.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ data types examples: 3 | an int 42 4 | a float 1.5 5 | an atom :foo 6 | nil is :nil 7 | booleans are atoms too :true and :false 8 | an empty list [] 9 | a list with some items [1 2.5 :true :nil [] [:bar]] 10 | an empty tuple #[] 11 | a tuple with some items #[1 2.5 :true :nil [] [:bar]] 12 | a cons (cons 1 []) 13 | improper list (cons 1 2) 14 | nested conses (cons 1 (cons 2 [])) 15 | a list string "hi there" 16 | a binary string 'hi there too' 17 | function reference (fn ref divideby 2) 18 | function reference (fn ref `other_module` divideby 2) 19 | fun ref by name (fn ref divide _ by _) 20 | fun ref by name (fn ref other module :: divide _ by _). 21 | 22 | -------------------------------------------------------------------------------- /examples/do.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ simple do with A: 3 | do: 4 | some stuff with A 5 | and some other stuff 6 | A + 2. 7 | . 8 | 9 | fn+ do with A as value: 10 | with result of do (do: 11 | some stuff with A 12 | and some other stuff 13 | A + 2). 14 | -------------------------------------------------------------------------------- /examples/fors.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ examples B: 3 | for A in B: 4 | A + 1 5 | . 6 | 7 | bin for A in B: 8 | A + 1. 9 | 10 | for A in B: 11 | C is A + 1 12 | C + 2. 13 | 14 | bin for A in B: 15 | C is A + 1 16 | C + 2. 17 | 18 | for A in B :: when A < 10: 19 | A + 1. 20 | 21 | for A in B 22 | :: when A < 10 23 | :: when (A % 2) == 0: 24 | A + 1. 25 | 26 | for A in B :: when A < 10 :: C in B :: when (A % 2) == 0: 27 | A + (C + 1). 28 | 29 | for A in B :: when A < 10 :: C in bin B: 30 | A + (C + 1). 31 | 32 | for A in bin B :: when A < 10 :: C in bin B: 33 | A + (C + 1). 34 | 35 | bin for A in B :: when A < 10 :: C in bin B: 36 | A + (C + 1). 37 | 38 | bin for A in bin B :: when A < 10 :: C in bin B: 39 | A + (C + 1). 40 | 41 | . 42 | -------------------------------------------------------------------------------- /examples/guards.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ to string V and A :: when (is integer V) :: when A < V :: when A != 2: 3 | integer to list V. 4 | 5 | fn+ lambda with guards 1: 6 | fn V A :: when (is integer V) :: when A < V: 7 | integer to list V.; 8 | fn+ lambda with guards 2: 9 | fn V A :: when (is integer V) :: when A < V: 10 | integer to list V; 11 | fn V A :: when (is float V) :: when A < V :: when V > 2.5: 12 | float to list V. 13 | . 14 | 15 | fn+ lambda without guards 1: 16 | fn V A: 17 | integer to list V.; 18 | fn+ lambda without guards 2: 19 | fn V 1: 20 | integer to list V; 21 | fn V A: 22 | float to list V. 23 | . 24 | -------------------------------------------------------------------------------- /examples/ifis.ifx: -------------------------------------------------------------------------------- 1 | fn+ check if A: 2 | if A is 12: print 12. 3 | . 4 | 5 | fn+ check if else A: 6 | if A is 12: print 12; 7 | else: print :other. 8 | . 9 | 10 | fn+ check if 1 else it A: 11 | if A is 12: print 12; 12 | if it is 32: print 32; 13 | else: print :other. 14 | . 15 | 16 | fn+ check if 2 else it A: 17 | if A is 12: print 12; 18 | if it is 32: print 32; 19 | if it is 33: print 32; 20 | else: print :other. 21 | . 22 | 23 | fn+ check all A: 24 | if A is 12: print 12; 25 | if it is 32: print 32; 26 | else: print :other. 27 | . 28 | fn+ check if A B guard: 29 | if A is 12 :: when (is integer B): print 12. 30 | . 31 | 32 | fn+ check if else A B guard: 33 | if A is 12 :: when B < 10: 34 | print 12; 35 | else: 36 | print :other. 37 | . 38 | 39 | fn+ check if 1 else it A B guard: 40 | if A is 12 :: when B > 5: 41 | print 12; 42 | if it is 32 :: when B > 12 :: when B < A: 43 | print 32; 44 | else: 45 | print :other. 46 | . 47 | 48 | fn+ check if 2 else it A guard: 49 | if A is 12: 50 | print 12; 51 | if it is 32 :: when B is 23: 52 | print 32; 53 | if it is 33 :: when B > 12 :: when B < A: 54 | print 32; 55 | else: 56 | print :other. 57 | . 58 | 59 | fn+ check all A guard: 60 | if A is 12 :: when B > 5: 61 | print 12; 62 | if it is 32: 63 | print 32; 64 | else: 65 | print :other. 66 | . 67 | -------------------------------------------------------------------------------- /examples/lamdas.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ lambda 0: 3 | fn: :ok. 4 | . 5 | 6 | fn+ lambda 1: 7 | fn A: A + 1. 8 | . 9 | 10 | fn+ lambda 2: 11 | fn A B: A + B. 12 | . 13 | 14 | fn+ lambda 3: 15 | fn A :minus B: 16 | log info "is minus ~p ~p" [A B] 17 | A - B. 18 | . 19 | 20 | fn+ lambda multiple clauses: 21 | fn A :divided :by 0: #[:error :divide_by_zero]; 22 | fn A :divided :by B: #[:ok (A / B)]. 23 | . 24 | -------------------------------------------------------------------------------- /examples/maps.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ maps: 3 | Empty is {} 4 | One is {:a 42} 5 | Two is {:a 42 :b 65} 6 | Three is {:a 42 :b 65 :c 82} 7 | {(:a =) A} is Three 8 | Three1 is Three # {:b 66 :c 92}. 9 | -------------------------------------------------------------------------------- /examples/receive.ifx: -------------------------------------------------------------------------------- 1 | fn+ receive one: 2 | on message 43: do something here. 3 | . 4 | 5 | fn+ receive two: 6 | on message 43: do something here; 7 | on message :a: something else. 8 | . 9 | 10 | fn+ receive two and timeout: 11 | on message 43: do something here; 12 | on message :a: something else; 13 | after 50 milliseconds: do timeout thing. 14 | . 15 | 16 | fn+ receive one guard: 17 | on message A :: when A < 10: do something here. 18 | . 19 | 20 | fn+ receive two guard: 21 | on message A :: when A < 10 :: when A > 0: do something here; 22 | on message B :: when (is interger B): something else. 23 | . 24 | 25 | fn+ receive two guard and timeout: 26 | on message A :: when A < 10 :: when A > 0: do something here; 27 | on message B :: when (is interger B): something else; 28 | after 50 milliseconds: do timeout thing. 29 | . 30 | -------------------------------------------------------------------------------- /examples/records.ifx: -------------------------------------------------------------------------------- 1 | fn+ records: 2 | State is state # {counter 1 last_modification (now)} 3 | State1 is state # State {counter 2} 4 | Counter is state # counter State 5 | CounterIdx is state # counter 6 | (state # {counter Counter}) is State. 7 | -------------------------------------------------------------------------------- /examples/samples.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ say hello to Name: 3 | format "hello ~s!~n" with [Name]. 4 | 5 | fn+ format Str with Args: 6 | io :: format Str Args. 7 | -------------------------------------------------------------------------------- /examples/tlfn.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn divide A by 0: 3 | #[:error :division_by_zero]; 4 | fn divide A by B: 5 | #[:ok (A / B)]. 6 | 7 | fn format Str with Args: 8 | io :: format Str Args. 9 | 10 | fn+ export this one 0: 11 | 0; 12 | fn export this one A: 13 | A + 1. 14 | 15 | fn+ main: 16 | _ is #[1 1.5 :foo [] [:bar #[]]] 17 | other module :: multiply 3 by 7 18 | format "value is ~p" with [C] 19 | C is (divide 42 by 2). 20 | -------------------------------------------------------------------------------- /examples/try.ifx: -------------------------------------------------------------------------------- 1 | 2 | fn+ try always: 3 | try: 4 | something that may break 5 | something else; 6 | always: 7 | try to recover 8 | and cleanup. 9 | . 10 | 11 | fn+ try catch: 12 | try: 13 | something that may break 14 | something else; 15 | 16 | catch throw T: handle throw T; 17 | catch error E: handle error E; 18 | catch exit Ex: handle exit Ex; 19 | catch Type E: handle Type E. 20 | . 21 | 22 | fn+ try catch always: 23 | try: 24 | something that may break 25 | something else; 26 | 27 | catch throw T: handle throw T; 28 | catch error E: handle error E; 29 | catch exit Ex: handle exit Ex; 30 | catch Type E: handle Type E; 31 | 32 | always: 33 | try to recover 34 | and cleanup. 35 | . 36 | 37 | fn+ try catch guards: 38 | try: 39 | something that may break 40 | something else; 41 | 42 | catch throw T :: when (is integer T) :: when T < 10: handle throw T; 43 | catch error E :: when (is atom T): handle error E; 44 | catch exit Ex: handle exit Ex; 45 | catch Type E: handle Type E. 46 | . 47 | 48 | fn+ try catch always guards: 49 | try: 50 | something that may break 51 | something else; 52 | 53 | catch throw T :: when (is integer T) :: when T < 10: handle throw T; 54 | catch error E :: when (is atom T) :: when T != :foo: handle error E; 55 | catch exit Ex: handle exit Ex; 56 | catch Type E :: when T == E: handle Type E; 57 | 58 | always: 59 | try to recover 60 | and cleanup. 61 | . 62 | 63 | -------------------------------------------------------------------------------- /examples/whenex.ifx: -------------------------------------------------------------------------------- 1 | fn+ main: 2 | when A < 10: 3 | do something with A 4 | something else; 5 | when A < 20: 6 | log warning "A is ~p" [A]; 7 | else: 8 | log warn "wat". 9 | . 10 | -------------------------------------------------------------------------------- /include/ifix.hrl: -------------------------------------------------------------------------------- 1 | -define(CB(Clauses), {cb, _, Clauses}). 2 | -define(CB(Line, Clauses), {cb, Line, Clauses}). 3 | -define(CL(Exp), {clause, _, {Exp, _}}). 4 | -define(CL(Exp, Body), {clause, _, {Exp, Body}}). 5 | -define(CL(Line, Exp, Body), {clause, Line, {Exp, Body}}). 6 | -define(C(Names), {call, _, {Names, _}}). 7 | -define(C(Names, Args), {call, _, {Names, Args}}). 8 | -define(C(Line, Names, Args), {call, Line, {Names, Args}}). 9 | -define(Op(Line, Op, Left, Right), {call, Line, {['_', Op, '_'], [Left, Right]}}). 10 | -define(Op(Line, Op, Left), {call, Line, {[Op, '_'], [Left]}}). 11 | 12 | -define(V(Line, Name), {var, Line, Name}). 13 | -define(KW(Line, Name), {kw, Line, Name}). 14 | % clause call shape 15 | -define(CCS(Shape), {clause, _, {{call, _, {Shape, _}}, _}}). 16 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [no_debug_info]}. 2 | {deps, []}. 3 | 4 | {escript_incl_apps, 5 | ['interfix']}. 6 | {escript_top_level_app, 'interfix'}. 7 | {escript_name, 'interfix'}. 8 | {escript_emu_args, "%%! +sbtu +A0\n"}. 9 | 10 | %% Profiles 11 | {profiles, [{test, 12 | [{erl_opts, [debug_info]} 13 | ]}]}. 14 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /src/ifix_lexer.xrl: -------------------------------------------------------------------------------- 1 | Definitions. 2 | 3 | % numbers 4 | Number = [0-9] 5 | Float = [0-9]+\.[0-9]+([eE][-+]?[0-9]+)? 6 | 7 | % delimiters and operators 8 | Open = \( 9 | Close = \) 10 | OpenList = \[ 11 | CloseList = \] 12 | OpenMap = \{ 13 | CloseMap = \} 14 | Sep = , 15 | SemiColon = ; 16 | Endls = (\s|\t)*(\r?\n) 17 | Whites = \s+ 18 | Tabs = \t+ 19 | Colon = : 20 | Split = :: 21 | Dot = \. 22 | Hash = # 23 | 24 | % string stuff 25 | String = "(\\\^.|\\.|[^\"])*" 26 | BString = '(\\\^.|\\.|[^\'])*' 27 | AtomString = `(\\\^.|\\.|[^\`])*` 28 | 29 | % identifiers and atoms 30 | Identifier = [A-Z\_][a-zA-Z0-9\_]* 31 | Atom = ([a-z<=>\+\-\*\/\%\^\&\?\|\!\_][a-z<=>\+\-\*\/\%\^\&\?\|\!\_A-Z0-9]*) 32 | Kw = :[a-z<=>\+\-\*\/\%\^\&\?\|\!\_A-Z0-9]+ 33 | 34 | Rules. 35 | 36 | % numbers 37 | {Float} : make_token(float, TokenLine, TokenChars, fun erlang:list_to_float/1). 38 | {Number}+ : make_token(integer, TokenLine, TokenChars, fun erlang:list_to_integer/1). 39 | 40 | % delimiters and operators 41 | {Open} : make_token(open, TokenLine, TokenChars). 42 | {Close} : make_token(close, TokenLine, TokenChars). 43 | {Hash}{OpenList} : make_token(open_tuple, TokenLine, TokenChars). 44 | {OpenList} : make_token(open_list, TokenLine, TokenChars). 45 | {CloseList} : make_token(close_list, TokenLine, TokenChars). 46 | {OpenMap} : make_token(open_map, TokenLine, TokenChars). 47 | {CloseMap} : make_token(close_map , TokenLine, TokenChars). 48 | 49 | {Sep} : make_token(sep, TokenLine, TokenChars). 50 | {SemiColon} : make_token(semicolon, TokenLine, TokenChars). 51 | {Send} : make_token(send_op, TokenLine, TokenChars). 52 | {Hash} : make_token(atom, TokenLine, TokenChars). 53 | {At} : make_token(at, TokenLine, TokenChars). 54 | {Split} : make_token(split, TokenLine, TokenChars). 55 | {Colon} : make_token(colon, TokenLine, TokenChars). 56 | {Dot} : make_token(dot, TokenLine, TokenChars). 57 | {Arrow} : make_token(arrow, TokenLine, TokenChars). 58 | 59 | % string stuff 60 | {String} : build_string(string, TokenChars, TokenLine, TokenLen). 61 | {BString} : build_string(bstring, TokenChars, TokenLine, TokenLen). 62 | 63 | % identifiers and atoms 64 | {Identifier} : make_token(var, TokenLine, TokenChars). 65 | {Kw} : {token, atom(kw, tl(TokenChars), TokenLine)}. 66 | {Atom} : {token, atom(atom, TokenChars, TokenLine)}. 67 | {AtomString} : build_atom_string(TokenChars, TokenLine, TokenLen). 68 | 69 | % spaces, tabs and new lines 70 | {Endls} : make_token(nl, TokenLine, endls(TokenChars)). 71 | {Whites} : skip_token. 72 | {Tabs} : skip_token. 73 | 74 | Erlang code. 75 | 76 | make_token(Name, Line, Chars) when is_list(Chars) -> 77 | {token, {Name, Line, list_to_atom(Chars)}}; 78 | make_token(Name, Line, Chars) -> 79 | {token, {Name, Line, Chars}}. 80 | 81 | make_token(Name, Line, Chars, Fun) -> 82 | {token, {Name, Line, Fun(Chars)}}. 83 | 84 | endls(Chars) -> 85 | lists:filter(fun (C) -> C == $\n orelse C == $; end, Chars). 86 | 87 | atom(Type, String, TokenLine) -> 88 | {Type, TokenLine, build_atom(String, TokenLine)}. 89 | 90 | build_atom_string(Chars, Line, Len) -> 91 | String = unescape_string(lists:sublist(Chars, 2, Len - 2), Line), 92 | {token, {atom, Line, list_to_atom(String)}}. 93 | 94 | build_string(Type, Chars, Line, Len) -> 95 | String = unescape_string(lists:sublist(Chars, 2, Len - 2), Line), 96 | {token, {Type, Line, String}}. 97 | 98 | unescape_string(String, Line) -> unescape_string(String, Line, []). 99 | 100 | unescape_string([], _Line, Output) -> 101 | lists:reverse(Output); 102 | unescape_string([$\\, Escaped | Rest], Line, Output) -> 103 | Char = map_escaped_char(Escaped, Line), 104 | unescape_string(Rest, Line, [Char|Output]); 105 | unescape_string([Char|Rest], Line, Output) -> 106 | unescape_string(Rest, Line, [Char|Output]). 107 | 108 | map_escaped_char(Escaped, Line) -> 109 | case Escaped of 110 | $\\ -> $\\; 111 | $/ -> $/; 112 | $\" -> $\"; 113 | $\' -> $\'; 114 | $\( -> $(; 115 | $b -> $\b; 116 | $d -> $\d; 117 | $e -> $\e; 118 | $f -> $\f; 119 | $n -> $\n; 120 | $r -> $\r; 121 | $s -> $\s; 122 | $t -> $\t; 123 | $v -> $\v; 124 | _ -> throw({error, {Line, fn_lexer, ["unrecognized escape sequence: ", [$\\, Escaped]]}}) 125 | end. 126 | 127 | build_atom(Atom, _Line) -> list_to_atom(Atom). 128 | -------------------------------------------------------------------------------- /src/ifix_parser.yrl: -------------------------------------------------------------------------------- 1 | Nonterminals 2 | program tl_exprs tl_expr call 3 | call_block call_blocks call_block_1 4 | call_items call_item literal 5 | seq seq_items seq_item list map tuple. 6 | 7 | Terminals 8 | atom kw var integer float string bstring 9 | nl colon dot semicolon split 10 | open_list close_list 11 | open_map close_map 12 | open_tuple 13 | open close. 14 | 15 | Rootsymbol program. 16 | 17 | program -> tl_exprs : '$1'. 18 | program -> nl tl_exprs : '$2'. 19 | 20 | tl_exprs -> tl_expr : ['$1']. 21 | tl_exprs -> tl_expr nl tl_exprs : ['$1'|'$3']. 22 | 23 | tl_expr -> call : '$1'. 24 | tl_expr -> call_block : '$1'. 25 | 26 | call -> call_items : {call, line('$1'), parse_call_items('$1')}. 27 | call_block -> call_blocks dot : {cb, line('$1'), '$1'}. 28 | 29 | call_block_1 -> call colon tl_exprs : {clause, line('$1'), {'$1', '$3'}}. 30 | 31 | call_blocks -> call_block_1: ['$1']. 32 | call_blocks -> call_block_1 semicolon: ['$1']. 33 | call_blocks -> call_block_1 semicolon call_blocks : ['$1'|'$3']. 34 | 35 | call_items -> call_item : ['$1']. 36 | call_items -> call_item call_items : ['$1'|'$2']. 37 | 38 | call_item -> split : '$1'. 39 | call_item -> atom : '$1'. 40 | call_item -> literal : '$1'. 41 | call_item -> seq : '$1'. 42 | 43 | seq -> list : '$1'. 44 | seq -> map : '$1'. 45 | seq -> tuple: '$1'. 46 | 47 | list -> open_list close_list : {list, line('$1'), []}. 48 | list -> open_list seq_items close_list : {list, line('$1'), '$2'}. 49 | 50 | tuple -> open_tuple close_list : {tuple, line('$1'), []}. 51 | tuple -> open_tuple seq_items close_list : {tuple, line('$1'), '$2'}. 52 | 53 | map -> open_map close_map : {map, line('$1'), []}. 54 | map -> open_map seq_items close_map : {map, line('$1'), '$2'}. 55 | 56 | seq_items -> seq_item : ['$1']. 57 | seq_items -> seq_item seq_items : ['$1'|'$2']. 58 | 59 | seq_item -> literal : '$1'. 60 | seq_item -> atom : '$1'. 61 | seq_item -> seq : '$1'. 62 | 63 | literal -> kw : '$1'. 64 | literal -> var : '$1'. 65 | literal -> integer : '$1'. 66 | literal -> float: '$1'. 67 | literal -> string : '$1'. 68 | literal -> bstring : '$1'. 69 | literal -> open call close : '$2'. 70 | literal -> open call_blocks close : {cb, line('$2'), '$2'}. 71 | 72 | Erlang code. 73 | 74 | %unwrap({_,V}) -> V; 75 | %unwrap({_,_,V}) -> V; 76 | %unwrap(V) -> ct:print("WAT ~p", [V]). 77 | 78 | line(T) when is_tuple(T) -> element(2, T); 79 | line([H|_T]) -> element(2, H); 80 | line(T) -> ct:print("WAT ~p", [T]). 81 | 82 | parse_call_items(Items) -> parse_call_items(Items, [], []). 83 | 84 | parse_call_items([], Names, Values) -> 85 | {lists:reverse(Names), lists:reverse(Values)}; 86 | parse_call_items([{atom, _L, V}|T], Names, Values) -> 87 | parse_call_items(T, [V|Names], Values); 88 | parse_call_items([V={split, _L, _V}|T], Names, Values) -> 89 | parse_call_items(T, [V|Names], Values); 90 | parse_call_items([H={_T, _L, _V}|T], Names, Values) -> 91 | parse_call_items(T, ['_'|Names], [H|Values]). 92 | -------------------------------------------------------------------------------- /src/ifix_to_erl.erl: -------------------------------------------------------------------------------- 1 | -module(ifix_to_erl). 2 | -export([to_erl/1]). 3 | 4 | -include("../include/ifix.hrl"). 5 | 6 | -record(call, {line=2, names=[], args=[]}). 7 | -define(EMPTY_CALL, #call{names=[], args=[]}). 8 | -define(Call(Names, Args), #call{names=Names, args=Args}). 9 | -define(Call(Line, Names, Args), #call{line=Line, names=Names, args=Args}). 10 | 11 | to_erl(Ast) -> to_erl(Ast, #{errors => [], warnings => [], exported => [], 12 | level => 0}). 13 | 14 | to_erl(Ast, State) -> to_erl(Ast, [], State). 15 | 16 | to_erl([], Accum, State) -> {lists:reverse(Accum), State}; 17 | to_erl([H|T], Accum, State) -> 18 | case convert(H, State) of 19 | {ok, R, State1} -> to_erl(T, [R|Accum], State1); 20 | {error, Error, State1} -> to_erl(T, Accum, add_error(State1, H, Error)) 21 | end. 22 | 23 | add_error(State=#{errors := Errors}, Ast, Error) -> 24 | State#{errors => [#{error => Error, code => Ast}|Errors]}. 25 | 26 | reverse_call(#call{names=Names, args=Args}) -> 27 | #call{names=lists:reverse(Names), args=lists:reverse(Args)}. 28 | 29 | split_guards(Names, Args) -> 30 | split_guards(Names, Args, ?EMPTY_CALL, [], ?EMPTY_CALL, names). 31 | 32 | split_guards([], [], FCall, Guards, ?EMPTY_CALL, _) -> 33 | {reverse_call(FCall), lists:reverse(lists:map(fun reverse_call/1, Guards))}; 34 | split_guards(Names=[], Args=[], FCall, Guards, CurGuard, State) -> 35 | split_guards(Names, Args, FCall, [CurGuard|Guards], ?EMPTY_CALL, State); 36 | 37 | 38 | split_guards([{split, _, _}|T], Args, FCall, Guards, CurGuard=?EMPTY_CALL, names) -> 39 | split_guards(T, Args, FCall, Guards, CurGuard, guards); 40 | 41 | split_guards([{split, _, _}|T], Args, FCall, Guards, CurGuard=?EMPTY_CALL, State=guards) -> 42 | split_guards(T, Args, FCall, Guards, CurGuard, State); 43 | 44 | split_guards([{split, _, _}|T], Args, FCall, Guards, CurGuard, State=guards) -> 45 | split_guards(T, Args, FCall, [CurGuard|Guards], ?EMPTY_CALL, State); 46 | 47 | 48 | split_guards([Name='_'|T], [Arg|Args], ?Call(CNames, CArgs), Guards, 49 | CurGuard=?EMPTY_CALL, State=names) -> 50 | NewCall = ?Call([Name|CNames], [Arg|CArgs]), 51 | split_guards(T, Args, NewCall, Guards, CurGuard, State); 52 | 53 | split_guards([Name='_'|T], [Arg|Args], FCall, Guards, 54 | ?Call(CNames, CArgs), State=guards) -> 55 | NewGuard = ?Call([Name|CNames], [Arg|CArgs]), 56 | split_guards(T, Args, FCall, Guards, NewGuard, State); 57 | 58 | split_guards([Name|T], Args, ?Call(CNames, CArgs), Guards, 59 | CurGuard=?EMPTY_CALL, State=names) -> 60 | NewCall = ?Call([Name|CNames], CArgs), 61 | split_guards(T, Args, NewCall, Guards, CurGuard, State); 62 | 63 | split_guards([Name|T], Args, FCall, Guards, 64 | ?Call(CNames, CArgs), State=guards) -> 65 | NewGuard = ?Call([Name|CNames], CArgs), 66 | split_guards(T, Args, FCall, Guards, NewGuard, State). 67 | 68 | guards_to_erl(Guards, State) -> guards_to_erl(Guards, State, []). 69 | 70 | guards_to_erl([], State, Accum) -> 71 | {lists:reverse(Accum), State}; 72 | 73 | guards_to_erl([Guard=?Call(['when'|Names], Args)|T], State, Accum) -> 74 | case convert(?C(2, Names, Args), State) of 75 | {ok, R, State1} -> 76 | guards_to_erl(T, State1, [R|Accum]); 77 | {error, Error, State1} -> 78 | State2 = add_error(State1, Guard, Error), 79 | guards_to_erl(T, State2, Accum) 80 | end; 81 | guards_to_erl([Other|T], State, Accum) -> 82 | State1 = add_error(State, Other, invalid_guard), 83 | guards_to_erl(T, State1, Accum). 84 | 85 | 86 | convert_fn_clauses(Clauses=[?CL(?C([_Fn|FNames], Args))|_], State) -> 87 | {#call{names=FName, args=FArgs}, _Guards} = split_guards(FNames, Args), 88 | Arity = length(FArgs), 89 | convert_fn_clauses(Clauses, FName, Arity, [], State). 90 | 91 | convert_fn_clauses([], Name, Arity, Accum, State) -> 92 | {ok, to_name(Name), Arity, lists:reverse(Accum), State}; 93 | convert_fn_clauses([?CL(Line, ?C([_Fn|FNames], CArgs), Body)|T], Name, Arity, Accum, State) -> 94 | {#call{names=FName, args=FArgs}, Guards} = split_guards(FNames, CArgs), 95 | CurArity = length(FArgs), 96 | if CurArity /= Arity -> {error, head_arity_mismatch, State}; 97 | FName /= Name -> {error, head_name_mismatch, State}; 98 | true -> 99 | {EBody, State1} = to_erl(Body, [], State), 100 | {EArgs, State2} = to_erl(FArgs, [], State1), 101 | {EGuards, State3} = guards_to_erl(Guards, State2), 102 | EClause = {clause, Line, EArgs, EGuards, EBody}, 103 | convert_fn_clauses(T, Name, Arity, [EClause|Accum], State3) 104 | end. 105 | 106 | convert(?C(Line, [fn, ref, FName, '_'], [{integer, _, Arity}]), State) 107 | when FName /= '_' -> 108 | {ok, {'fun', Line, {function, FName, Arity}}, State}; 109 | 110 | convert(?C(Line, [fn, ref, ModName, FName, '_'], [{integer, _, Arity}]), State) 111 | when ModName /= '_', FName /= '_' -> 112 | {ok, {'fun', Line, {function, ModName, FName, Arity}}, State}; 113 | 114 | convert(?C(Line, [fn, ref|Names], _), State) -> 115 | Arity = length(lists:filter(fun('_') -> true; (_) -> false end, Names)), 116 | case to_name(Names) of 117 | [FName] -> 118 | {ok, {'fun', Line, {function, FName, Arity}}, State}; 119 | [ModName, FName] -> 120 | {ok, {'fun', Line, {function, ModName, FName, Arity}}, State} 121 | end; 122 | 123 | convert(?CB(Line, Clauses=[?CL(?C([Fn|_FName]))|_]), State=#{level := Level}) 124 | when Fn == 'fn'; Fn == 'fn+' -> 125 | if Level == 0 -> 126 | Exported = Fn == 'fn+', 127 | case convert_fn_clauses(Clauses, State#{level => Level + 1}) of 128 | {ok, [Name], Arity, EClauses, State1} -> 129 | State2 = if Exported -> add_export(Name, Arity, State1); 130 | true -> State1 131 | end, 132 | {ok, {function, Line, Name, Arity, EClauses}, State2#{level => Level}}; 133 | {error, Error, State1} -> 134 | {error, Error, State1} 135 | end; 136 | Fn == 'fn' -> 137 | case {check_clauses_shape(Clauses, fun check_lambda_shape/2), 138 | check_clauses_same_arity(Clauses)} of 139 | {ok, ok} -> 140 | {EClauses, State1} = with_clauses(Clauses, State#{level => Level + 1}, 141 | fun convert_lambda_clauses/6, []), 142 | R = {'fun', Line, {clauses, EClauses}}, 143 | {ok, R, State1#{level => Level}}; 144 | {{error, Reason}, _} -> 145 | {error, {bad_expr, 'fun', Reason}, State}; 146 | {_, {error, Reason}} -> 147 | {error, {bad_expr, 'fun', Reason}, State} 148 | end; 149 | true -> {error, fn_not_on_top_level, State} 150 | end; 151 | 152 | convert(?CB(Line, Clauses=[?CL(?C(['when'|_FName]))|_]), State) -> 153 | case check_clauses(Clauses, 'when', true) of 154 | ok -> 155 | {EClauses, State1} = with_clauses(Clauses, State, 156 | fun convert_when_clauses/6, []), 157 | R = {'if', Line, EClauses}, 158 | {ok, R, State1}; 159 | {error, Reason} -> 160 | {error, {bad_expr, 'when', Reason}, State} 161 | end; 162 | 163 | convert(?CB(Line, Clauses=[?CCS(['if', '_', is, '_'|_])|_]), State) -> 164 | case check_clauses_shape(Clauses, fun check_case_shape/2) of 165 | ok -> 166 | {MEClauses, State1} = with_clauses(Clauses, State, 167 | fun convert_case_clauses/6, []), 168 | [[EMatch, EFirstClause]|ERestClauses] = MEClauses, 169 | EClauses = [EFirstClause|ERestClauses], 170 | R = {'case', Line, EMatch, EClauses}, 171 | {ok, R, State1}; 172 | {error, Reason} -> 173 | {error, {bad_expr, 'case', Reason}, State} 174 | end; 175 | 176 | convert(?CB(Line, Clauses=[?CCS(['try'])|_]), State) -> 177 | case check_clauses_shape(Clauses, fun check_try_shape/2) of 178 | ok -> 179 | Vs = with_clauses(Clauses, State, fun convert_try_clauses/6, []), 180 | case Vs of 181 | {[{'finally', _, EFBody}, {'try', _, ETBody}|EClauses], State1} -> 182 | R = {'try', Line, ETBody, [], EClauses, EFBody}, 183 | {ok, R, State1}; 184 | {[{'try', _, ETBody}|EClauses], State1} -> 185 | R = {'try', Line, ETBody, [], EClauses, []}, 186 | {ok, R, State1} 187 | end; 188 | {error, Reason} -> 189 | {error, {bad_expr, 'try', Reason}, State} 190 | end; 191 | convert(?CB(Line, Clauses=[?CCS([on, message, '_'|_])|_]), State) -> 192 | case check_clauses_shape(Clauses, fun check_recv_shape/2) of 193 | ok -> 194 | Vs = with_clauses(Clauses, State, fun convert_recv_clauses/6, []), 195 | case Vs of 196 | {[{'after', _, ETimeout, EAfterBody}|EClauses], State1} -> 197 | R = {'receive', Line, EClauses, ETimeout, EAfterBody}, 198 | {ok, R, State1}; 199 | {EClauses, State1} -> 200 | R = {'receive', Line, EClauses}, 201 | {ok, R, State1} 202 | end; 203 | {error, Reason} -> 204 | {error, {bad_expr, 'receive', Reason}, State} 205 | end; 206 | 207 | convert(?CB([?CL({call, _, {['do'], _}}, Body)]), State) -> 208 | with_converted(Body, State, 209 | fun (State1, EBody) -> 210 | {ok, to_block(EBody), State1} 211 | end); 212 | 213 | convert(?CB(Line, [?CL(?C(GNames=['for', '_', in, '_'|_], GArgs), Body)]), State) -> 214 | {FirstGen, Guards} = split_guards(tl(GNames), GArgs), 215 | compile_lc(lc, Line, [FirstGen|Guards], Body, State, []); 216 | 217 | convert(?CB(Line, [?CL(?C(GNames=['for', '_', in, bin, '_'|_], GArgs), Body)]), State) -> 218 | {FirstGen, Guards} = split_guards(tl(GNames), GArgs), 219 | compile_lc(lc, Line, [FirstGen|Guards], Body, State, []); 220 | 221 | convert(?CB(Line, [?CL(?C(GNames=[bin, 'for', '_', in, '_'|_], GArgs), Body)]), State) -> 222 | {FirstGen, Guards} = split_guards(tl(tl(GNames)), GArgs), 223 | compile_lc(bc, Line, [FirstGen|Guards], Body, State, []); 224 | 225 | convert(?CB(Line, [?CL(?C(GNames=[bin, 'for', '_', in, bin, '_'|_], GArgs), Body)]), State) -> 226 | {FirstGen, Guards} = split_guards(tl(tl(GNames)), GArgs), 227 | compile_lc(bc, Line, [FirstGen|Guards], Body, State, []); 228 | 229 | convert(V={var, _, _}, State) -> {ok, V, State}; 230 | convert(V={integer, _, _}, State) -> {ok, V, State}; 231 | convert(V={float, _, _}, State) -> {ok, V, State}; 232 | convert(V={string, _, _}, State) -> {ok, V, State}; 233 | convert({bstring, Line, Val}, State) -> 234 | R = {bin, Line, [{bin_element, Line, {string, Line, Val}, default, default}]}, 235 | {ok, R, State}; 236 | convert({kw, Line, Val}, State) -> {ok, {atom, Line, Val}, State}; 237 | convert({list, Line, Val}, State) -> 238 | {R, State1} = list_to_cons_list(Line, Val, State), 239 | {ok, R, State1}; 240 | convert({tuple, Line, Val}, State) -> 241 | {EVal, State1} = to_erl(Val, [], State), 242 | {ok, {tuple, Line, EVal}, State1}; 243 | 244 | convert({map, Line, _Items}=Node, State) -> 245 | {Pairs, State1} = group_map_items(Node, State), 246 | {EPairs, State2} = convert_map_items(Pairs, State1), 247 | R = {map, Line, EPairs}, 248 | {ok, R, State2}; 249 | 250 | convert(?Op(Line, Op='+', Left, Right), State) -> op(Line, Op, Left, Right, State); 251 | convert(?Op(Line, Op='-', Left, Right), State) -> op(Line, Op, Left, Right, State); 252 | convert(?Op(Line, Op='*', Left, Right), State) -> op(Line, Op, Left, Right, State); 253 | convert(?Op(Line, Op='/', Left, Right), State) -> op(Line, Op, Left, Right, State); 254 | convert(?Op(Line, Op='//', Left, Right), State) -> op(Line, Op, Left, Right, State); 255 | convert(?Op(Line, Op='%', Left, Right), State) -> op(Line, Op, Left, Right, State); 256 | 257 | convert(?Op(Line, Op='&', Left, Right), State) -> op(Line, Op, Left, Right, State); 258 | convert(?Op(Line, Op='^', Left, Right), State) -> op(Line, Op, Left, Right, State); 259 | convert(?Op(Line, Op='|', Left, Right), State) -> op(Line, Op, Left, Right, State); 260 | 261 | convert(?Op(Line, Op='<<', Left, Right), State) -> op(Line, Op, Left, Right, State); 262 | convert(?Op(Line, Op='>>', Left, Right), State) -> op(Line, Op, Left, Right, State); 263 | 264 | convert(?Op(Line, Op='and', Left, Right), State)-> op(Line, Op, Left, Right, State); 265 | convert(?Op(Line, Op='or', Left, Right), State) -> op(Line, Op, Left, Right, State); 266 | convert(?Op(Line, Op='xor', Left, Right), State)-> op(Line, Op, Left, Right, State); 267 | 268 | convert(?Op(Line, Op='++', Left, Right), State) -> op(Line, Op, Left, Right, State); 269 | convert(?Op(Line, Op='--', Left, Right), State) -> op(Line, Op, Left, Right, State); 270 | 271 | convert(?Op(Line, Op='<', Left, Right), State) -> op(Line, Op, Left, Right, State); 272 | convert(?Op(Line, Op='>', Left, Right), State) -> op(Line, Op, Left, Right, State); 273 | convert(?Op(Line, Op='>=', Left, Right), State) -> op(Line, Op, Left, Right, State); 274 | convert(?Op(Line, Op='<=', Left, Right), State) -> op(Line, Op, Left, Right, State); 275 | convert(?Op(Line, Op='==', Left, Right), State) -> op(Line, Op, Left, Right, State); 276 | convert(?Op(Line, Op='!=', Left, Right), State) -> op(Line, Op, Left, Right, State); 277 | 278 | convert(?Op(Line, Op='not', Left), State) -> op(Line, Op, Left, State); 279 | convert(?Op(Line, Op='~', Left), State) -> op(Line, Op, Left, State); 280 | 281 | convert(?C(Line, [RecName, '#', '_'], [Map={map, _, _}]), State) -> 282 | {RFields, State1} = convert_record_fields(Map, State), 283 | R = {record, Line, RecName, RFields}, 284 | {ok, R, State1}; 285 | 286 | convert(?C(Line, [RecName, '#', '_', '_'], [Var={var, _, _}, Map={map, _, _}]), State) -> 287 | {RFields, State1} = convert_record_fields(Map, State), 288 | R = {record, Line, Var, RecName, RFields}, 289 | {ok, R, State1}; 290 | 291 | convert(?C(Line, [RecName, '#', FieldName, '_'], [Var={var, _, _}]), State) -> 292 | R = {record_field, Line, Var, RecName, {atom, Line, FieldName}}, 293 | {ok, R, State}; 294 | 295 | convert(?C(Line, [RecName, '#', FieldName], []), State) -> 296 | R = {record_index, Line, RecName, {atom, Line, FieldName}}, 297 | {ok, R, State}; 298 | 299 | 300 | convert(?Op(Line, '#', Var={var, _, _}, Map={map, _, _}), State) -> 301 | with_converted([Var, Map], State, 302 | fun (State1, [EVar, {map, _, EMapItems}]) -> 303 | R = {map, Line, EVar, EMapItems}, 304 | {ok, R, State1} 305 | end); 306 | 307 | convert(?C(Line, ['_', is|RNames], [Left|RVals]), State) -> 308 | with_converted([Left, ?C(Line, RNames, RVals)], State, 309 | fun (State1, [ELeft, ERight]) -> 310 | {ok, {match, Line, ELeft, ERight}, State1} 311 | end); 312 | 313 | convert(?C(Line, [cons, '_', '_'], [H, T]), State) -> 314 | with_converted([H, T], State, 315 | fun (State1, [EH, ET]) -> 316 | {ok, {cons, Line, EH, ET}, State1} 317 | end); 318 | convert(?C(['_'], [V]), State) -> 319 | convert(V, State); 320 | 321 | convert(Call=?C([First|_]), State) when First /= '_' -> 322 | convert_call(Call, State); 323 | 324 | convert(?C(Line, ['_', {split, _, _}, '_'|_Names], 325 | [?V(VMLine, ModName), ?V(VFLine, FunName)|Args]), State) -> 326 | {EArgs, State1} = to_erl(Args, [], State), 327 | {ok, {call, Line, 328 | {remote, Line, {var, VMLine, ModName}, {var, VFLine, FunName}}, 329 | EArgs}, State1}; 330 | 331 | convert(?C(Line, _Names, [?V(VLine, FunName)|Args]), State) -> 332 | {EArgs, State1} = to_erl(Args, [], State), 333 | {ok, {call, Line, {var, VLine, FunName}, EArgs}, State1}; 334 | 335 | convert(Call=?C(Line, [_|Names], [?KW(VLine, Name)|Args]), State) -> 336 | case all_values(Names) of 337 | true -> 338 | {EArgs, State1} = to_erl(Args, [], State), 339 | {ok, {tuple, Line, [{atom, VLine, Name}|EArgs]}, State1}; 340 | false -> 341 | convert_call(Call, State) 342 | end; 343 | 344 | convert(_, State) -> 345 | {error, unknown_node, State}. 346 | 347 | to_call_name(Line, Names) -> 348 | case to_name(Names) of 349 | [FName] -> 350 | {atom, Line, FName}; 351 | [MName, FName] -> 352 | {remote, Line, {atom, Line, MName}, {atom, Line, FName}} 353 | end. 354 | 355 | token_to_name('_') -> "O"; 356 | token_to_name(T) -> atom_to_list(T). 357 | 358 | drop_tail_underscores(Items) -> 359 | lists:reverse(lists:dropwhile(fun ('_') -> true; 360 | (_) -> false 361 | end, lists:reverse(Items))). 362 | 363 | to_name(Items) -> to_name(drop_tail_underscores(Items), [], []). 364 | 365 | to_name([], CurAccum, Accum) -> 366 | lists:reverse(add_name_part(CurAccum, Accum)); 367 | to_name([{split, _, _}|T], CurAccum, Accum) -> 368 | to_name(T, [], add_name_part(CurAccum, Accum)); 369 | to_name([H|T], CurAccum, Accum) -> 370 | to_name(T, [H|CurAccum], Accum). 371 | 372 | add_name_part([], Accum) -> Accum; 373 | add_name_part(CurAccum, Accum) -> 374 | StrItems = lists:map(fun token_to_name/1, lists:reverse(CurAccum)), 375 | CurName = list_to_atom(string:join(StrItems, "_")), 376 | [CurName|Accum]. 377 | 378 | op(Line, Op, Left, Right, State) -> 379 | with_converted([Left, Right], State, 380 | fun (State1, [ELeft, ERight]) -> 381 | {ok, {op, Line, map_op(Op), ELeft, ERight}, State1} 382 | end). 383 | 384 | op(Line, Op, Left, State) -> 385 | with_converted([Left], State, 386 | fun (State1, [ELeft]) -> 387 | {ok, {op, Line, map_op(Op), ELeft}, State1} 388 | end). 389 | 390 | map_op('+') -> '+'; 391 | map_op('-') -> '-'; 392 | map_op('*') -> '*'; 393 | map_op('/') -> '/'; 394 | map_op('//') -> 'div'; 395 | map_op('%') -> 'rem'; 396 | map_op('|') -> 'bor'; 397 | map_op('&') -> 'band'; 398 | map_op('^') -> 'bxor'; 399 | map_op('>>') -> 'bsr'; 400 | map_op('<<') -> 'bsl'; 401 | map_op('~') -> 'bnot'; 402 | map_op('and') -> 'andalso'; 403 | map_op('or') -> 'orelse'; 404 | map_op('xor') -> 'xor'; 405 | map_op('!') -> '!'; 406 | map_op('not') -> 'not'; 407 | map_op('++') -> '++'; 408 | map_op('--') -> '--'; 409 | map_op('<') -> '<'; 410 | map_op('<=') -> '=<'; 411 | map_op('>') -> '>'; 412 | map_op('>=') -> '>='; 413 | map_op('==') -> '=:='; 414 | map_op('!=') -> '=/='. 415 | 416 | convert_nodes(Nodes, State) -> 417 | R = lists:foldl(fun (Node, {Status, Accum, StateIn}) -> 418 | case convert(Node, StateIn) of 419 | {ok, R, StateOut} -> 420 | {Status, [R|Accum], StateOut}; 421 | {error, Error, StateOut} -> 422 | {error, Accum, add_error(StateOut, Node, Error)} 423 | end 424 | end, {ok, [], State}, Nodes), 425 | {Status, ENodes, State1} = R, 426 | {Status, lists:reverse(ENodes), State1}. 427 | 428 | with_converted(Nodes, State, Fun) -> 429 | case convert_nodes(Nodes, State) of 430 | {ok, ENodes, State1} -> 431 | Fun(State1, ENodes); 432 | {error, _ENodes, State1} -> 433 | {error, parsing_expr, State1} 434 | end. 435 | 436 | with_converted(Nodes, Body, State, Fun) -> 437 | case convert_nodes(Nodes, State) of 438 | {ok, ENodes, State1} -> 439 | case convert_nodes(Body, State1) of 440 | {ok, EBody, State2} -> 441 | Fun(State2, ENodes, EBody); 442 | {error, _ENodes, State2} -> 443 | {error, parsing_body_expr, State2} 444 | end; 445 | {error, _ENodes, State1} -> 446 | {error, parsing_expr, State1} 447 | end. 448 | 449 | add_export(Name, Arity, State=#{exported := Exported}) -> 450 | State#{exported => [{Name, Arity}|Exported]}. 451 | 452 | list_to_cons_list(Line, Val, State) -> 453 | list_to_cons_list_r(Line, lists:reverse(Val), {nil, Line}, State). 454 | 455 | list_to_cons_list_r(_Line, [], Cons, State) -> 456 | {Cons, State}; 457 | 458 | list_to_cons_list_r(Line, [H|T], Cons, State) -> 459 | case convert(H, State) of 460 | {ok, EH, State1} -> 461 | list_to_cons_list_r(Line, T, {cons, Line, EH, Cons}, State1); 462 | {error, Error, State1} -> 463 | list_to_cons_list_r(Line, T, Cons, add_error(State1, H, Error)) 464 | end. 465 | 466 | check_clauses([], _Name, _AllowElse) -> true; 467 | check_clauses([?CL(?C([Name|_]))|T], Name, AllowElse) -> 468 | check_clauses(T, Name, AllowElse); 469 | check_clauses([?CL(?C(['else'], []))], _Name, AllowElse) -> 470 | if AllowElse -> ok; 471 | true -> {error, else_not_allowed} 472 | end; 473 | check_clauses([Other|_], _Name, _AllowElse) -> 474 | {error, {bad_clause, Other}}. 475 | 476 | convert_when_clauses(Line, 'when', Names, Args, Body, State) -> 477 | Cond = ?C(Line, Names, Args), 478 | case convert(Cond, State) of 479 | {ok, ECond, State1} -> 480 | case to_erl(Body, State1) of 481 | {EBody, State2} -> 482 | R = {clause, Line, [], [[ECond]], EBody}, 483 | {ok, R, State2}; 484 | Other -> Other 485 | end; 486 | Other -> Other 487 | end; 488 | convert_when_clauses(Line, 'else', [], [], Body, State) -> 489 | case to_erl(Body, State) of 490 | {EBody, State1} -> 491 | R = {clause, Line, [], [[{atom, Line, true}]], EBody}, 492 | {ok, R, State1}; 493 | Other -> Other 494 | end. 495 | 496 | convert_case_clauses(Line, 'if', ['_', is, '_'|GNames], [Expr, Match|GArgs], Body, State) -> 497 | {_, Guards} = split_guards(GNames, GArgs), 498 | {EGuards, State1} = guards_to_erl(Guards, State), 499 | with_converted([Expr, Match], Body, State1, 500 | fun (State2, [EExpr, EMatch], EBody) -> 501 | R = [EExpr, {clause, Line, [EMatch], EGuards, EBody}], 502 | {ok, R, State2} 503 | end); 504 | convert_case_clauses(Line, 'if', [it, is, '_'|GNames], [Match|GArgs], Body, State) -> 505 | {_, Guards} = split_guards(GNames, GArgs), 506 | {EGuards, State1} = guards_to_erl(Guards, State), 507 | with_converted([Match], Body, State1, 508 | fun (State2, [EMatch], EBody) -> 509 | R = {clause, Line, [EMatch], EGuards, EBody}, 510 | {ok, R, State2} 511 | end); 512 | convert_case_clauses(Line, 'else', [], [], Body, State) -> 513 | case to_erl(Body, State) of 514 | {EBody, State1} -> 515 | R = {clause, Line, [{var, Line, '_'}], [], EBody}, 516 | {ok, R, State1}; 517 | Other -> Other 518 | end. 519 | 520 | convert_recv_clauses(Line, 'on', [message, '_'|GNames], [Match|GArgs], Body, State) -> 521 | {_, Guards} = split_guards(GNames, GArgs), 522 | {EGuards, State1} = guards_to_erl(Guards, State), 523 | with_converted([Match], Body, State1, 524 | fun (State2, [EMatch], EBody) -> 525 | R = {clause, Line, [EMatch], EGuards, EBody}, 526 | {ok, R, State2} 527 | end); 528 | convert_recv_clauses(Line, 'after', ['_', milliseconds], [AfterExpr], Body, State) -> 529 | with_converted([AfterExpr], Body, State, 530 | fun (State1, [EAfter], EBody) -> 531 | R = {'after', Line, EAfter, EBody}, 532 | {prepend, R, State1} 533 | end). 534 | 535 | convert_try_clauses(Line, 'try', [], [], Body, State) -> 536 | with_converted(Body, State, 537 | fun (State1, EBody) -> 538 | {ok, {'try', Line, EBody}, State1} 539 | end); 540 | 541 | convert_try_clauses(Line, 'catch', ['_', '_'|GNames], [Type, Catch|GArgs], Body, State) -> 542 | {_, Guards} = split_guards(GNames, GArgs), 543 | {EGuards, State1} = guards_to_erl(Guards, State), 544 | with_converted([Type, Catch], Body, State1, 545 | fun (State2, [EType, ECatch], EBody) -> 546 | R = {clause, Line, 547 | [{tuple, Line, [EType, ECatch, 548 | {var, Line, '_'}]}], 549 | EGuards, EBody}, 550 | {ok, R, State2} 551 | end); 552 | 553 | convert_try_clauses(Line, 'catch', [Type, '_'|GNames], [Catch|GArgs], Body, State) -> 554 | {_, Guards} = split_guards(GNames, GArgs), 555 | {EGuards, State1} = guards_to_erl(Guards, State), 556 | with_converted([Catch], Body, State1, 557 | fun (State2, [ECatch], EBody) -> 558 | R = {clause, Line, 559 | [{tuple, Line, [{atom, Line, Type}, ECatch, 560 | {var, Line, '_'}]}], 561 | EGuards, EBody}, 562 | {ok, R, State2} 563 | end); 564 | 565 | convert_try_clauses(Line, 'always', [], [], Body, State) -> 566 | with_converted(Body, State, 567 | fun (State1, EBody) -> 568 | {prepend, {'finally', Line, EBody}, State1} 569 | end). 570 | 571 | convert_lambda_clauses(Line, 'fn', FNames, Args, Body, State) -> 572 | {#call{args=FArgs}, Guards} = split_guards(FNames, Args), 573 | with_converted(FArgs, Body, State, 574 | fun (State1, EArgs, EBody) -> 575 | {EGuards, State2} = guards_to_erl(Guards, State1), 576 | {ok, {clause, Line, EArgs, EGuards, EBody}, State2} 577 | end). 578 | 579 | with_clauses([], State, _Fun, Accum) -> 580 | {lists:reverse(Accum), State}; 581 | with_clauses([Clause=?CL(?C(Line, [Type|Rest], Args), Body)|T], State, Fun, Accum) -> 582 | case Fun(Line, Type, Rest, Args, Body, State) of 583 | {ok, R, State1} -> 584 | with_clauses(T, State1, Fun, [R|Accum]); 585 | {prepend, R, State1} -> 586 | with_clauses(T, State1, Fun, Accum ++ [R]); 587 | {error, Reason, State1} -> 588 | State2 = add_error(State1, Clause, Reason), 589 | with_clauses(T, State2, Fun, Accum) 590 | end. 591 | 592 | check_clauses_shape(Clauses, Fun) -> 593 | check_clauses_shape(Clauses, Fun, first). 594 | 595 | check_clauses_shape([], _Fun, _Pos) -> ok; 596 | check_clauses_shape([Clause|Clauses], Fun, Pos) -> 597 | case Fun(Clause, Pos) of 598 | ok -> check_clauses_shape(Clauses, Fun, next_pos(Pos, Clauses)); 599 | {error, _Reason}=Error -> Error 600 | end. 601 | 602 | next_pos(first, []) -> 'end'; % shouldn't be used 603 | next_pos(first, [_]) -> last; 604 | next_pos(first, _) -> middle; 605 | next_pos(middle, []) -> 'end'; % shouldn't be used 606 | next_pos(middle, [_]) -> last; 607 | next_pos(middle, _) -> middle; 608 | next_pos(last, _) -> 'end'. % shouldn't be used 609 | 610 | check_try_shape(?CCS(['try']), first) -> ok; 611 | check_try_shape(?CCS(['catch', error, '_']), middle) -> ok; 612 | check_try_shape(?CCS(['catch', error, '_', {split, _, _}|_]), middle) -> ok; 613 | check_try_shape(?CCS(['catch', throw, '_']), middle) -> ok; 614 | check_try_shape(?CCS(['catch', throw, '_', {split, _, _}|_]), middle) -> ok; 615 | check_try_shape(?CCS(['catch', exit, '_']), middle) -> ok; 616 | check_try_shape(?CCS(['catch', exit, '_', {split, _, _}|_]), middle) -> ok; 617 | check_try_shape(?CCS(['catch', '_', '_']), middle) -> ok; 618 | check_try_shape(?CCS(['catch', '_', '_', {split, _, _}|_]), middle) -> ok; 619 | 620 | check_try_shape(?CCS(['catch', error, '_']), last) -> ok; 621 | check_try_shape(?CCS(['catch', error, '_', {split, _, _}|_]), last) -> ok; 622 | check_try_shape(?CCS(['catch', throw, '_']), last) -> ok; 623 | check_try_shape(?CCS(['catch', throw, '_', {split, _, _}|_]), last) -> ok; 624 | check_try_shape(?CCS(['catch', exit, '_']), last) -> ok; 625 | check_try_shape(?CCS(['catch', exit, '_', {split, _, _}|_]), last) -> ok; 626 | check_try_shape(?CCS(['catch', '_', '_']), last) -> ok; 627 | check_try_shape(?CCS(['catch', '_', '_', {split, _, _}|_]), last) -> ok; 628 | 629 | check_try_shape(?CCS(['always']), last) -> ok; 630 | check_try_shape(?CCS(Other), Pos) -> {error, {bad_try_clause, Pos, Other}}. 631 | 632 | 633 | check_case_shape(?CCS(['if', '_', is, '_']), first) -> ok; 634 | check_case_shape(?CCS(['if', '_', is, '_', {split, _, _}|_]), first) -> ok; 635 | check_case_shape(?CCS(['if', it, is, '_']), middle) -> ok; 636 | check_case_shape(?CCS(['if', it, is, '_', {split, _, _}|_]), middle) -> ok; 637 | check_case_shape(?CCS(['if', it, is, '_']), last) -> ok; 638 | check_case_shape(?CCS(['if', it, is, '_', {split, _, _}|_]), last) -> ok; 639 | check_case_shape(?CCS([else]), last) -> ok; 640 | check_case_shape(?CCS(Other), Pos) -> {error, {bad_case_clause, Pos, Other}}. 641 | 642 | check_recv_shape(?CCS([on, message, '_']), first) -> ok; 643 | check_recv_shape(?CCS([on, message, '_', {split, _, _}|_]), first) -> ok; 644 | check_recv_shape(?CCS([on, message, '_']), middle) -> ok; 645 | check_recv_shape(?CCS([on, message, '_', {split, _, _}|_]), middle) -> ok; 646 | check_recv_shape(?CCS([on, message, '_']), last) -> ok; 647 | check_recv_shape(?CCS([on, message, '_', {split, _, _}|_]), last) -> ok; 648 | check_recv_shape(?CCS(['after', '_', milliseconds]), last) -> ok; 649 | check_recv_shape(?CCS(Other), Pos) -> {error, {bad_receive_clause, Pos, Other}}. 650 | 651 | check_lambda_shape(?CCS(['fn'|Names]), _) -> 652 | case lists:all(fun ('_') -> true; (_) -> false end, drop_guards(Names)) of 653 | true -> ok; 654 | false -> {error, {bad_lambda_clause, {invalid_arguments, Names}}} 655 | end. 656 | 657 | check_clauses_same_arity([?CCS(['fn'|FNames])|Clauses]) -> 658 | Arity = length(drop_guards(FNames)), 659 | lists:foldl(fun (?CCS(['fn'|Names]), Status) -> 660 | CurArity = length(drop_guards(Names)), 661 | if CurArity =/= Arity -> {error, head_mismatch}; 662 | true -> Status 663 | end 664 | end, ok, Clauses). 665 | 666 | all_values([]) -> true; 667 | all_values(['_'|T]) -> all_values(T); 668 | all_values(_) -> false. 669 | 670 | convert_call(?C(Line, Names, Args), State) -> 671 | Name = to_call_name(Line, Names), 672 | {EArgs, State1} = to_erl(Args, [], State), 673 | {ok, {call, Line, Name, EArgs}, State1}. 674 | 675 | drop_guards(Names) -> drop_guards(Names, []). 676 | 677 | drop_guards([], Accum) -> lists:reverse(Accum); 678 | drop_guards([{split, _, _}|_], Accum) -> lists:reverse(Accum); 679 | drop_guards([H|T], Accum) -> drop_guards(T, [H|Accum]). 680 | 681 | convert_map_items(Pairs, State) -> convert_map_items(Pairs, State, []). 682 | 683 | convert_map_items([], State, Accum) -> 684 | {lists:reverse(Accum), State}; 685 | convert_map_items([{?C(KLine, ['_', '='], [Key]), Val}|T], State, Accum) -> 686 | Fun = fun (State1, [EKey, EVal]) -> 687 | R = {map_field_exact, KLine, EKey, EVal}, 688 | {ok, R, State1} 689 | end, 690 | convert_map_pair(Key, Val, State, Accum, T, Fun); 691 | 692 | convert_map_items([{Key, Val}|T], State, Accum) -> 693 | Fun = fun (State1, [EKey, EVal]) -> 694 | KLine = element(2, EKey), 695 | R = {map_field_assoc, KLine, EKey, EVal}, 696 | {ok, R, State1} 697 | end, 698 | convert_map_pair(Key, Val, State, Accum, T, Fun). 699 | 700 | 701 | convert_map_pair(Key, Val, State, Accum, T, Fun) -> 702 | case with_converted([Key, Val], State, Fun) of 703 | {ok, R, State1} -> 704 | convert_map_items(T, State1, [R|Accum]); 705 | {error, _, State1} -> 706 | convert_map_items(T, State1, Accum) 707 | end. 708 | 709 | convert_record_fields(Map, State) -> 710 | {Pairs, State1} = group_map_items(Map, State), 711 | convert_record_fields(Pairs, State1, []). 712 | 713 | convert_record_fields([], State, Accum) -> {lists:reverse(Accum), State}; 714 | convert_record_fields([{{atom, KLine, K}, Val}|T], State, Accum) -> 715 | case convert(Val, State) of 716 | {ok, EVal, State1} -> 717 | EField = {record_field, KLine, {atom, KLine, K}, EVal}, 718 | convert_record_fields(T, State1, [EField|Accum]); 719 | {error, Reason, State1} -> 720 | State2 = add_error(State1, Val, {bad_record_value, Reason}), 721 | convert_record_fields(T, State2, Accum) 722 | end; 723 | 724 | convert_record_fields([{Other, _Val}|T], State, Accum) -> 725 | State1 = add_error(State, Other, bad_record_key), 726 | convert_record_fields(T, State1, Accum). 727 | 728 | group_map_items({map, _Line, Items}=Node, State) -> 729 | State1 = if length(Items) rem 2 /= 0 -> 730 | add_error(State, Node, odd_number_of_map_items); 731 | true -> State 732 | end, 733 | 734 | {PairsR, _} = lists:foldl(fun (Item, {Accum, nil}) -> 735 | {Accum, Item}; 736 | (Item, {Accum, First}) -> 737 | {[{First, Item}|Accum], nil} 738 | end, {[], nil}, Items), 739 | 740 | Pairs = lists:reverse(PairsR), 741 | {Pairs, State1}. 742 | 743 | compile_lc(Type, Line, [], Body, State, Accum) -> 744 | EGFs = lists:reverse(Accum), 745 | case to_erl(Body, State) of 746 | {EBody, State1} -> 747 | R = {Type, Line, to_block(EBody), EGFs}, 748 | {ok, R, State1}; 749 | Other -> Other 750 | end; 751 | compile_lc(Type, Line, [?Call(GLine, ['_', in, '_'], [Val, Seq])|T], Body, State, Accum) -> 752 | with_converted([Val, Seq], State, 753 | fun (State1, [EVal, ESeq]) -> 754 | R = {generate, GLine, EVal, ESeq}, 755 | compile_lc(Type, Line, T, Body, State1, [R|Accum]) 756 | end); 757 | compile_lc(Type, Line, [?Call(GLine, ['_', in, bin, '_'], [Val, Seq])|T], Body, State, Accum) -> 758 | with_converted([Val, Seq], State, 759 | fun (State1, [EVal, ESeq]) -> 760 | R = {b_generate, GLine, EVal, ESeq}, 761 | compile_lc(Type, Line, T, Body, State1, [R|Accum]) 762 | end); 763 | compile_lc(Type, Line, [?Call(GLine, ['when'|Names], Args)|T], Body, State, Accum) -> 764 | Call = ?C(GLine, Names, Args), 765 | case convert(Call, State) of 766 | {ok, R, State1} -> 767 | compile_lc(Type, Line, T, Body, State1, [R|Accum]); 768 | {error, Error, State1} -> 769 | State2 = add_error(State1, Call, Error), 770 | compile_lc(Type, Line, T, Body, State2, Accum) 771 | end; 772 | compile_lc(Type, Line, [Other|T], Body, State, Accum) -> 773 | State1 = add_error(State, Other, bad_for_expr), 774 | compile_lc(Type, Line, T, Body, State1, Accum). 775 | 776 | to_block([Expr]) -> 777 | Expr; 778 | to_block(EBody=[H|_]) -> 779 | Line = element(2, H), 780 | {'block', Line, EBody}. 781 | -------------------------------------------------------------------------------- /src/interfix.app.src: -------------------------------------------------------------------------------- 1 | {application, 'interfix', 2 | [{description, "An escript"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib 8 | ]}, 9 | {env,[]}, 10 | {modules, []}, 11 | 12 | {contributors, []}, 13 | {licenses, []}, 14 | {links, []} 15 | ]}. 16 | -------------------------------------------------------------------------------- /src/interfix.erl: -------------------------------------------------------------------------------- 1 | -module('interfix'). 2 | 3 | %% API exports 4 | -export([to_lex/1, to_ast/1, to_erl_ast/1, to_erl/1, to_mod/1, to_code/1, 5 | to_code/2, compile/2, compile/3]). 6 | -export([str_to_lex/1, str_to_ast/1, str_to_erl_ast/1]). 7 | 8 | %% CLI exports 9 | -export([main/1]). 10 | 11 | %%==================================================================== 12 | %% API functions 13 | %%==================================================================== 14 | %% 15 | read_file(Path) -> 16 | case file:read_file(Path) of 17 | {ok, Content} -> {ok, unicode:characters_to_list(Content, utf8)}; 18 | Other -> Other 19 | end. 20 | 21 | with_file_content(Path, Fn) -> 22 | case read_file(Path) of 23 | {ok, Content} -> Fn(Content); 24 | Other -> Other 25 | end. 26 | 27 | to_lex(Path) -> with_file_content(Path, fun str_to_lex/1). 28 | to_ast(Path) -> with_file_content(Path, fun str_to_ast/1). 29 | to_erl_ast(Path) -> with_file_content(Path, fun str_to_erl_ast/1). 30 | to_erl(Path) -> 31 | case to_mod(Path) of 32 | {ok, Mod} -> erl_prettypr:format(erl_syntax:form_list(Mod)); 33 | Other -> Other 34 | end. 35 | to_mod(Path) -> 36 | case to_erl_ast(Path) of 37 | {Ast, State=#{exported := Exports}} -> 38 | ModAtomName = get_module_name(Path), 39 | ToMod = fun () -> 40 | ModAttr = {attribute, 1, module, ModAtomName}, 41 | FileAttr = {attribute, 1, file, {Path, 1}}, 42 | ExportAttr = {attribute, 1, export, lists:reverse(Exports)}, 43 | {ok, [FileAttr, ModAttr, ExportAttr|Ast]} 44 | end, 45 | format_errors_or(ModAtomName, State, ToMod); 46 | Other -> Other 47 | end. 48 | 49 | to_code(Path) -> 50 | to_code(Path, []). 51 | 52 | to_code(Path, Opts) -> 53 | case to_mod(Path) of 54 | {ok, Ast} -> 55 | case compile:forms(Ast, [return, strong_validation|Opts]) of 56 | {error, _Errors, _Warnings}=Error -> Error; 57 | error -> {error, [{error, compile_forms_error}], []}; 58 | _ -> 59 | case compile:forms(Ast, Opts) of 60 | {ok, ModuleName, Code} -> {ok, ModuleName, Code, []}; 61 | {ok, _ModuleName, _Code, _Warnings}=Res -> Res; 62 | error -> {error, [{error, compile_forms_error}], []}; 63 | {error, _Errors, _Warnings}=Error -> Error 64 | end 65 | end; 66 | Other -> Other 67 | end. 68 | 69 | compile(Path, DestPath) -> 70 | compile(Path, DestPath, []). 71 | 72 | compile(Path, DestPath, Opts) -> 73 | case to_code(Path, Opts) of 74 | {ok, ModuleName, Code, Warnings} -> 75 | BeamPath = filename:join(DestPath, get_module_beam_name(Path)), 76 | case bin_to_file(Code, BeamPath) of 77 | error -> {error, [{file_write_error, BeamPath}], []}; 78 | ok -> {ok, [{warnings, Warnings}, {module_name, ModuleName}]} 79 | end; 80 | Other -> Other 81 | end. 82 | 83 | bin_to_file(Bin, Path) -> 84 | to_file(Bin, Path, [binary, write]). 85 | 86 | to_file(Data, Path, Mode) -> 87 | case file:open(Path, Mode) of 88 | {ok, Device} -> 89 | file:write(Device, Data), 90 | file:close(Device), 91 | ok; 92 | Error -> Error 93 | end. 94 | 95 | get_module_beam_name(Path) -> 96 | ModuleNameStr = get_module_str_name(Path), 97 | string:concat(ModuleNameStr, ".beam"). 98 | 99 | get_module_str_name(Path) -> 100 | BaseName = filename:basename(Path), 101 | filename:rootname(BaseName). 102 | 103 | get_module_name(Path) -> 104 | list_to_atom(get_module_str_name(Path)). 105 | format_errors_or(_Module, #{errors:=[]}, Fn) -> Fn(); 106 | format_errors_or(Module, #{errors:=Errors}, _Fn) -> 107 | ErrorsFirstToLast = lists:reverse(Errors), 108 | lists:foreach(fun (#{error := Error, code := Ast}) -> 109 | Line = line(Ast), 110 | io:format("error:~p:~p: ~p~n~p~n~n", [Module, Line, Error, Ast]) 111 | end, ErrorsFirstToLast), 112 | {error, compile_errors}. 113 | 114 | str_to_lex(String) -> 115 | case ifix_lexer:string(String) of 116 | {ok, Tokens, Endline} -> 117 | CleanTokens = clean_tokens(Tokens), 118 | {ok, CleanTokens, Endline}; 119 | {eof, Endline} -> {error, {Endline, fn_lexer, {eof, Endline}}}; 120 | {error, Error} -> {error, Error}; 121 | {error, Error, _} -> {error, Error} 122 | end. 123 | 124 | str_to_ast(Str) -> 125 | case str_to_lex(Str) of 126 | {ok, Tokens, _NewLine} -> ifix_parser:parse(Tokens); 127 | Other -> Other 128 | end. 129 | 130 | str_to_erl_ast(Str) -> 131 | case str_to_ast(Str) of 132 | {ok, Ast} -> ifix_to_erl:to_erl(Ast); 133 | Other -> Other 134 | end. 135 | 136 | clean_tokens(Tokens) -> clean_tokens(Tokens, []). 137 | 138 | clean_tokens([], Accum) -> lists:reverse(Accum); 139 | % remove newline after colon, semicolon 140 | clean_tokens([{colon, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum); 141 | clean_tokens([{semicolon, _, _}=H, {nl, _, _}|T], Accum) -> clean_tokens([H|T], Accum); 142 | % remove duplicated endlines 143 | clean_tokens([{nl, _, _}, {nl, _, _}=H|T], Accum) -> clean_tokens([H|T], Accum); 144 | % remove newline before dots 145 | clean_tokens([{nl, _, _}, V={dot, _, _}|T], Accum) -> 146 | clean_tokens([V|T], Accum); 147 | % remove newline before splits 148 | clean_tokens([{nl, _, _}, V={split, _, _}|T], Accum) -> 149 | clean_tokens([V|T], Accum); 150 | % remove last endline 151 | clean_tokens([{nl, _, _}], Accum) -> clean_tokens([], Accum); 152 | clean_tokens([H|T], Accum) -> clean_tokens(T, [H|Accum]). 153 | 154 | normalize_error({error, Reason}) -> io_lib:format("~p", [Reason]). 155 | 156 | print({ok, Data}) -> 157 | print(Data); 158 | print({error, _}=Error) -> 159 | Reason = normalize_error(Error), 160 | io:format("error:~s~n", [Reason]); 161 | print(Data) -> 162 | try io:format("~s~n", [Data]) catch 163 | _:_ -> io:format("~p~n", [Data]) 164 | end. 165 | 166 | %% escript Entry point 167 | main(["lex", Path]) -> 168 | case to_lex(Path) of 169 | {ok, Data, _EndLine} -> print(Data); 170 | Other -> print(Other) 171 | end; 172 | main(["ast", Path]) -> print(to_ast(Path)); 173 | main(["erl", Path]) -> print(to_erl(Path)); 174 | main(["mod", Path]) -> print(to_mod(Path)); 175 | main(["erlast", Path]) -> print(to_erl_ast(Path)); 176 | main(["beam", Path, DestPath]) -> print(compile(Path, DestPath)); 177 | main(Args) -> 178 | io:format("Unknown Command: ~p~n", [Args]), 179 | erlang:halt(0). 180 | 181 | %%==================================================================== 182 | %% Internal functions 183 | %%==================================================================== 184 | 185 | line(T) when is_tuple(T) -> element(2, T); 186 | line([H|_]) -> line(H). 187 | --------------------------------------------------------------------------------