├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── Basic.elm ├── BrowserElement.elm ├── Lists.elm └── MutateRecord.elm └── src ├── ast ├── binop.rs ├── expression │ ├── access.rs │ ├── access_function.rs │ ├── character.rs │ ├── core.rs │ ├── float.rs │ ├── function.rs │ ├── integer.rs │ ├── mod.rs │ ├── string.rs │ └── variable.rs ├── helpers.rs ├── mod.rs ├── statement │ ├── comment.rs │ ├── core.rs │ ├── export.rs │ ├── import.rs │ ├── infix.rs │ ├── mod.rs │ ├── module.rs │ ├── port.rs │ └── type_declaration.rs └── type_ │ ├── core.rs │ └── mod.rs ├── check.rs ├── dump.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aho-corasick" 3 | version = "0.6.4" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "ansi_term" 11 | version = "0.11.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "atty" 19 | version = "0.2.10" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "bitflags" 29 | version = "1.0.3" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "clap" 34 | version = "2.32.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | dependencies = [ 37 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 44 | ] 45 | 46 | [[package]] 47 | name = "elm-parser" 48 | version = "0.1.0" 49 | dependencies = [ 50 | "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "nom 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 54 | ] 55 | 56 | [[package]] 57 | name = "lazy_static" 58 | version = "1.0.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | 61 | [[package]] 62 | name = "libc" 63 | version = "0.2.40" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | 66 | [[package]] 67 | name = "memchr" 68 | version = "2.0.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | dependencies = [ 71 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 72 | ] 73 | 74 | [[package]] 75 | name = "nom" 76 | version = "4.0.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | dependencies = [ 79 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 81 | ] 82 | 83 | [[package]] 84 | name = "redox_syscall" 85 | version = "0.1.40" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | 88 | [[package]] 89 | name = "redox_termios" 90 | version = "0.1.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "regex" 98 | version = "1.0.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 106 | ] 107 | 108 | [[package]] 109 | name = "regex-syntax" 110 | version = "0.6.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | dependencies = [ 113 | "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 114 | ] 115 | 116 | [[package]] 117 | name = "same-file" 118 | version = "1.0.2" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | dependencies = [ 121 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "strsim" 126 | version = "0.7.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | 129 | [[package]] 130 | name = "termion" 131 | version = "1.5.1" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | dependencies = [ 134 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 137 | ] 138 | 139 | [[package]] 140 | name = "textwrap" 141 | version = "0.10.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | dependencies = [ 144 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 145 | ] 146 | 147 | [[package]] 148 | name = "thread_local" 149 | version = "0.3.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 154 | ] 155 | 156 | [[package]] 157 | name = "ucd-util" 158 | version = "0.1.1" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | 161 | [[package]] 162 | name = "unicode-width" 163 | version = "0.1.5" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | 166 | [[package]] 167 | name = "unreachable" 168 | version = "1.0.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | dependencies = [ 171 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 172 | ] 173 | 174 | [[package]] 175 | name = "utf8-ranges" 176 | version = "1.0.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | 179 | [[package]] 180 | name = "vec_map" 181 | version = "0.8.1" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | 184 | [[package]] 185 | name = "void" 186 | version = "1.0.2" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | 189 | [[package]] 190 | name = "walkdir" 191 | version = "2.1.4" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | dependencies = [ 194 | "same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 195 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 196 | ] 197 | 198 | [[package]] 199 | name = "winapi" 200 | version = "0.3.5" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | dependencies = [ 203 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 205 | ] 206 | 207 | [[package]] 208 | name = "winapi-i686-pc-windows-gnu" 209 | version = "0.4.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "winapi-x86_64-pc-windows-gnu" 214 | version = "0.4.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | 217 | [metadata] 218 | "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" 219 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 220 | "checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1" 221 | "checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" 222 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" 223 | "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" 224 | "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" 225 | "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" 226 | "checksum nom 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "898696750eb5c3ce5eb5afbfbe46e7f7c4e1936e19d3e97be4b7937da7b6d114" 227 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 228 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 229 | "checksum regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75ecf88252dce580404a22444fc7d626c01815debba56a7f4f536772a5ff19d3" 230 | "checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b" 231 | "checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637" 232 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 233 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 234 | "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" 235 | "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" 236 | "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" 237 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 238 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 239 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" 240 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 241 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 242 | "checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369" 243 | "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" 244 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 245 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 246 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elm-parser" 3 | version = "0.1.0" 4 | authors = ["Michael Jones "] 5 | 6 | [[bin]] 7 | name = "elm-parser-dump" 8 | path = "src/dump.rs" 9 | 10 | [[bin]] 11 | name = "elm-parser-check" 12 | path = "src/check.rs" 13 | 14 | [dependencies] 15 | nom = { version = "4.0.0", features = ["regexp", "verbose-errors"] } 16 | regex = { version = "^1.0" } 17 | walkdir = "2" 18 | clap = "2.32.0" 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Elm Syntax Parser in Rust 3 | 4 | ## Status 5 | 6 | This was one of my first projects in Rust. I ran into troubles with nom macros and then with combine 7 | and then with lalrpop. Now I'm attempting to another approach following the guidance from [Crafting 8 | Interpreters](http://craftinginterpreters.com/) and you can see that over 9 | [here](https://github.com/michaeljones/erm). 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/Basic.elm: -------------------------------------------------------------------------------- 1 | module Basic exposing (..) 2 | 3 | import String 4 | 5 | 6 | a : Int 7 | a = 8 | 1 9 | -------------------------------------------------------------------------------- /examples/BrowserElement.elm: -------------------------------------------------------------------------------- 1 | module BrowserElement exposing (..) 2 | 3 | import Browser exposing (..) 4 | import Html exposing (..) 5 | import Html.Events exposing (onClick) 6 | 7 | type alias Model = 8 | { toggleCount: Int 9 | , isToggled: Bool 10 | } 11 | 12 | type Msg 13 | = Toggle 14 | | Reset 15 | 16 | type alias Flags = {} 17 | 18 | view : Model -> Html Msg 19 | view model = 20 | div [] 21 | [ button [ onClick Toggle ] [ text "Toggle" ] 22 | , button [ onClick Reset ] [ text "Reset" ] 23 | , text (if model.isToggled then "Yup" else "Nope") 24 | ] 25 | 26 | update : Msg -> Model -> (Model, Cmd msg) 27 | update msg model = 28 | case msg of 29 | Toggle -> 30 | ({ model | isToggled = not model.isToggled, toggleCount = model.toggleCount + 1}, Cmd.none) 31 | Reset -> 32 | ({ model | isToggled = False, toggleCount = 0 }, Cmd.none) 33 | 34 | init : Flags -> (Model, Cmd msg) 35 | init flags = 36 | ({ toggleCount = 0, isToggled = False }, Cmd.none) 37 | 38 | main = Browser.element 39 | { init = init 40 | , update = update 41 | , subscriptions = (\_ -> Sub.none) 42 | , view = view 43 | } 44 | -------------------------------------------------------------------------------- /examples/Lists.elm: -------------------------------------------------------------------------------- 1 | module Lists exposing (..) 2 | 3 | isListLengthFour : (List a) -> Bool 4 | isListLengthFour list = 5 | case List.length list of 6 | 4 -> True 7 | _ -> False 8 | -------------------------------------------------------------------------------- /examples/MutateRecord.elm: -------------------------------------------------------------------------------- 1 | module MutateRecord exposing (..) 2 | 3 | type alias Model = 4 | { name: String 5 | } 6 | 7 | renameToSteve : Model -> Model 8 | renameToSteve model = 9 | { model | name = "Steve" } 10 | -------------------------------------------------------------------------------- /src/ast/binop.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq, Clone)] 2 | pub enum Assoc { 3 | N, 4 | L, 5 | R, 6 | } 7 | -------------------------------------------------------------------------------- /src/ast/expression/access.rs: -------------------------------------------------------------------------------- 1 | use nom::types::CompleteStr; 2 | 3 | use ast::expression::core::Expression; 4 | use ast::expression::variable::variable; 5 | use ast::helpers::lo_name; 6 | 7 | named!(pub access, 8 | do_parse!( 9 | var: variable >> 10 | accessors: many1!( 11 | do_parse!( 12 | char!('.') >> 13 | name: lo_name >> 14 | (name) 15 | ) 16 | ) >> 17 | (Expression::Access(Box::new(var), accessors)) 18 | ) 19 | ); 20 | -------------------------------------------------------------------------------- /src/ast/expression/access_function.rs: -------------------------------------------------------------------------------- 1 | use nom::types::CompleteStr; 2 | 3 | use ast::expression::core::Expression; 4 | use ast::helpers::lo_name; 5 | 6 | named!(pub access_function, 7 | do_parse!( 8 | char!('.') >> 9 | name: lo_name >> 10 | (Expression::AccessFunction(name)) 11 | ) 12 | ); 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | 17 | use ast::expression::*; 18 | use nom::types::CompleteStr; 19 | 20 | #[test] 21 | fn simple_access_function() { 22 | assert_eq!( 23 | access_function(CompleteStr(".property")), 24 | Ok(( 25 | CompleteStr(""), 26 | Expression::AccessFunction("property".to_string()) 27 | )) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ast/expression/character.rs: -------------------------------------------------------------------------------- 1 | use ast::expression::core::Expression; 2 | 3 | use std; 4 | 5 | use nom::anychar; 6 | use nom::types::CompleteStr; 7 | 8 | named!(character_content, 9 | alt!( 10 | // Look for hex codes starting with \\x 11 | map_res!( 12 | do_parse!( 13 | tag!("\\x") >> 14 | code: take!(2) >> 15 | (u8::from_str_radix(code.0, 16)) 16 | ), 17 | |r: Result| { 18 | println!("{:?}", r); 19 | r.map(|u| u as char) 20 | } 21 | ) 22 | | anychar 23 | ) 24 | ); 25 | 26 | named!(pub character, 27 | map!( 28 | delimited!(char!('\''), 29 | character_content, 30 | char!('\'')), 31 | Expression::Character 32 | ) 33 | ); 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | 38 | use ast::expression::*; 39 | use nom::types::CompleteStr; 40 | 41 | #[test] 42 | fn character_literal() { 43 | assert_eq!( 44 | character(CompleteStr("'a'")), 45 | Ok((CompleteStr(""), Expression::Character('a'))) 46 | ); 47 | } 48 | 49 | #[test] 50 | fn newline_literal() { 51 | assert_eq!( 52 | character(CompleteStr("'\n'")), 53 | Ok((CompleteStr(""), Expression::Character('\n'))) 54 | ); 55 | } 56 | 57 | #[test] 58 | fn charcode_literal() { 59 | println!("{:?}", u8::from_str_radix("23", 16)); 60 | assert_eq!( 61 | character(CompleteStr("'\\x23'")), 62 | Ok((CompleteStr(""), Expression::Character('#'))) 63 | ); 64 | } 65 | 66 | #[test] 67 | fn character_literals_must_contain_one_character() { 68 | assert!(character(CompleteStr("''")).is_err()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/ast/expression/core.rs: -------------------------------------------------------------------------------- 1 | use ast::helpers::Name; 2 | use ast::type_::core::Type; 3 | 4 | #[derive(Debug, PartialEq, Clone)] 5 | pub struct FunctionSignature { 6 | pub name: Name, 7 | pub type_: Type, 8 | } 9 | 10 | #[derive(Debug, PartialEq, Clone)] 11 | pub struct FunctionDefinition { 12 | pub name: Name, 13 | pub args: Vec, 14 | pub body: Expression, 15 | } 16 | 17 | #[derive(Debug, PartialEq, Clone)] 18 | pub struct Function { 19 | pub signature: Option, 20 | pub definition: FunctionDefinition, 21 | } 22 | 23 | #[derive(Debug, PartialEq, Clone)] 24 | pub enum LetEntry { 25 | LetFunction(Function), 26 | LetBinding(Expression, Expression), 27 | } 28 | 29 | #[derive(Debug, PartialEq, Clone)] 30 | pub enum Expression { 31 | Character(char), 32 | String(String), 33 | Integer(String), 34 | Float(String), 35 | Variable(Vec), 36 | List(Vec), 37 | Tuple(Vec), 38 | Access(Box, Vec), 39 | AccessFunction(Name), 40 | Record(Vec<(Name, Expression)>), 41 | RecordUpdate(Name, Vec<(Name, Expression)>), 42 | If(Box, Box, Box), 43 | Let(Vec, Box), 44 | Case(Box, Vec<(Expression, Expression)>), 45 | Lambda(Vec, Box), 46 | Application(Box, Box), 47 | BinOp(Box, Box, Box), 48 | } 49 | -------------------------------------------------------------------------------- /src/ast/expression/float.rs: -------------------------------------------------------------------------------- 1 | use ast::expression::core::Expression; 2 | 3 | use nom::digit; 4 | use nom::types::CompleteStr; 5 | 6 | named!(pub float, 7 | do_parse!( 8 | sign: map!( 9 | opt!(alt!(char!('+') | char!('-'))), 10 | |opt| opt.map(|c| c.to_string()).unwrap_or_else(String::new) 11 | ) >> 12 | start: digit >> 13 | char!('.') >> 14 | end: digit >> 15 | (Expression::Float(sign.to_string() + start.0 + "." + end.0)) 16 | ) 17 | ); 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | 22 | use ast::expression::*; 23 | use nom::types::CompleteStr; 24 | 25 | #[test] 26 | fn float_literal() { 27 | assert_eq!( 28 | float(CompleteStr("1.01")), 29 | Ok((CompleteStr(""), Expression::Float("1.01".to_string()))) 30 | ); 31 | } 32 | 33 | #[test] 34 | fn postive_float_literal() { 35 | assert_eq!( 36 | float(CompleteStr("+1.5")), 37 | Ok((CompleteStr(""), Expression::Float("+1.5".to_string()))) 38 | ); 39 | } 40 | 41 | #[test] 42 | fn negative_float_literal() { 43 | assert_eq!( 44 | float(CompleteStr("-1.8")), 45 | Ok((CompleteStr(""), Expression::Float("-1.8".to_string()))) 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ast/expression/function.rs: -------------------------------------------------------------------------------- 1 | use ast::expression::core::{Function, FunctionDefinition, FunctionSignature}; 2 | use ast::expression::{expression, term}; 3 | use ast::helpers::{ 4 | lo_name, new_line_and_same_indent, operator, spaces, spaces_or_new_lines_and_indent, IR, 5 | }; 6 | use ast::type_::type_annotation; 7 | 8 | use nom::types::CompleteStr; 9 | 10 | named_args!(function_signature(indentation: u32), 11 | do_parse!( 12 | name: alt!( 13 | lo_name 14 | | delimited!(char!('('), operator, char!(')')) 15 | ) >> 16 | spaces >> 17 | char!(':') >> 18 | new_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GT) >> 19 | type_: call!(type_annotation, new_indent) >> 20 | (FunctionSignature { name, type_ }) 21 | ) 22 | ); 23 | 24 | named_args!(function_definition(indentation: u32), 25 | do_parse!( 26 | name: alt!( 27 | lo_name 28 | | delimited!(char!('('), operator, char!(')')) 29 | ) >> 30 | args: many0!(preceded!(spaces, call!(term, indentation))) >> 31 | spaces >> 32 | char!('=') >> 33 | new_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GT) >> 34 | body: call!(expression, new_indent) >> 35 | (FunctionDefinition {name, args, body}) 36 | ) 37 | ); 38 | 39 | named_args!(pub function(indentation: u32), 40 | do_parse!( 41 | signature: opt!( 42 | terminated!( 43 | call!(function_signature, indentation), 44 | call!(new_line_and_same_indent, indentation) 45 | ) 46 | ) >> 47 | definition: call!(function_definition, indentation) >> 48 | (Function { signature, definition }) 49 | ) 50 | ); 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | 55 | use ast::expression::function::*; 56 | use ast::expression::Expression; 57 | use ast::type_::core::Type; 58 | 59 | use nom::types::CompleteStr; 60 | 61 | fn application(a: Expression, b: Expression) -> Expression { 62 | Expression::Application(Box::new(a), Box::new(b)) 63 | } 64 | 65 | fn var(name: &str) -> Expression { 66 | Expression::Variable(vec![name.to_string()]) 67 | } 68 | 69 | fn int(text: &str) -> Expression { 70 | Expression::Integer(text.to_string()) 71 | } 72 | 73 | fn tapp(a: Type, b: Type) -> Type { 74 | Type::TypeApplication(Box::new(a), Box::new(b)) 75 | } 76 | 77 | fn tcon(name: &str, v: Vec) -> Type { 78 | Type::TypeConstructor(vec![name.to_string()], v) 79 | } 80 | 81 | #[test] 82 | fn simple_function_type() { 83 | assert_eq!( 84 | function_signature(CompleteStr("f : Int -> Int"), 0), 85 | Ok(( 86 | CompleteStr(""), 87 | FunctionSignature { 88 | name: "f".to_string(), 89 | type_: tapp(tcon("Int", vec![]), tcon("Int", vec![])), 90 | } 91 | )) 92 | ); 93 | } 94 | 95 | #[test] 96 | fn simple_function_type_with_new_line() { 97 | assert_eq!( 98 | function_signature(CompleteStr("f : Int ->\n Int"), 0), 99 | Ok(( 100 | CompleteStr(""), 101 | FunctionSignature { 102 | name: "f".to_string(), 103 | type_: tapp(tcon("Int", vec![]), tcon("Int", vec![])), 104 | } 105 | )) 106 | ); 107 | } 108 | 109 | #[test] 110 | fn simple_function() { 111 | assert_eq!( 112 | function_definition(CompleteStr("f x = a { r | f = 1 } c"), 0), 113 | Ok(( 114 | CompleteStr(""), 115 | FunctionDefinition { 116 | name: "f".to_string(), 117 | args: vec![var("x")], 118 | body: application( 119 | application( 120 | var("a"), 121 | Expression::RecordUpdate( 122 | "r".to_string(), 123 | vec![("f".to_string(), Expression::Integer("1".to_string()))], 124 | ), 125 | ), 126 | var("c"), 127 | ), 128 | } 129 | )) 130 | ); 131 | } 132 | 133 | #[test] 134 | fn simple_function_with_new_line() { 135 | assert_eq!( 136 | function_definition(CompleteStr("f x = a \n c"), 0), 137 | Ok(( 138 | CompleteStr(""), 139 | FunctionDefinition { 140 | name: "f".to_string(), 141 | args: vec![var("x")], 142 | body: application(var("a"), var("c")), 143 | } 144 | )) 145 | ); 146 | } 147 | 148 | #[test] 149 | fn function_type_with_tuple() { 150 | assert_eq!( 151 | function_signature(CompleteStr("h : (Int, Int) -> Int"), 0), 152 | Ok(( 153 | CompleteStr(""), 154 | FunctionSignature { 155 | name: "h".to_string(), 156 | type_: tapp( 157 | Type::TypeTuple(vec![tcon("Int", vec![]), tcon("Int", vec![])]), 158 | tcon("Int", vec![]) 159 | ), 160 | } 161 | )) 162 | ); 163 | } 164 | 165 | #[test] 166 | fn function_type_with_operator() { 167 | assert_eq!( 168 | function_signature(CompleteStr("(+) : Int -> Int"), 0), 169 | Ok(( 170 | CompleteStr(""), 171 | FunctionSignature { 172 | name: "+".to_string(), 173 | type_: tapp(tcon("Int", vec![]), tcon("Int", vec![])), 174 | } 175 | )) 176 | ); 177 | } 178 | 179 | #[test] 180 | fn function_with_operator() { 181 | assert_eq!( 182 | function_definition(CompleteStr("(+) a b = 1"), 0), 183 | Ok(( 184 | CompleteStr(""), 185 | FunctionDefinition { 186 | name: "+".to_string(), 187 | args: vec![var("a"), var("b")], 188 | body: int("1"), 189 | } 190 | )) 191 | ); 192 | } 193 | 194 | #[test] 195 | fn multi_line_function_type() { 196 | assert_eq!( 197 | function_signature( 198 | CompleteStr( 199 | "cssm : 200 | CssModules.Helpers 201 | { a : String 202 | , b : String 203 | } 204 | msg" 205 | ), 206 | 0 207 | ), 208 | Ok(( 209 | CompleteStr(""), 210 | FunctionSignature { 211 | name: "cssm".to_string(), 212 | type_: Type::TypeConstructor( 213 | vec!["CssModules".to_string(), "Helpers".to_string()], 214 | vec![ 215 | Type::TypeRecord(vec![ 216 | ( 217 | "a".to_string(), 218 | Type::TypeConstructor(vec!["String".to_string()], vec![]), 219 | ), 220 | ( 221 | "b".to_string(), 222 | Type::TypeConstructor(vec!["String".to_string()], vec![]), 223 | ), 224 | ]), 225 | Type::TypeVariable("msg".to_string()), 226 | ] 227 | ), 228 | } 229 | )) 230 | ); 231 | } 232 | 233 | #[test] 234 | fn multi_line_function_type_with_parens() { 235 | assert_eq!( 236 | function_signature( 237 | CompleteStr( 238 | "classes : 239 | List 240 | ({ viewReportPage : String } -> String 241 | )" 242 | ), 243 | 0 244 | ), 245 | Ok(( 246 | CompleteStr(""), 247 | FunctionSignature { 248 | name: "classes".to_string(), 249 | type_: tcon( 250 | "List", 251 | vec![tapp( 252 | Type::TypeRecord(vec![( 253 | "viewReportPage".to_string(), 254 | tcon("String", vec![]), 255 | )]), 256 | tcon("String", vec![]), 257 | )] 258 | ), 259 | } 260 | )) 261 | ); 262 | } 263 | 264 | #[test] 265 | fn function_with_signature() { 266 | assert_eq!( 267 | function(CompleteStr("f : Int -> Int\nf x = a \n c"), 0), 268 | Ok(( 269 | CompleteStr(""), 270 | Function { 271 | signature: Some(FunctionSignature { 272 | name: "f".to_string(), 273 | type_: tapp(tcon("Int", vec![]), tcon("Int", vec![])), 274 | }), 275 | definition: FunctionDefinition { 276 | name: "f".to_string(), 277 | args: vec![var("x")], 278 | body: application(var("a"), var("c")), 279 | }, 280 | } 281 | )) 282 | ); 283 | } 284 | 285 | #[test] 286 | fn function_without_signature() { 287 | assert_eq!( 288 | function(CompleteStr("f x = a \n c"), 0), 289 | Ok(( 290 | CompleteStr(""), 291 | Function { 292 | signature: None, 293 | definition: FunctionDefinition { 294 | name: "f".to_string(), 295 | args: vec![var("x")], 296 | body: application(var("a"), var("c")), 297 | }, 298 | } 299 | )) 300 | ); 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/ast/expression/integer.rs: -------------------------------------------------------------------------------- 1 | use ast::expression::core::Expression; 2 | 3 | use nom::digit; 4 | use nom::types::CompleteStr; 5 | 6 | named!(pub integer, 7 | do_parse!( 8 | sign: map!( 9 | opt!(alt!(char!('+') | char!('-'))), 10 | |opt| opt.map(|c| c.to_string()).unwrap_or_else(String::new) 11 | ) >> 12 | number: digit >> 13 | (Expression::Integer(sign.to_string() + number.0)) 14 | ) 15 | ); 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | 20 | use ast::expression::*; 21 | use nom::types::CompleteStr; 22 | 23 | #[test] 24 | fn int_literal() { 25 | assert_eq!( 26 | integer(CompleteStr("101")), 27 | Ok((CompleteStr(""), Expression::Integer("101".to_string()))) 28 | ); 29 | } 30 | 31 | #[test] 32 | fn postive_int_literal() { 33 | assert_eq!( 34 | integer(CompleteStr("+15")), 35 | Ok((CompleteStr(""), Expression::Integer("+15".to_string()))) 36 | ); 37 | } 38 | 39 | #[test] 40 | fn negative_int_literal() { 41 | assert_eq!( 42 | integer(CompleteStr("-18")), 43 | Ok((CompleteStr(""), Expression::Integer("-18".to_string()))) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ast/expression/mod.rs: -------------------------------------------------------------------------------- 1 | mod access; 2 | mod access_function; 3 | mod character; 4 | pub mod core; 5 | mod float; 6 | pub mod function; 7 | mod integer; 8 | mod string; 9 | mod variable; 10 | 11 | pub use ast::expression::core::Expression; 12 | use ast::expression::core::{Function, FunctionDefinition, FunctionSignature, LetEntry}; 13 | 14 | use ast::expression::access::access; 15 | use ast::expression::access_function::access_function; 16 | use ast::expression::character::character; 17 | use ast::expression::float::float; 18 | use ast::expression::function::function; 19 | use ast::expression::integer::integer; 20 | use ast::expression::string::string; 21 | use ast::expression::variable::variable; 22 | use ast::helpers::{ 23 | lo_name, operator, opt_spaces_or_new_lines_and_indent, spaces_or_new_lines_and_indent, IR, 24 | }; 25 | 26 | // use nom; 27 | use nom::types::CompleteStr; 28 | use nom::{multispace, multispace0, space1}; 29 | 30 | named_args!(tuple(indentation: u32) , 31 | map!( 32 | delimited!( 33 | char!('('), 34 | separated_list!( 35 | char!(','), 36 | delimited!( 37 | multispace0, 38 | call!(expression, indentation), 39 | multispace0 40 | ) 41 | ), 42 | char!(')') 43 | ), 44 | Expression::Tuple 45 | ) 46 | ); 47 | 48 | named_args!(list(indentation: u32) , 49 | map!( 50 | delimited!( 51 | preceded!(char!('['), opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE))), 52 | // Not sure why this optional is required 53 | opt!( 54 | separated_list!( 55 | delimited!( 56 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), 57 | char!(','), 58 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)) 59 | ), 60 | call!(expression, indentation) 61 | ) 62 | ), 63 | terminated!(opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), char!(']')) 64 | ), 65 | |o| Expression::List(o.unwrap_or_else(|| vec![])) 66 | ) 67 | ); 68 | 69 | named_args!(record(indentation: u32) , 70 | map!( 71 | delimited!( 72 | preceded!(char!('{'), opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE))), 73 | separated_list!( 74 | delimited!( 75 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), 76 | char!(','), 77 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)) 78 | ), 79 | separated_pair!( 80 | lo_name, 81 | delimited!( 82 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), 83 | char!('='), 84 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)) 85 | ), 86 | call!(expression, indentation) 87 | ) 88 | ), 89 | terminated!(opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), char!('}')) 90 | ), 91 | Expression::Record 92 | ) 93 | ); 94 | 95 | named_args!(simplified_record(indentation: u32) , 96 | map!( 97 | delimited!( 98 | char!('{'), 99 | separated_list!( 100 | char!(','), 101 | do_parse!( 102 | multispace0 >> 103 | name: lo_name >> 104 | multispace0 >> 105 | ((name.clone(), Expression::Variable(vec![name.to_string()]))) 106 | ) 107 | ), 108 | char!('}') 109 | ), 110 | Expression::Record 111 | ) 112 | ); 113 | 114 | named_args!(record_update(indentation: u32) , 115 | delimited!( 116 | preceded!(char!('{'), call!(opt_spaces_or_new_lines_and_indent, indentation, IR::GTE)), 117 | do_parse!( 118 | name: lo_name >> 119 | call!(opt_spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 120 | char!('|') >> 121 | multispace0 >> 122 | pairs: separated_list!( 123 | delimited!( 124 | call!(opt_spaces_or_new_lines_and_indent, indentation, IR::GTE), 125 | char!(','), 126 | call!(opt_spaces_or_new_lines_and_indent, indentation, IR::GTE) 127 | ), 128 | do_parse!( 129 | name: lo_name >> 130 | multispace0 >> 131 | char!('=') >> 132 | multispace0 >> 133 | expression: call!(expression, indentation) >> 134 | ((name, expression)) 135 | ) 136 | ) >> 137 | (Expression::RecordUpdate(name.to_string(), pairs)) 138 | ), 139 | terminated!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE), char!('}')) 140 | ) 141 | ); 142 | 143 | named_args!(parens(indentation: u32) , 144 | delimited!( 145 | char!('('), 146 | call!(expression, indentation), 147 | char!(')') 148 | ) 149 | ); 150 | 151 | // Term 152 | 153 | named_args!(pub term(indentation: u32) , 154 | alt!( 155 | access 156 | | variable 157 | | access_function 158 | | string 159 | | float 160 | | integer 161 | | character 162 | | call!(parens, indentation) 163 | | call!(list, indentation) 164 | | call!(tuple, indentation) 165 | | call!(record_update, indentation) 166 | | call!(record, indentation) 167 | | call!(simplified_record, indentation) 168 | ) 169 | ); 170 | 171 | // Lambda 172 | 173 | named_args!(lambda(indentation: u32) , 174 | do_parse!( 175 | char!('\\') >> 176 | args: separated_nonempty_list!(space1, call!(term, indentation)) >> 177 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 178 | tag!("->") >> 179 | new_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 180 | body: call!(expression, new_indent) >> 181 | (Expression::Lambda(args, Box::new(body))) 182 | ) 183 | ); 184 | 185 | // Application 186 | 187 | named_args!(application_or_var(indentation: u32) , 188 | map_res!( 189 | separated_list!( 190 | call!(spaces_or_new_lines_and_indent, indentation, IR::GT), 191 | call!(term, indentation) 192 | ), 193 | |v: Vec| { 194 | if v.is_empty() { 195 | Err("Empty list".to_string()) 196 | } 197 | else if v.len() == 1 { 198 | Ok(v[0].clone()) 199 | } else { 200 | let mut app = Expression::Application(Box::new(v[0].clone()), Box::new(v[1].clone())); 201 | for entry in v.iter().skip(2) { 202 | app = Expression::Application(Box::new(app), Box::new(entry.clone())) 203 | } 204 | Ok(app) 205 | } 206 | } 207 | ) 208 | ); 209 | 210 | // Let Expression 211 | 212 | named_args!(let_binding_target(indentation: u32) , 213 | alt!( 214 | variable 215 | | call!(list, indentation) 216 | | call!(tuple, indentation) 217 | | call!(simplified_record, indentation) 218 | ) 219 | ); 220 | 221 | named_args!(let_binding(indentation: u32) , 222 | do_parse!( 223 | binding: call!(let_binding_target, indentation) >> 224 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 225 | char!('=') >> 226 | new_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 227 | expression: call!(expression, new_indent) >> 228 | (LetEntry::LetBinding(binding, expression)) 229 | ) 230 | ); 231 | 232 | named_args!(let_bindings(indentation: u32) >, 233 | separated_nonempty_list!( 234 | call!(spaces_or_new_lines_and_indent, indentation, IR::EQ), 235 | alt!( 236 | call!(let_binding, indentation) 237 | | map!(call!(function, indentation), LetEntry::LetFunction) 238 | ) 239 | ) 240 | ); 241 | 242 | named_args!(let_expression(indentation: u32) , 243 | do_parse!( 244 | tag!("let") >> 245 | assignment_indentation: call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 246 | assignments: call!(let_bindings, assignment_indentation) >> 247 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 248 | tag!("in") >> 249 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 250 | expression: call!(expression, indentation) >> 251 | (Expression::Let(assignments, Box::new(expression))) 252 | ) 253 | ); 254 | 255 | // Case Expression 256 | 257 | named_args!(case(indentation: u32) , 258 | do_parse!( 259 | matcher: call!(expression, indentation) >> 260 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 261 | tag!("->") >> 262 | new_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 263 | expression: call!(expression, new_indent) >> 264 | (matcher, expression) 265 | ) 266 | ); 267 | 268 | named_args!(cases(indentation: u32) >, 269 | separated_nonempty_list!( 270 | call!(spaces_or_new_lines_and_indent, indentation, IR::EQ), 271 | call!(case, indentation) 272 | ) 273 | ); 274 | 275 | named_args!(case_expression(indentation: u32) , 276 | do_parse!( 277 | tag!("case") >> 278 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 279 | expression: call!(expression, indentation) >> 280 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 281 | tag!("of") >> 282 | new_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 283 | cases: call!(cases, new_indent) >> 284 | (Expression::Case(Box::new(expression), cases)) 285 | ) 286 | ); 287 | 288 | // If Expression 289 | 290 | named_args!(if_expression(indentation: u32) , 291 | do_parse!( 292 | tag!("if") >> 293 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 294 | test: call!(expression, indentation) >> 295 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 296 | tag!("then") >> 297 | new_if_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 298 | if_exp: call!(expression, new_if_indent) >> 299 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 300 | tag!("else") >> 301 | new_else_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 302 | else_exp: call!(expression, new_else_indent) >> 303 | (Expression::If(Box::new(test), Box::new(if_exp), Box::new(else_exp))) 304 | ) 305 | ); 306 | 307 | // Binary 308 | 309 | named!(operator_or_as, 310 | map!( 311 | alt!( 312 | map!(tag!("as"), |text| text.to_string()) 313 | | operator 314 | ), 315 | |op| Expression::Variable(vec![op]) 316 | ) 317 | ); 318 | 319 | named_args!(binary(indentation: u32) , 320 | // Application followed by possibly a operator + expression 321 | do_parse!( 322 | application_or_var: call!(application_or_var, indentation) >> 323 | operator_exp: opt!( 324 | do_parse!( 325 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 326 | operator: operator_or_as >> 327 | multispace >> 328 | expression: call!(expression, indentation) >> 329 | (operator, expression) 330 | ) 331 | ) >> 332 | (match operator_exp { 333 | Some((op, exp)) => Expression::BinOp( 334 | Box::new(op), 335 | Box::new(application_or_var), 336 | Box::new(exp) 337 | ), 338 | None => application_or_var 339 | }) 340 | ) 341 | ); 342 | 343 | named_args!(pub expression(indentation: u32) , 344 | alt!( 345 | call!(binary, indentation) 346 | | call!(let_expression, indentation) 347 | | call!(case_expression, indentation) 348 | | call!(if_expression, indentation) 349 | | call!(lambda, indentation) 350 | ) 351 | ); 352 | 353 | #[cfg(test)] 354 | mod tests { 355 | 356 | use ast::expression::*; 357 | use ast::type_::core::Type; 358 | 359 | use nom::types::CompleteStr; 360 | 361 | fn var(name: &str) -> Expression { 362 | Expression::Variable(vec![name.to_string()]) 363 | } 364 | 365 | fn int(text: &str) -> Expression { 366 | Expression::Integer(text.to_string()) 367 | } 368 | 369 | fn application(a: Expression, b: Expression) -> Expression { 370 | Expression::Application(Box::new(a), Box::new(b)) 371 | } 372 | 373 | fn bin_op(op: Expression, left: Expression, right: Expression) -> Expression { 374 | Expression::BinOp(Box::new(op), Box::new(left), Box::new(right)) 375 | } 376 | 377 | fn if_(test: Expression, if_exp: Expression, else_exp: Expression) -> Expression { 378 | Expression::If(Box::new(test), Box::new(if_exp), Box::new(else_exp)) 379 | } 380 | 381 | fn tapp(a: Type, b: Type) -> Type { 382 | Type::TypeApplication(Box::new(a), Box::new(b)) 383 | } 384 | 385 | fn tcon(name: &str, v: Vec) -> Type { 386 | Type::TypeConstructor(vec![name.to_string()], v) 387 | } 388 | 389 | // Tuples 390 | 391 | #[test] 392 | fn simple_tuple() { 393 | assert_eq!( 394 | tuple(CompleteStr("(a,b)"), 0), 395 | Ok((CompleteStr(""), Expression::Tuple(vec![var("a"), var("b")]))) 396 | ); 397 | } 398 | 399 | #[test] 400 | fn simple_tuple_with_formatting() { 401 | assert_eq!( 402 | tuple(CompleteStr("( a, b )"), 0), 403 | Ok((CompleteStr(""), Expression::Tuple(vec![var("a"), var("b")]))) 404 | ); 405 | } 406 | 407 | // Lists 408 | 409 | #[test] 410 | fn empty_list() { 411 | assert_eq!( 412 | list(CompleteStr("[]"), 0), 413 | Ok((CompleteStr(""), Expression::List(vec![]))) 414 | ); 415 | } 416 | 417 | #[test] 418 | fn simple_list() { 419 | assert_eq!( 420 | list(CompleteStr("[a,b]"), 0), 421 | Ok((CompleteStr(""), Expression::List(vec![var("a"), var("b")]))) 422 | ); 423 | } 424 | 425 | #[test] 426 | fn simple_list_with_formatting() { 427 | assert_eq!( 428 | list(CompleteStr("[ a, b ]"), 0), 429 | Ok((CompleteStr(""), Expression::List(vec![var("a"), var("b")]))) 430 | ); 431 | } 432 | 433 | #[test] 434 | fn simple_int_list() { 435 | assert_eq!( 436 | list(CompleteStr("[1,2]"), 0), 437 | Ok((CompleteStr(""), Expression::List(vec![int("1"), int("2")]))) 438 | ); 439 | } 440 | 441 | #[test] 442 | fn multi_line_list() { 443 | assert_eq!( 444 | list( 445 | CompleteStr( 446 | "[ 1 447 | , 2 448 | ]" 449 | ), 450 | 0 451 | ), 452 | Ok((CompleteStr(""), Expression::List(vec![int("1"), int("2")]))) 453 | ); 454 | } 455 | 456 | #[test] 457 | fn tuple_list() { 458 | assert_eq!( 459 | list(CompleteStr("[(a, b), (a, b)]"), 0), 460 | Ok(( 461 | CompleteStr(""), 462 | Expression::List(vec![ 463 | Expression::Tuple(vec![var("a"), var("b")]), 464 | Expression::Tuple(vec![var("a"), var("b")]), 465 | ]) 466 | )) 467 | ); 468 | } 469 | 470 | // Application or Var 471 | 472 | #[test] 473 | fn simple_variable() { 474 | assert_eq!( 475 | application_or_var(CompleteStr("abc"), 0), 476 | Ok((CompleteStr(""), var("abc"))) 477 | ); 478 | } 479 | 480 | #[test] 481 | fn simple_application() { 482 | assert_eq!( 483 | application_or_var(CompleteStr("f a"), 0), 484 | Ok(( 485 | CompleteStr(""), 486 | Expression::Application(Box::new(var("f")), Box::new(var("a"))) 487 | )) 488 | ); 489 | } 490 | 491 | #[test] 492 | fn curried_application() { 493 | assert_eq!( 494 | application_or_var(CompleteStr("f a b"), 0), 495 | Ok(( 496 | CompleteStr(""), 497 | Expression::Application( 498 | Box::new(Expression::Application( 499 | Box::new(var("f")), 500 | Box::new(var("a")) 501 | )), 502 | Box::new(var("b")) 503 | ) 504 | )) 505 | ); 506 | } 507 | 508 | #[test] 509 | fn curried_application_with_parens() { 510 | assert_eq!( 511 | application_or_var(CompleteStr("(f a) b"), 0), 512 | Ok(( 513 | CompleteStr(""), 514 | Expression::Application( 515 | Box::new(Expression::Application( 516 | Box::new(var("f")), 517 | Box::new(var("a")) 518 | )), 519 | Box::new(var("b")) 520 | ) 521 | )) 522 | ); 523 | } 524 | 525 | #[test] 526 | fn multiline_application() { 527 | assert_eq!( 528 | application_or_var(CompleteStr("f\n a\n b"), 0), 529 | Ok(( 530 | CompleteStr(""), 531 | Expression::Application( 532 | Box::new(Expression::Application( 533 | Box::new(var("f")), 534 | Box::new(var("a")) 535 | )), 536 | Box::new(var("b")) 537 | ) 538 | )) 539 | ); 540 | } 541 | 542 | #[test] 543 | fn multiline_bug() { 544 | assert_eq!( 545 | application_or_var(CompleteStr("f\n (==)"), 0), 546 | Ok(( 547 | CompleteStr(""), 548 | Expression::Application(Box::new(var("f")), Box::new(var("=="))), 549 | )) 550 | ); 551 | } 552 | 553 | #[test] 554 | fn same_multiline_bug() { 555 | assert_eq!( 556 | application_or_var(CompleteStr("f\n \"I like the symbol =\""), 0), 557 | Ok(( 558 | CompleteStr(""), 559 | Expression::Application( 560 | Box::new(var("f")), 561 | Box::new(Expression::String("I like the symbol =".to_string())) 562 | ), 563 | )) 564 | ); 565 | } 566 | 567 | #[test] 568 | fn constructor_application() { 569 | assert_eq!( 570 | application_or_var(CompleteStr("Cons a Nil"), 0), 571 | Ok(( 572 | CompleteStr(""), 573 | Expression::Application( 574 | Box::new(Expression::Application( 575 | Box::new(var("Cons")), 576 | Box::new(var("a")) 577 | )), 578 | Box::new(var("Nil")) 579 | ) 580 | )) 581 | ); 582 | } 583 | 584 | #[test] 585 | fn application_with_record_update() { 586 | assert_eq!( 587 | application_or_var(CompleteStr("a { r | f = 1 } c"), 0), 588 | Ok(( 589 | CompleteStr(""), 590 | application( 591 | application( 592 | var("a"), 593 | Expression::RecordUpdate( 594 | "r".to_string(), 595 | vec![("f".to_string(), int("1"))] 596 | ) 597 | ), 598 | var("c") 599 | ) 600 | )) 601 | ); 602 | } 603 | 604 | // Record 605 | 606 | #[test] 607 | fn simple_record() { 608 | assert_eq!( 609 | record(CompleteStr("{ a = b }"), 0), 610 | Ok(( 611 | CompleteStr(""), 612 | Expression::Record(vec![("a".to_string(), var("b"))]) 613 | )) 614 | ); 615 | } 616 | 617 | #[test] 618 | fn simple_record_with_many_fields() { 619 | assert_eq!( 620 | record(CompleteStr("{ a = b, b = 2 }"), 0), 621 | Ok(( 622 | CompleteStr(""), 623 | Expression::Record(vec![ 624 | ("a".to_string(), var("b")), 625 | ("b".to_string(), int("2")), 626 | ]) 627 | )) 628 | ); 629 | } 630 | 631 | #[test] 632 | fn multi_line_record() { 633 | assert_eq!( 634 | record( 635 | CompleteStr( 636 | "{ a = b 637 | , b = 2 638 | }" 639 | ), 640 | 1 641 | ), 642 | Ok(( 643 | CompleteStr(""), 644 | Expression::Record(vec![ 645 | ("a".to_string(), var("b")), 646 | ("b".to_string(), int("2")), 647 | ]) 648 | )) 649 | ); 650 | } 651 | 652 | #[test] 653 | fn multi_line_record_with_comments() { 654 | assert_eq!( 655 | record( 656 | CompleteStr( 657 | "{ a = b 658 | -- First comment 659 | , b = 2 -- Second comment 660 | }" 661 | ), 662 | 1 663 | ), 664 | Ok(( 665 | CompleteStr(""), 666 | Expression::Record(vec![ 667 | ("a".to_string(), var("b")), 668 | ("b".to_string(), int("2")), 669 | ]) 670 | )) 671 | ); 672 | } 673 | 674 | #[test] 675 | fn simple_record_with_many_tuple_fields() { 676 | assert_eq!( 677 | record(CompleteStr("{ a = (a, b), b = (a, b) }"), 0), 678 | Ok(( 679 | CompleteStr(""), 680 | Expression::Record(vec![ 681 | ("a".to_string(), Expression::Tuple(vec![var("a"), var("b")])), 682 | ("b".to_string(), Expression::Tuple(vec![var("a"), var("b")])), 683 | ]) 684 | )) 685 | ); 686 | } 687 | 688 | #[test] 689 | fn simple_record_with_updated_field() { 690 | assert_eq!( 691 | record_update(CompleteStr("{ a | b = 2, c = 3 }"), 0), 692 | Ok(( 693 | CompleteStr(""), 694 | Expression::RecordUpdate( 695 | "a".to_string(), 696 | vec![("b".to_string(), int("2")), ("c".to_string(), int("3"))] 697 | ) 698 | )) 699 | ); 700 | } 701 | 702 | #[test] 703 | fn simple_record_with_advanced_field() { 704 | assert_eq!( 705 | record(CompleteStr("{ a = Just 2 }"), 0), 706 | Ok(( 707 | CompleteStr(""), 708 | Expression::Record(vec![("a".to_string(), application(var("Just"), int("2")))]) 709 | )) 710 | ); 711 | } 712 | 713 | #[test] 714 | fn simple_record_update_with_advanced_field() { 715 | assert_eq!( 716 | record_update(CompleteStr("{ a | a = Just 2 }"), 0), 717 | Ok(( 718 | CompleteStr(""), 719 | Expression::RecordUpdate( 720 | "a".to_string(), 721 | vec![("a".to_string(), application(var("Just"), int("2")))], 722 | ) 723 | )) 724 | ); 725 | } 726 | 727 | #[test] 728 | fn record_update_with_comment() { 729 | assert_eq!( 730 | record_update( 731 | CompleteStr( 732 | "{ a 733 | -- Comment about something 734 | | b = 1 735 | }" 736 | ), 737 | 0 738 | ), 739 | Ok(( 740 | CompleteStr(""), 741 | Expression::RecordUpdate("a".to_string(), vec![("b".to_string(), int("1"))],) 742 | )) 743 | ); 744 | } 745 | 746 | #[test] 747 | fn simple_record_destructuring_pattern() { 748 | assert_eq!( 749 | simplified_record(CompleteStr("{ a, b }"), 0), 750 | Ok(( 751 | CompleteStr(""), 752 | Expression::Record(vec![ 753 | ("a".to_string(), var("a")), 754 | ("b".to_string(), var("b")), 755 | ],) 756 | )) 757 | ); 758 | } 759 | 760 | // Binary Ops 761 | 762 | #[test] 763 | fn simple_binary_op() { 764 | assert_eq!( 765 | binary(CompleteStr("x + 1"), 0), 766 | Ok(( 767 | CompleteStr(""), 768 | Expression::BinOp(Box::new(var("+")), Box::new(var("x")), Box::new(int("1")),) 769 | )) 770 | ); 771 | } 772 | 773 | // Let Expressions 774 | 775 | #[test] 776 | fn let_single_binding() { 777 | assert_eq!( 778 | let_binding(CompleteStr("a = 42"), 0), 779 | Ok((CompleteStr(""), LetEntry::LetBinding(var("a"), int("42")))) 780 | ); 781 | } 782 | 783 | #[test] 784 | fn let_group_single_binding() { 785 | assert_eq!( 786 | let_bindings(CompleteStr("a = 42"), 0), 787 | Ok(( 788 | CompleteStr(""), 789 | vec![LetEntry::LetBinding(var("a"), int("42"))] 790 | )) 791 | ); 792 | } 793 | 794 | #[test] 795 | fn let_group_single_function() { 796 | assert_eq!( 797 | let_bindings(CompleteStr("a b = 42"), 0), 798 | Ok(( 799 | CompleteStr(""), 800 | vec![LetEntry::LetFunction(Function { 801 | signature: None, 802 | definition: FunctionDefinition { 803 | name: "a".to_string(), 804 | args: vec![var("b")], 805 | body: int("42"), 806 | }, 807 | })] 808 | )) 809 | ); 810 | } 811 | 812 | #[test] 813 | fn let_group_single_function_and_signature() { 814 | assert_eq!( 815 | let_bindings(CompleteStr("a : Int -> Int\na b = 42"), 0), 816 | Ok(( 817 | CompleteStr(""), 818 | vec![LetEntry::LetFunction(Function { 819 | signature: Some(FunctionSignature { 820 | name: "a".to_string(), 821 | type_: tapp(tcon("Int", vec![]), tcon("Int", vec![])), 822 | }), 823 | definition: FunctionDefinition { 824 | name: "a".to_string(), 825 | args: vec![var("b")], 826 | body: int("42"), 827 | }, 828 | })] 829 | )) 830 | ); 831 | } 832 | 833 | #[test] 834 | fn let_group_two_functions() { 835 | assert_eq!( 836 | let_bindings(CompleteStr("a b = 42\nc d = 24"), 0), 837 | Ok(( 838 | CompleteStr(""), 839 | vec![ 840 | LetEntry::LetFunction(Function { 841 | signature: None, 842 | definition: FunctionDefinition { 843 | name: "a".to_string(), 844 | args: vec![var("b")], 845 | body: int("42"), 846 | }, 847 | }), 848 | LetEntry::LetFunction(Function { 849 | signature: None, 850 | definition: FunctionDefinition { 851 | name: "c".to_string(), 852 | args: vec![var("d")], 853 | body: int("24"), 854 | }, 855 | }), 856 | ] 857 | )) 858 | ); 859 | } 860 | 861 | #[test] 862 | fn let_block_with_single_binding() { 863 | assert_eq!( 864 | let_expression(CompleteStr("let a = 42 in a"), 0), 865 | Ok(( 866 | CompleteStr(""), 867 | Expression::Let( 868 | vec![LetEntry::LetBinding(var("a"), int("42"))], 869 | Box::new(var("a")) 870 | ) 871 | )) 872 | ); 873 | } 874 | 875 | #[test] 876 | fn let_block_bind_to_underscore() { 877 | assert_eq!( 878 | let_expression(CompleteStr("let _ = 42 in 24"), 0), 879 | Ok(( 880 | CompleteStr(""), 881 | Expression::Let( 882 | vec![LetEntry::LetBinding(var("_"), int("42"))], 883 | Box::new(int("24")) 884 | ) 885 | )) 886 | ); 887 | } 888 | 889 | #[test] 890 | fn let_block_can_start_with_a_tag_name() { 891 | assert_eq!( 892 | let_expression(CompleteStr("let letter = 1 \n in letter"), 0), 893 | Ok(( 894 | CompleteStr(""), 895 | Expression::Let( 896 | vec![LetEntry::LetBinding(var("letter"), int("1"))], 897 | Box::new(var("letter")) 898 | ) 899 | )) 900 | ); 901 | } 902 | 903 | #[test] 904 | fn let_block_function_1() { 905 | assert_eq!( 906 | let_expression( 907 | CompleteStr( 908 | "let 909 | f x = x + 1 910 | in 911 | f 4" 912 | ), 913 | 0 914 | ), 915 | Ok(( 916 | CompleteStr(""), 917 | Expression::Let( 918 | vec![LetEntry::LetFunction(Function { 919 | signature: None, 920 | definition: FunctionDefinition { 921 | name: "f".to_string(), 922 | args: vec![var("x")], 923 | body: bin_op(var("+"), var("x"), int("1")), 924 | }, 925 | })], 926 | Box::new(application(var("f"), int("4"))) 927 | ) 928 | )) 929 | ); 930 | } 931 | 932 | #[test] 933 | fn let_block_function_2() { 934 | assert_eq!( 935 | let_expression( 936 | CompleteStr( 937 | "let 938 | f x = x + 1 939 | g x = x + 1 940 | in 941 | f 4" 942 | ), 943 | 0 944 | ), 945 | Ok(( 946 | CompleteStr(""), 947 | Expression::Let( 948 | vec![ 949 | LetEntry::LetFunction(Function { 950 | signature: None, 951 | definition: FunctionDefinition { 952 | name: "f".to_string(), 953 | args: vec![var("x")], 954 | body: bin_op(var("+"), var("x"), int("1")), 955 | }, 956 | }), 957 | LetEntry::LetFunction(Function { 958 | signature: None, 959 | definition: FunctionDefinition { 960 | name: "g".to_string(), 961 | args: vec![var("x")], 962 | body: bin_op(var("+"), var("x"), int("1")), 963 | }, 964 | }), 965 | ], 966 | Box::new(application(var("f"), int("4"))) 967 | ) 968 | )) 969 | ); 970 | } 971 | 972 | #[test] 973 | fn let_block_multiple_bindings() { 974 | assert_eq!( 975 | let_expression( 976 | CompleteStr( 977 | "let 978 | a = 42 979 | b = a + 1 980 | in 981 | b" 982 | ), 983 | 0 984 | ), 985 | Ok(( 986 | CompleteStr(""), 987 | Expression::Let( 988 | vec![ 989 | LetEntry::LetBinding(var("a"), int("42")), 990 | LetEntry::LetBinding(var("b"), bin_op(var("+"), var("a"), int("1"))), 991 | ], 992 | Box::new(var("b")) 993 | ) 994 | )) 995 | ); 996 | } 997 | 998 | #[test] 999 | fn let_block_function_with_function_signature() { 1000 | assert_eq!( 1001 | let_expression( 1002 | CompleteStr( 1003 | "let 1004 | f : Int -> Int 1005 | f x = x + 1 1006 | in 1007 | f 4" 1008 | ), 1009 | 0 1010 | ), 1011 | Ok(( 1012 | CompleteStr(""), 1013 | Expression::Let( 1014 | vec![LetEntry::LetFunction(Function { 1015 | signature: Some(FunctionSignature { 1016 | name: "f".to_string(), 1017 | type_: tapp(tcon("Int", vec![]), tcon("Int", vec![])), 1018 | }), 1019 | definition: FunctionDefinition { 1020 | name: "f".to_string(), 1021 | args: vec![var("x")], 1022 | body: bin_op(var("+"), var("x"), int("1")), 1023 | }, 1024 | })], 1025 | Box::new(application(var("f"), int("4"))) 1026 | ) 1027 | )) 1028 | ); 1029 | } 1030 | 1031 | #[test] 1032 | fn let_block_destructuring() { 1033 | assert_eq!( 1034 | expression(CompleteStr("let (a,b) = (1,2) in a"), 0), 1035 | Ok(( 1036 | CompleteStr(""), 1037 | Expression::Let( 1038 | vec![LetEntry::LetBinding( 1039 | Expression::Tuple(vec![var("a"), var("b")]), 1040 | Expression::Tuple(vec![int("1"), int("2")]), 1041 | )], 1042 | Box::new(var("a")), 1043 | ) 1044 | )) 1045 | ); 1046 | } 1047 | 1048 | // Case Expressions 1049 | 1050 | #[test] 1051 | fn case_simple_statement() { 1052 | assert_eq!( 1053 | case_expression( 1054 | CompleteStr( 1055 | "case x of 1056 | Nothing -> 1057 | 0 1058 | Just y -> 1059 | y" 1060 | ), 1061 | 0 1062 | ), 1063 | Ok(( 1064 | CompleteStr(""), 1065 | Expression::Case( 1066 | Box::new(var("x")), 1067 | vec![ 1068 | (var("Nothing"), int("0")), 1069 | (application(var("Just"), var("y")), var("y")), 1070 | ], 1071 | ) 1072 | )) 1073 | ); 1074 | } 1075 | 1076 | #[test] 1077 | fn case_simple_statement_with_blank_line() { 1078 | assert_eq!( 1079 | case_expression( 1080 | CompleteStr( 1081 | "case x of 1082 | Nothing -> 1083 | 0 1084 | 1085 | Just y -> 1086 | y" 1087 | ), 1088 | 0 1089 | ), 1090 | Ok(( 1091 | CompleteStr(""), 1092 | Expression::Case( 1093 | Box::new(var("x")), 1094 | vec![ 1095 | (var("Nothing"), int("0")), 1096 | (application(var("Just"), var("y")), var("y")), 1097 | ], 1098 | ) 1099 | )) 1100 | ); 1101 | } 1102 | 1103 | #[test] 1104 | fn case_binding_to_underscore() { 1105 | assert_eq!( 1106 | case_expression( 1107 | CompleteStr( 1108 | "case x of 1109 | _ -> 1110 | 42" 1111 | ), 1112 | 0 1113 | ), 1114 | Ok(( 1115 | CompleteStr(""), 1116 | Expression::Case(Box::new(var("x")), vec![(var("_"), int("42"))]) 1117 | )) 1118 | ); 1119 | } 1120 | 1121 | #[test] 1122 | fn case_nested() { 1123 | assert_eq!( 1124 | case_expression( 1125 | CompleteStr( 1126 | "case x of 1127 | a -> a 1128 | b -> 1129 | case y of 1130 | a1 -> a1 1131 | b1 -> b1 1132 | c -> c" 1133 | ), 1134 | 0 1135 | ), 1136 | Ok(( 1137 | CompleteStr(""), 1138 | Expression::Case( 1139 | Box::new(var("x")), 1140 | vec![ 1141 | (var("a"), var("a")), 1142 | ( 1143 | var("b"), 1144 | Expression::Case( 1145 | Box::new(var("y")), 1146 | vec![(var("a1"), var("a1")), (var("b1"), var("b1"))], 1147 | ), 1148 | ), 1149 | (var("c"), var("c")), 1150 | ] 1151 | ) 1152 | )) 1153 | ); 1154 | } 1155 | 1156 | // If Expressions 1157 | 1158 | #[test] 1159 | fn if_statement() { 1160 | assert_eq!( 1161 | if_expression( 1162 | CompleteStr( 1163 | "if a then 1164 | 1 1165 | else 1166 | 2" 1167 | ), 1168 | 0 1169 | ), 1170 | Ok((CompleteStr(""), if_(var("a"), int("1"), int("2")))) 1171 | ); 1172 | } 1173 | 1174 | #[test] 1175 | fn if_statement_with_tuple() { 1176 | assert_eq!( 1177 | if_expression( 1178 | CompleteStr( 1179 | "if (a, b) == (1, 2) then 1180 | 1 1181 | else 1182 | 2" 1183 | ), 1184 | 0 1185 | ), 1186 | Ok(( 1187 | CompleteStr(""), 1188 | if_( 1189 | bin_op( 1190 | var("=="), 1191 | Expression::Tuple(vec![var("a"), var("b")]), 1192 | Expression::Tuple(vec![int("1"), int("2")]) 1193 | ), 1194 | int("1"), 1195 | int("2") 1196 | ) 1197 | )) 1198 | ); 1199 | } 1200 | 1201 | #[test] 1202 | fn if_statement_else_if_else() { 1203 | assert_eq!( 1204 | if_expression( 1205 | CompleteStr( 1206 | "if a then 1207 | 1 1208 | else if b then 1209 | 2 1210 | else 1211 | 3" 1212 | ), 1213 | 0 1214 | ), 1215 | Ok(( 1216 | CompleteStr(""), 1217 | if_(var("a"), int("1"), if_(var("b"), int("2"), int("3"))) 1218 | )) 1219 | ); 1220 | } 1221 | 1222 | // Expressions 1223 | 1224 | #[test] 1225 | fn operator_passed_to_map() { 1226 | assert_eq!( 1227 | expression(CompleteStr("reduce (+) list"), 0), 1228 | Ok(( 1229 | CompleteStr(""), 1230 | application(application(var("reduce"), var("+")), var("list")) 1231 | )) 1232 | ); 1233 | } 1234 | 1235 | #[test] 1236 | fn partial_application() { 1237 | assert_eq!( 1238 | expression(CompleteStr("(+) 2"), 0), 1239 | Ok((CompleteStr(""), application(var("+"), int("2")))) 1240 | ); 1241 | } 1242 | 1243 | #[test] 1244 | fn case_with_as() { 1245 | assert_eq!( 1246 | expression(CompleteStr("case a of \nT _ as x -> 1"), 0), 1247 | Ok(( 1248 | CompleteStr(""), 1249 | Expression::Case( 1250 | Box::new(var("a")), 1251 | vec![( 1252 | Expression::BinOp( 1253 | Box::new(var("as")), 1254 | Box::new(application(var("T"), var("_"))), 1255 | Box::new(var("x")), 1256 | ), 1257 | int("1"), 1258 | )] 1259 | ) 1260 | )) 1261 | ); 1262 | } 1263 | 1264 | #[test] 1265 | fn lambda_destructuring() { 1266 | assert_eq!( 1267 | expression(CompleteStr("\\(a,b) acc -> 1"), 0), 1268 | Ok(( 1269 | CompleteStr(""), 1270 | Expression::Lambda( 1271 | vec![Expression::Tuple(vec![var("a"), var("b")]), var("acc")], 1272 | Box::new(int("1")), 1273 | ) 1274 | )) 1275 | ); 1276 | } 1277 | 1278 | #[test] 1279 | fn access() { 1280 | assert_eq!( 1281 | expression(CompleteStr("Module.a"), 0), 1282 | Ok(( 1283 | CompleteStr(""), 1284 | Expression::Access(Box::new(var("Module")), vec!["a".to_string()]) 1285 | )) 1286 | ); 1287 | } 1288 | 1289 | #[test] 1290 | fn access_function() { 1291 | assert_eq!( 1292 | expression(CompleteStr("map .a list"), 0), 1293 | Ok(( 1294 | CompleteStr(""), 1295 | application( 1296 | application(var("map"), Expression::AccessFunction("a".to_string())), 1297 | var("list") 1298 | ) 1299 | )) 1300 | ); 1301 | } 1302 | } 1303 | -------------------------------------------------------------------------------- /src/ast/expression/string.rs: -------------------------------------------------------------------------------- 1 | use ast::expression::core::Expression; 2 | 3 | use nom::types::CompleteStr; 4 | 5 | named!(single_string, 6 | map!( 7 | delimited!( 8 | char!('"'), 9 | re_matches!(r#"^(\\\\|\\"|[^"\n])*"#), 10 | char!('"') 11 | ), 12 | // There is a match above so we'll have a single entry in the vec 13 | |v| v[0].to_string() 14 | ) 15 | ); 16 | 17 | named!(multi_string, 18 | map!( 19 | delimited!(tag!(r#"""""#), re_matches!(r#"^([^"]*)"#), tag!(r#"""""#)), 20 | // There is a match above so we'll have a single entry in the vec 21 | |v| v[0].to_string() 22 | ) 23 | ); 24 | 25 | named!(pub string, 26 | map!( 27 | alt!( 28 | multi_string 29 | | single_string 30 | ), 31 | |c| Expression::String(c.to_string()) 32 | ) 33 | ); 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | 38 | use ast::expression::*; 39 | use nom::types::CompleteStr; 40 | 41 | #[test] 42 | fn empty_string() { 43 | assert_eq!( 44 | string(CompleteStr(r#""""#)), 45 | Ok((CompleteStr(""), Expression::String("".to_string()))) 46 | ); 47 | } 48 | 49 | #[test] 50 | fn simple_string() { 51 | assert_eq!( 52 | string(CompleteStr(r#""hello""#)), 53 | Ok((CompleteStr(""), Expression::String("hello".to_string()))) 54 | ); 55 | } 56 | 57 | #[test] 58 | fn escaped_string() { 59 | assert_eq!( 60 | string(CompleteStr(r#""hello, \"world\"""#)), 61 | Ok(( 62 | CompleteStr(""), 63 | Expression::String(r#"hello, \"world\""#.to_string()) 64 | )) 65 | ); 66 | } 67 | 68 | #[test] 69 | fn double_escaped_string() { 70 | assert_eq!( 71 | string(CompleteStr("\"\\\\\"")), 72 | Ok((CompleteStr(""), Expression::String("\\\\".to_string()))) 73 | ); 74 | } 75 | 76 | #[test] 77 | fn empty_triple_quote_string() { 78 | assert_eq!( 79 | string(CompleteStr(r#""""""""#)), 80 | Ok((CompleteStr(""), Expression::String("".to_string()))) 81 | ); 82 | } 83 | 84 | #[test] 85 | fn simple_multi_line_string() { 86 | assert_eq!( 87 | string(CompleteStr("\"\"\"hello\nworld\"\"\"")), 88 | Ok(( 89 | CompleteStr(""), 90 | Expression::String("hello\nworld".to_string()) 91 | )) 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ast/expression/variable.rs: -------------------------------------------------------------------------------- 1 | use nom::types::CompleteStr; 2 | 3 | use ast::expression::core::Expression; 4 | use ast::helpers::{lo_name, operator, up_name}; 5 | 6 | named!(lower_case, 7 | map!( 8 | lo_name, 9 | |v| Expression::Variable(vec!(v)) 10 | ) 11 | ); 12 | 13 | named!(upper_with_dots, 14 | map!( 15 | separated_nonempty_list!(char!('.'), up_name), 16 | Expression::Variable 17 | ) 18 | ); 19 | 20 | named!(op, 21 | map!( 22 | delimited!(char!('('), 23 | // Either a normal operator or commas inside brackets. We can't include ',' in the 24 | // operator match as it risks matching commas in lists, etc. 25 | alt!( 26 | operator 27 | | map!(is_a!(","), |c| c.to_string())), char!(')') 28 | ), 29 | |s| Expression::Variable(vec![s]) 30 | ) 31 | ); 32 | 33 | named!(pub variable, 34 | alt!( 35 | lower_case 36 | | upper_with_dots 37 | | op 38 | ) 39 | ); 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | 44 | use ast::expression::*; 45 | use nom::types::CompleteStr; 46 | 47 | #[test] 48 | fn simple_variable() { 49 | assert_eq!( 50 | variable(CompleteStr("abc")), 51 | Ok(( 52 | CompleteStr(""), 53 | Expression::Variable(vec!["abc".to_string()]) 54 | )) 55 | ); 56 | } 57 | 58 | #[test] 59 | fn simple_variable_with_digits() { 60 | assert_eq!( 61 | variable(CompleteStr("abc123")), 62 | Ok(( 63 | CompleteStr(""), 64 | Expression::Variable(vec!["abc123".to_string()]) 65 | )) 66 | ); 67 | } 68 | 69 | #[test] 70 | fn single_letter_variable() { 71 | assert_eq!( 72 | variable(CompleteStr("a")), 73 | Ok((CompleteStr(""), Expression::Variable(vec!["a".to_string()]))) 74 | ); 75 | } 76 | 77 | #[test] 78 | fn upper_letter_variable() { 79 | assert_eq!( 80 | variable(CompleteStr("Abc")), 81 | Ok(( 82 | CompleteStr(""), 83 | Expression::Variable(vec!["Abc".to_string()]) 84 | )) 85 | ); 86 | } 87 | 88 | #[test] 89 | fn operator_in_parens() { 90 | assert_eq!( 91 | variable(CompleteStr("(==)")), 92 | Ok(( 93 | CompleteStr(""), 94 | Expression::Variable(vec!["==".to_string()]) 95 | )) 96 | ); 97 | } 98 | 99 | #[test] 100 | fn invalid_variable_2() { 101 | assert!(variable(CompleteStr("3bc")).is_err()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/ast/helpers.rs: -------------------------------------------------------------------------------- 1 | use nom::types::CompleteStr; 2 | 3 | pub type Name = String; 4 | 5 | pub type QualifiedType = Vec; 6 | 7 | pub type ModuleName = Vec; 8 | 9 | pub type Alias = String; 10 | 11 | const RESERVED_WORDS: &[&str] = &[ 12 | "module", "where", "import", "as", "exposing", "type", "alias", "port", "if", "then", "else", 13 | "let", "in", "case", "of", 14 | ]; 15 | 16 | const RESERVED_OPERATORS: &[&str] = &["=", ".", "..", "->", "--", "|", ":"]; 17 | 18 | named!(pub lo_name, 19 | alt!( 20 | map!(tag!("_"), |v| v.to_string()) 21 | | map_res!( 22 | re_matches!(r"^([a-z][a-zA-Z0-9_]*)"), 23 | |v: Vec| { 24 | let name = v[0].to_string(); 25 | if RESERVED_WORDS.contains(&name.as_str()) { 26 | Err("Reserved word: ".to_string() + &name) 27 | } 28 | else { 29 | Ok(name) 30 | } 31 | } 32 | ) 33 | ) 34 | ); 35 | 36 | named!(pub function_name, 37 | map!(lo_name, |s| s) 38 | ); 39 | 40 | named!(pub module_name, 41 | separated_nonempty_list!( 42 | char!('.'), 43 | up_name 44 | ) 45 | ); 46 | 47 | named!(pub up_name, 48 | map!(re_matches!(r"^([A-Z][a-zA-Z0-9_]*)"), |c| c[0].to_string()) 49 | ); 50 | 51 | named!(pub operator, 52 | map_res!( 53 | // Regex from elm-ast with extra '-' at the start as it was failing 54 | map!(re_matches!(r"^([-+\\-\\/*=.$<>:&|^?%#@~!]+)"), |c| c[0].to_string()), 55 | |op: String| { 56 | if RESERVED_OPERATORS.contains(&op.as_str()) { 57 | Err("Reserved operator: ".to_string() + &op) 58 | } 59 | else { 60 | Ok(op) 61 | } 62 | } 63 | ) 64 | ); 65 | 66 | named!(pub spaces , 67 | map!(is_a!(" "), |s| s.to_string()) 68 | ); 69 | 70 | named!(pub spaces0 , 71 | map!(opt!(is_a!(" ")), |s| s.map(|c| c.to_string()).unwrap_or_else(String::new)) 72 | ); 73 | 74 | named!(pub spaces_and_newlines , 75 | map!(is_a!(" \n"), |s| s.to_string()) 76 | ); 77 | 78 | named!(single_line_comment, 79 | map!(preceded!(tag!("--"), re_matches!(r"^(.*)")), |v| v[0].to_string()) 80 | ); 81 | 82 | // IndentRelation 83 | pub enum IR { 84 | EQ, 85 | GT, 86 | GTE, 87 | } 88 | 89 | #[derive(Debug, PartialEq, Clone)] 90 | pub enum SpacesContent { 91 | Spaces, 92 | Comment, 93 | NewLineAndSpaces, 94 | } 95 | 96 | named_args!(pub spaces_or_new_lines_and_indent(indentation: u32, ir: IR) , 97 | map_res!( 98 | many1!( 99 | alt!( 100 | value!(vec![(SpacesContent::Spaces, indentation)], spaces) 101 | | map!(single_line_comment, |_| vec![(SpacesContent::Comment, 0)]) 102 | | many1!( 103 | preceded!( 104 | char!('\n'), 105 | alt!( 106 | map!(spaces0, |s| (SpacesContent::NewLineAndSpaces, s.len() as u32)) 107 | | map!(single_line_comment, |_| (SpacesContent::Comment, 0)) 108 | ) 109 | ) 110 | ) 111 | ) 112 | ), 113 | |v: Vec>| { 114 | let (_, new_indent) = *(v.last().unwrap_or(&vec![]).last().unwrap_or(&(SpacesContent::Spaces, 0))); 115 | let has_new_line = v.iter().any(|ref subv| subv.iter().any(|(ref content, _)| *content == SpacesContent::NewLineAndSpaces)); 116 | 117 | // Only apply test if there have been new lines - otherwise we've just had some spaces or 118 | // comments and there isn't any new indentation to test 119 | match ir { 120 | IR::EQ => { 121 | if !has_new_line || new_indent == indentation { 122 | Ok(new_indent) 123 | } 124 | else { 125 | Err("Indent does not match".to_string()) 126 | } 127 | }, 128 | IR::GT => { 129 | if !has_new_line || new_indent > indentation { 130 | Ok(new_indent) 131 | } 132 | else { 133 | Err("Indent does must be greater".to_string()) 134 | } 135 | }, 136 | IR::GTE => { 137 | if !has_new_line || new_indent >= indentation { 138 | Ok(new_indent) 139 | } 140 | else { 141 | Err("Indent does must be greater than or equal".to_string()) 142 | } 143 | } 144 | } 145 | } 146 | ) 147 | ); 148 | 149 | named_args!(pub opt_spaces_or_new_lines_and_indent(indentation: u32, ir: IR) , 150 | map!( 151 | opt!(call!(spaces_or_new_lines_and_indent, indentation, ir)), 152 | |v| { 153 | v.unwrap_or(indentation) 154 | } 155 | ) 156 | ); 157 | 158 | named_args!(pub new_line_and_same_indent(indentation: u32) , 159 | map_res!( 160 | preceded!( 161 | char!('\n'), 162 | alt!( 163 | map!(spaces0, |s| s.len() as u32) 164 | ) 165 | ), 166 | |new_indent: u32| { 167 | if new_indent == indentation { 168 | Ok(new_indent) 169 | } 170 | else { 171 | Err("Indent does not match".to_string()) 172 | } 173 | } 174 | ) 175 | ); 176 | 177 | #[cfg(test)] 178 | mod tests { 179 | 180 | use ast::helpers::*; 181 | use nom::types::CompleteStr; 182 | 183 | #[test] 184 | fn simple_operator() { 185 | assert_eq!( 186 | operator(CompleteStr("==")), 187 | Ok((CompleteStr(""), "==".to_string())) 188 | ); 189 | } 190 | 191 | #[test] 192 | fn another_simple_operator() { 193 | assert_eq!( 194 | operator(CompleteStr(":-")), 195 | Ok((CompleteStr(""), ":-".to_string())) 196 | ); 197 | } 198 | 199 | #[test] 200 | fn invalid_operator() { 201 | assert!(operator(CompleteStr("=")).is_err()); 202 | } 203 | 204 | #[test] 205 | fn valid_zero_spaces() { 206 | assert_eq!( 207 | spaces_or_new_lines_and_indent(CompleteStr("\n "), 0, IR::GTE), 208 | Ok((CompleteStr(""), 1)) 209 | ); 210 | } 211 | 212 | #[test] 213 | fn invalid_zero_spaces() { 214 | assert!(spaces_or_new_lines_and_indent(CompleteStr("\n"), 1, IR::GTE).is_err()); 215 | } 216 | 217 | #[test] 218 | fn spaces_with_comment() { 219 | assert_eq!( 220 | spaces_or_new_lines_and_indent(CompleteStr(" -- Comment\n "), 0, IR::GTE), 221 | Ok((CompleteStr(""), 2)) 222 | ); 223 | } 224 | 225 | #[test] 226 | fn just_spaces() { 227 | assert_eq!( 228 | spaces_or_new_lines_and_indent(CompleteStr(" "), 1, IR::GTE), 229 | Ok((CompleteStr(""), 1)) 230 | ); 231 | } 232 | 233 | #[test] 234 | fn opt_just_spaces() { 235 | assert_eq!( 236 | opt_spaces_or_new_lines_and_indent(CompleteStr(" "), 1, IR::GTE), 237 | Ok((CompleteStr(""), 1)) 238 | ); 239 | } 240 | 241 | #[test] 242 | fn opt_no_spaces() { 243 | assert_eq!( 244 | opt_spaces_or_new_lines_and_indent(CompleteStr(""), 2, IR::GTE), 245 | Ok((CompleteStr(""), 2)) 246 | ); 247 | } 248 | 249 | #[test] 250 | fn simple_name() { 251 | assert_eq!( 252 | lo_name(CompleteStr("goToUserPage")), 253 | Ok((CompleteStr(""), "goToUserPage".to_string())) 254 | ); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | use nom::types::CompleteStr; 2 | 3 | pub mod binop; 4 | pub mod expression; 5 | pub mod helpers; 6 | pub mod statement; 7 | pub mod type_; 8 | 9 | use ast::statement::statements; 10 | pub use ast::statement::{ExportSet, Statement}; 11 | 12 | named!(pub file>, 13 | terminated!(statements, eof!()) 14 | ); 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | 19 | use ast::file; 20 | use nom::types::CompleteStr; 21 | 22 | #[test] 23 | fn simple_file() { 24 | assert!( 25 | file(CompleteStr( 26 | "module Test exposing (..) 27 | 28 | main : Program Flags Model Msg 29 | main = 30 | myProgram 31 | " 32 | )).is_ok() 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ast/statement/comment.rs: -------------------------------------------------------------------------------- 1 | use ast::statement::core::Statement; 2 | 3 | use nom::types::CompleteStr; 4 | 5 | named!(single_line_comment, 6 | map!( 7 | preceded!(tag!("--"), re_matches!(r"^(.*)")), 8 | |v| Statement::Comment(v[0].to_string()) 9 | ) 10 | ); 11 | 12 | named!(multi_line_comment, 13 | map!( 14 | preceded!( 15 | tag!("{-"), 16 | take_until_and_consume!("-}") 17 | ), 18 | |s| Statement::Comment(s.to_string()) 19 | ) 20 | ); 21 | 22 | named!(pub comment, 23 | alt!( 24 | single_line_comment 25 | | multi_line_comment 26 | ) 27 | ); 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | 32 | use ast::statement::comment::*; 33 | use ast::statement::core::*; 34 | use nom::types::CompleteStr; 35 | 36 | #[test] 37 | fn simple_comment() { 38 | assert_eq!( 39 | comment(CompleteStr("-- abc")), 40 | Ok((CompleteStr(""), Statement::Comment(" abc".to_string()))) 41 | ); 42 | } 43 | 44 | #[test] 45 | fn comment_with_trailing_white_space() { 46 | assert_eq!( 47 | comment(CompleteStr("-- abc ")), 48 | Ok((CompleteStr(""), Statement::Comment(" abc ".to_string()))) 49 | ); 50 | } 51 | 52 | #[test] 53 | fn open_close_comment() { 54 | assert_eq!( 55 | comment(CompleteStr("{- cba -}")), 56 | Ok((CompleteStr(""), Statement::Comment(" cba ".to_string()))) 57 | ); 58 | } 59 | 60 | #[test] 61 | fn multi_line_comment() { 62 | assert_eq!( 63 | comment(CompleteStr("{- c\nb\na -}")), 64 | Ok((CompleteStr(""), Statement::Comment(" c\nb\na ".to_string()))) 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ast/statement/core.rs: -------------------------------------------------------------------------------- 1 | use ast::binop::Assoc; 2 | use ast::expression::core::Function; 3 | use ast::expression::Expression; 4 | use ast::helpers::{Alias, ModuleName, Name}; 5 | use ast::type_::core::Type; 6 | 7 | #[derive(Debug, PartialEq, Clone)] 8 | pub enum ExportSet { 9 | AllExport, 10 | SubsetExport(Vec), 11 | FunctionExport(Name), 12 | TypeExport(Name, Option>), 13 | } 14 | 15 | #[derive(Debug, PartialEq, Clone)] 16 | pub enum Statement { 17 | ModuleDeclaration(ModuleName, ExportSet), 18 | PortModuleDeclaration(ModuleName, ExportSet), 19 | EffectModuleDeclaration(ModuleName, Vec<(Name, Name)>, ExportSet), 20 | ImportStatement(ModuleName, Option, Option), 21 | TypeAliasDeclaration(Type, Type), 22 | TypeDeclaration(Type, Vec), 23 | PortTypeDeclaration(Name, Type), 24 | PortDeclaration(Name, Vec, Expression), 25 | Function(Function), 26 | InfixDeclaration(Assoc, i64, Name), 27 | Comment(String), 28 | } 29 | -------------------------------------------------------------------------------- /src/ast/statement/export.rs: -------------------------------------------------------------------------------- 1 | use ast::helpers::{ 2 | function_name, operator, spaces_and_newlines, spaces_or_new_lines_and_indent, up_name, IR, 3 | }; 4 | use ast::statement::core::ExportSet; 5 | 6 | use nom::types::CompleteStr; 7 | 8 | named!(all_export, 9 | map!(tag!(".."), |_| ExportSet::AllExport) 10 | ); 11 | 12 | named!(function_export, 13 | map!( 14 | alt!( 15 | function_name 16 | | delimited!(char!('('), operator, char!(')')) 17 | ), 18 | ExportSet::FunctionExport 19 | ) 20 | ); 21 | 22 | named!(constructor_subset_exports, 23 | map!( 24 | separated_nonempty_list!( 25 | tag!(", "), 26 | map!(up_name, ExportSet::FunctionExport) 27 | ), 28 | ExportSet::SubsetExport 29 | ) 30 | ); 31 | 32 | named!(constructor_exports>>, 33 | opt!( 34 | map!( 35 | delimited!( 36 | char!('('), 37 | alt!( 38 | all_export 39 | | constructor_subset_exports 40 | ), 41 | char!(')') 42 | ), 43 | Box::new 44 | ) 45 | ) 46 | ); 47 | 48 | named!(type_export, 49 | do_parse!( 50 | name: up_name >> 51 | constructors: constructor_exports >> 52 | (ExportSet::TypeExport(name, constructors)) 53 | ) 54 | ); 55 | 56 | named!(subset_export, 57 | map!( 58 | separated_nonempty_list!( 59 | delimited!(opt!(spaces_and_newlines), char!(','), opt!(spaces_and_newlines)), 60 | alt!( 61 | function_export 62 | | type_export 63 | ) 64 | ), 65 | ExportSet::SubsetExport 66 | ) 67 | ); 68 | 69 | named!(pub exports, 70 | delimited!( 71 | preceded!(char!('('), opt!(call!(spaces_or_new_lines_and_indent, 0, IR::GTE))), 72 | alt!( 73 | all_export 74 | | subset_export 75 | ), 76 | terminated!(opt!(call!(spaces_or_new_lines_and_indent, 0, IR::GTE)), char!(')')) 77 | ) 78 | ); 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | 83 | use ast::statement::core::ExportSet; 84 | use ast::statement::export::exports; 85 | use nom::types::CompleteStr; 86 | 87 | fn fexp(name: &str) -> ExportSet { 88 | ExportSet::FunctionExport(name.to_string()) 89 | } 90 | 91 | #[test] 92 | fn simple_all_export() { 93 | assert_eq!( 94 | exports(CompleteStr("(..)")), 95 | Ok((CompleteStr(""), ExportSet::AllExport)) 96 | ); 97 | } 98 | 99 | #[test] 100 | fn simple_function_export() { 101 | assert_eq!( 102 | exports(CompleteStr("(a, b)")), 103 | Ok(( 104 | CompleteStr(""), 105 | ExportSet::SubsetExport(vec![fexp("a"), fexp("b")]) 106 | )) 107 | ); 108 | } 109 | 110 | #[test] 111 | fn simple_multiline_function_export() { 112 | assert_eq!( 113 | exports(CompleteStr("( a\n,\n b\n,\nc)")), 114 | Ok(( 115 | CompleteStr(""), 116 | ExportSet::SubsetExport(vec![fexp("a"), fexp("b"), fexp("c")]) 117 | )) 118 | ); 119 | } 120 | 121 | #[test] 122 | fn simple_operator_export() { 123 | assert_eq!( 124 | exports(CompleteStr("((=>), (++))")), 125 | Ok(( 126 | CompleteStr(""), 127 | ExportSet::SubsetExport(vec![fexp("=>"), fexp("++")]) 128 | )) 129 | ); 130 | } 131 | 132 | #[test] 133 | fn simple_constructors_export() { 134 | assert_eq!( 135 | exports(CompleteStr("(ReadOnly, Write(..), Sync(First, Last))")), 136 | Ok(( 137 | CompleteStr(""), 138 | ExportSet::SubsetExport(vec![ 139 | ExportSet::TypeExport("ReadOnly".to_string(), None), 140 | ExportSet::TypeExport( 141 | "Write".to_string(), 142 | Some(Box::new(ExportSet::AllExport)), 143 | ), 144 | ExportSet::TypeExport( 145 | "Sync".to_string(), 146 | Some(Box::new(ExportSet::SubsetExport(vec![ 147 | fexp("First"), 148 | fexp("Last"), 149 | ]))), 150 | ), 151 | ]) 152 | )) 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/ast/statement/import.rs: -------------------------------------------------------------------------------- 1 | use ast::helpers::{module_name, spaces, up_name}; 2 | use ast::statement::core::Statement; 3 | use ast::statement::export::exports; 4 | 5 | use nom::types::CompleteStr; 6 | 7 | named!(pub import_statement, 8 | do_parse!( 9 | tag!("import") >> 10 | spaces >> 11 | name: module_name >> 12 | alias: opt!( 13 | preceded!( 14 | delimited!(spaces, tag!("as"), spaces), 15 | up_name 16 | ) 17 | ) >> 18 | exposing: opt!( 19 | preceded!( 20 | delimited!(spaces, tag!("exposing"), spaces), 21 | exports 22 | ) 23 | ) >> 24 | (Statement::ImportStatement(name, alias, exposing)) 25 | ) 26 | ); 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | 31 | use ast::helpers::Alias; 32 | use ast::statement::core::*; 33 | use ast::statement::import::*; 34 | use nom::types::CompleteStr; 35 | 36 | fn imp(name: &str, alias: Option, exposing: Option) -> Statement { 37 | Statement::ImportStatement(vec![name.to_string()], alias, exposing) 38 | } 39 | 40 | fn fexp(name: &str) -> ExportSet { 41 | ExportSet::FunctionExport(name.to_string()) 42 | } 43 | 44 | fn texp(name: &str, exposing: Option>) -> ExportSet { 45 | ExportSet::TypeExport(name.to_string(), exposing) 46 | } 47 | 48 | #[test] 49 | fn simple_import() { 50 | assert_eq!( 51 | import_statement(CompleteStr("import A")), 52 | Ok((CompleteStr(""), imp("A", None, None))) 53 | ); 54 | } 55 | 56 | #[test] 57 | fn import_as() { 58 | assert_eq!( 59 | import_statement(CompleteStr("import A as B")), 60 | Ok((CompleteStr(""), imp("A", Some("B".to_string()), None))) 61 | ); 62 | } 63 | 64 | #[test] 65 | fn import_exposing_all() { 66 | assert_eq!( 67 | import_statement(CompleteStr("import A exposing (..)")), 68 | Ok((CompleteStr(""), imp("A", None, Some(ExportSet::AllExport)))) 69 | ); 70 | } 71 | 72 | #[test] 73 | fn import_exposing() { 74 | assert_eq!( 75 | import_statement(CompleteStr("import A exposing (A, b)")), 76 | Ok(( 77 | CompleteStr(""), 78 | imp( 79 | "A", 80 | None, 81 | Some(ExportSet::SubsetExport(vec![texp("A", None), fexp("b")])) 82 | ) 83 | )) 84 | ); 85 | } 86 | 87 | #[test] 88 | fn import_exposing_union() { 89 | assert_eq!( 90 | import_statement(CompleteStr("import A exposing (A(..))")), 91 | Ok(( 92 | CompleteStr(""), 93 | imp( 94 | "A", 95 | None, 96 | Some(ExportSet::SubsetExport(vec![texp( 97 | "A", 98 | Some(Box::new(ExportSet::AllExport)), 99 | )])) 100 | ) 101 | )) 102 | ); 103 | } 104 | 105 | #[test] 106 | fn import_exposing_constructor_subset() { 107 | assert_eq!( 108 | import_statement(CompleteStr("import A exposing (A(A))")), 109 | Ok(( 110 | CompleteStr(""), 111 | imp( 112 | "A", 113 | None, 114 | Some(ExportSet::SubsetExport(vec![texp( 115 | "A", 116 | Some(Box::new(ExportSet::SubsetExport(vec![fexp("A")]))), 117 | )])) 118 | ) 119 | )) 120 | ); 121 | } 122 | 123 | #[test] 124 | fn import_multiline() { 125 | assert_eq!( 126 | import_statement(CompleteStr("import A as B exposing (A, B,\nc)")), 127 | Ok(( 128 | CompleteStr(""), 129 | imp( 130 | "A", 131 | Some("B".to_string()), 132 | Some(ExportSet::SubsetExport(vec![ 133 | texp("A", None), 134 | texp("B", None), 135 | fexp("c"), 136 | ])) 137 | ) 138 | )) 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/ast/statement/infix.rs: -------------------------------------------------------------------------------- 1 | use ast::binop::Assoc; 2 | use ast::helpers::{lo_name, operator, spaces}; 3 | use ast::statement::core::Statement; 4 | 5 | use nom::digit; 6 | use nom::types::CompleteStr; 7 | 8 | named!(pub infix_declaration, 9 | do_parse!( 10 | type_: alt!( 11 | value!(Assoc::L, tag!("infixl")) 12 | | value!(Assoc::R, tag!("infixr")) 13 | | value!(Assoc::N, tag!("infix")) 14 | ) >> 15 | spaces >> 16 | value: map_res!(digit, |s: CompleteStr| s.to_string().parse::()) >> 17 | spaces >> 18 | name: alt!( 19 | lo_name 20 | | operator 21 | ) >> 22 | (Statement::InfixDeclaration(type_, value, name)) 23 | ) 24 | ); 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | 29 | use ast::statement::core::*; 30 | use ast::statement::infix::*; 31 | use nom::types::CompleteStr; 32 | 33 | #[test] 34 | fn non() { 35 | assert_eq!( 36 | infix_declaration(CompleteStr("infix 9 :-")), 37 | Ok(( 38 | CompleteStr(""), 39 | Statement::InfixDeclaration(Assoc::N, 9, ":-".to_string()) 40 | )) 41 | ); 42 | } 43 | 44 | #[test] 45 | fn left() { 46 | assert_eq!( 47 | infix_declaration(CompleteStr("infixl 8 :=:")), 48 | Ok(( 49 | CompleteStr(""), 50 | Statement::InfixDeclaration(Assoc::L, 8, ":=:".to_string()) 51 | )) 52 | ); 53 | } 54 | 55 | #[test] 56 | fn right() { 57 | assert_eq!( 58 | infix_declaration(CompleteStr("infixr 7 -+-")), 59 | Ok(( 60 | CompleteStr(""), 61 | Statement::InfixDeclaration(Assoc::R, 7, "-+-".to_string()) 62 | )) 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ast/statement/mod.rs: -------------------------------------------------------------------------------- 1 | use nom::multispace0; 2 | use nom::types::CompleteStr; 3 | 4 | mod comment; 5 | mod core; 6 | mod export; 7 | mod import; 8 | mod infix; 9 | mod module; 10 | mod port; 11 | mod type_declaration; 12 | 13 | use ast::statement::comment::comment; 14 | pub use ast::statement::core::{ExportSet, Statement}; 15 | use ast::statement::import::import_statement; 16 | use ast::statement::infix::infix_declaration; 17 | use ast::statement::module::{ 18 | effect_module_declaration, module_declaration, port_module_declaration, 19 | }; 20 | use ast::statement::port::{port_declaration, port_type_declaration}; 21 | use ast::statement::type_declaration::{type_alias_declaration, type_declaration}; 22 | 23 | use ast::expression::function::function; 24 | 25 | named!(pub statement, 26 | alt!( 27 | port_module_declaration 28 | | effect_module_declaration 29 | | module_declaration 30 | | import_statement 31 | | type_alias_declaration 32 | | call!(type_declaration, 0) 33 | | port_type_declaration 34 | | port_declaration 35 | | map!(call!(function, 0), Statement::Function) 36 | | infix_declaration 37 | | comment 38 | ) 39 | ); 40 | 41 | named!(pub statements>, 42 | delimited!( 43 | multispace0, 44 | separated_nonempty_list!( 45 | multispace0, 46 | statement 47 | ), 48 | multispace0 49 | ) 50 | ); 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | 55 | use ast::statement::statements; 56 | use nom::types::CompleteStr; 57 | 58 | #[test] 59 | fn simple_statements() { 60 | assert!( 61 | statements(CompleteStr( 62 | "module Test exposing (..) 63 | 64 | main : Program Flags Model Msg 65 | main = 66 | myProgram 67 | " 68 | )).is_ok() 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ast/statement/module.rs: -------------------------------------------------------------------------------- 1 | use ast::helpers::{lo_name, module_name, spaces, spaces_or_new_lines_and_indent, up_name, IR}; 2 | use ast::statement::core::Statement; 3 | use ast::statement::export::exports; 4 | 5 | use nom::types::CompleteStr; 6 | 7 | named!(pub port_module_declaration, 8 | do_parse!( 9 | tag!("port") >> 10 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 11 | tag!("module") >> 12 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 13 | name: module_name >> 14 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 15 | tag!("exposing") >> 16 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 17 | exports: exports >> 18 | (Statement::PortModuleDeclaration(name, exports)) 19 | ) 20 | ); 21 | 22 | named!(pub effect_module_declaration, 23 | do_parse!( 24 | tag!("effect") >> 25 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 26 | tag!("module") >> 27 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 28 | name: module_name >> 29 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 30 | tag!("where") >> 31 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 32 | where_: delimited!( 33 | char!('{'), 34 | separated_list!( 35 | tag!(", "), 36 | do_parse!( 37 | lname: lo_name >> 38 | opt!(spaces) >> 39 | char!('=') >> 40 | opt!(spaces) >> 41 | uname: up_name >> 42 | ((lname, uname)) 43 | ) 44 | ), 45 | char!('}') 46 | ) >> 47 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 48 | tag!("exposing") >> 49 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 50 | exports: exports >> 51 | (Statement::EffectModuleDeclaration(name, where_, exports)) 52 | ) 53 | ); 54 | 55 | named!(pub module_declaration, 56 | do_parse!( 57 | tag!("module") >> 58 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 59 | name: module_name >> 60 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 61 | tag!("exposing") >> 62 | call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 63 | exports: exports >> 64 | (Statement::ModuleDeclaration(name, exports)) 65 | ) 66 | ); 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | 71 | use ast::statement::core::*; 72 | use ast::statement::module::*; 73 | use nom::types::CompleteStr; 74 | 75 | fn fexp(name: &str) -> ExportSet { 76 | ExportSet::FunctionExport(name.to_string()) 77 | } 78 | 79 | #[test] 80 | fn simple_declaration_exposing_all() { 81 | assert_eq!( 82 | module_declaration(CompleteStr("module A exposing (..)")), 83 | Ok(( 84 | CompleteStr(""), 85 | Statement::ModuleDeclaration(vec!["A".to_string()], ExportSet::AllExport) 86 | )) 87 | ); 88 | } 89 | 90 | #[test] 91 | fn declaration_exposing_particular_things() { 92 | assert_eq!( 93 | module_declaration(CompleteStr("module A exposing (A, b)")), 94 | Ok(( 95 | CompleteStr(""), 96 | Statement::ModuleDeclaration( 97 | vec!["A".to_string()], 98 | ExportSet::SubsetExport(vec![ 99 | ExportSet::TypeExport("A".to_string(), None), 100 | fexp("b"), 101 | ]) 102 | ) 103 | )) 104 | ); 105 | } 106 | 107 | #[test] 108 | fn declaration_exposing_an_infix_operator() { 109 | assert_eq!( 110 | module_declaration(CompleteStr("module A exposing ((?))")), 111 | Ok(( 112 | CompleteStr(""), 113 | Statement::ModuleDeclaration( 114 | vec!["A".to_string()], 115 | ExportSet::SubsetExport(vec![fexp("?")]) 116 | ) 117 | )) 118 | ); 119 | } 120 | 121 | #[test] 122 | fn declaration_exposing_union() { 123 | assert_eq!( 124 | module_declaration(CompleteStr("module A exposing (A(..))")), 125 | Ok(( 126 | CompleteStr(""), 127 | Statement::ModuleDeclaration( 128 | vec!["A".to_string()], 129 | ExportSet::SubsetExport(vec![ExportSet::TypeExport( 130 | "A".to_string(), 131 | Some(Box::new(ExportSet::AllExport)), 132 | )]) 133 | ) 134 | )) 135 | ); 136 | } 137 | 138 | #[test] 139 | fn declaration_exposing_constructor_subset() { 140 | assert_eq!( 141 | module_declaration(CompleteStr("module A exposing (A(A))")), 142 | Ok(( 143 | CompleteStr(""), 144 | Statement::ModuleDeclaration( 145 | vec!["A".to_string()], 146 | ExportSet::SubsetExport(vec![ExportSet::TypeExport( 147 | "A".to_string(), 148 | Some(Box::new(ExportSet::SubsetExport(vec![fexp("A")]))), 149 | )]) 150 | ) 151 | )) 152 | ); 153 | } 154 | 155 | #[test] 156 | fn multiline_declaration() { 157 | assert_eq!( 158 | module_declaration(CompleteStr("module\n A\n exposing\n ( A, B,\nc)")), 159 | Ok(( 160 | CompleteStr(""), 161 | Statement::ModuleDeclaration( 162 | vec!["A".to_string()], 163 | ExportSet::SubsetExport(vec![ 164 | ExportSet::TypeExport("A".to_string(), None), 165 | ExportSet::TypeExport("B".to_string(), None), 166 | fexp("c"), 167 | ]) 168 | ) 169 | )) 170 | ); 171 | } 172 | 173 | #[test] 174 | fn declaration_using_a_port() { 175 | assert_eq!( 176 | port_module_declaration(CompleteStr("port module A exposing (A(..))")), 177 | Ok(( 178 | CompleteStr(""), 179 | Statement::PortModuleDeclaration( 180 | vec!["A".to_string()], 181 | ExportSet::SubsetExport(vec![ExportSet::TypeExport( 182 | "A".to_string(), 183 | Some(Box::new(ExportSet::AllExport)), 184 | )]) 185 | ) 186 | )) 187 | ); 188 | } 189 | 190 | #[test] 191 | fn simple_effects() { 192 | assert_eq!( 193 | effect_module_declaration(CompleteStr( 194 | "effect module A where {subscription = MySub, command = MyCmd} exposing (..)" 195 | )), 196 | Ok(( 197 | CompleteStr(""), 198 | Statement::EffectModuleDeclaration( 199 | vec!["A".to_string()], 200 | vec![ 201 | ("subscription".to_string(), "MySub".to_string()), 202 | ("command".to_string(), "MyCmd".to_string()), 203 | ], 204 | ExportSet::AllExport 205 | ) 206 | )) 207 | ); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/ast/statement/port.rs: -------------------------------------------------------------------------------- 1 | use ast::expression::expression; 2 | use ast::helpers::{lo_name, spaces, spaces_or_new_lines_and_indent, IR}; 3 | use ast::statement::core::Statement; 4 | use ast::type_::type_annotation; 5 | 6 | use nom::types::CompleteStr; 7 | 8 | named!(pub port_type_declaration, 9 | do_parse!( 10 | tag!("port") >> 11 | spaces >> 12 | name: lo_name >> 13 | spaces >> 14 | char!(':') >> 15 | new_indent: call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 16 | type_: call!(type_annotation, new_indent) >> 17 | (Statement::PortTypeDeclaration(name, type_)) 18 | ) 19 | ); 20 | 21 | named!(pub port_declaration, 22 | do_parse!( 23 | tag!("port") >> 24 | spaces >> 25 | name: lo_name >> 26 | args: many0!(preceded!(spaces, lo_name)) >> 27 | spaces >> 28 | char!('=') >> 29 | new_indent: call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 30 | exp: call!(expression, new_indent) >> 31 | (Statement::PortDeclaration(name, args, exp)) 32 | ) 33 | ); 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | 38 | use ast::expression::Expression; 39 | use ast::statement::core::*; 40 | use ast::statement::port::*; 41 | use ast::type_::core::Type; 42 | use nom::types::CompleteStr; 43 | 44 | fn tvar(name: &str) -> Type { 45 | Type::TypeVariable(name.to_string()) 46 | } 47 | 48 | fn tapp(a: Type, b: Type) -> Type { 49 | Type::TypeApplication(Box::new(a), Box::new(b)) 50 | } 51 | 52 | fn tcon(name: &str, v: Vec) -> Type { 53 | Type::TypeConstructor(vec![name.to_string()], v) 54 | } 55 | 56 | #[test] 57 | fn constant() { 58 | assert_eq!( 59 | port_type_declaration(CompleteStr("port focus : String -> Cmd msg")), 60 | Ok(( 61 | CompleteStr(""), 62 | Statement::PortTypeDeclaration( 63 | "focus".to_string(), 64 | tapp(tcon("String", vec![]), tcon("Cmd", vec![tvar("msg")])) 65 | ) 66 | )) 67 | ); 68 | } 69 | 70 | #[test] 71 | fn another_port_type_declaration() { 72 | assert_eq!( 73 | port_type_declaration(CompleteStr("port users : (User -> msg) -> Sub msg")), 74 | Ok(( 75 | CompleteStr(""), 76 | Statement::PortTypeDeclaration( 77 | "users".to_string(), 78 | tapp( 79 | tapp(tcon("User", vec![]), tvar("msg")), 80 | tcon("Sub", vec![tvar("msg")]) 81 | ) 82 | ) 83 | )) 84 | ); 85 | } 86 | 87 | #[test] 88 | fn port_definition() { 89 | assert_eq!( 90 | port_declaration(CompleteStr("port focus = Cmd.none")), 91 | Ok(( 92 | CompleteStr(""), 93 | Statement::PortDeclaration( 94 | "focus".to_string(), 95 | vec![], 96 | Expression::Access( 97 | Box::new(Expression::Variable(vec!["Cmd".to_string()])), 98 | vec!["none".to_string()], 99 | ) 100 | ) 101 | )) 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/ast/statement/type_declaration.rs: -------------------------------------------------------------------------------- 1 | use ast::helpers::{spaces, spaces_or_new_lines_and_indent, IR}; 2 | use ast::statement::core::Statement; 3 | use ast::type_::{type_, type_annotation, type_constructor}; 4 | 5 | use nom::types::CompleteStr; 6 | 7 | named!(pub type_alias_declaration, 8 | do_parse!( 9 | tag!("type") >> 10 | spaces >> 11 | tag!("alias") >> 12 | spaces >> 13 | name: call!(type_, 0) >> 14 | spaces >> 15 | char!('=') >> 16 | call!(spaces_or_new_lines_and_indent, 1, IR::GTE) >> 17 | declaration: call!(type_annotation, 1) >> 18 | (Statement::TypeAliasDeclaration(name, declaration)) 19 | ) 20 | ); 21 | 22 | named_args!(pub type_declaration(indentation: u32), 23 | do_parse!( 24 | tag!("type") >> 25 | spaces >> 26 | name: call!(type_, indentation) >> 27 | new_indent: call!(spaces_or_new_lines_and_indent, 0, IR::GT) >> 28 | char!('=') >> 29 | call!(spaces_or_new_lines_and_indent, new_indent, IR::GTE) >> 30 | definition: separated_nonempty_list!( 31 | delimited!(call!(spaces_or_new_lines_and_indent, new_indent, IR::GTE), char!('|'), spaces), 32 | call!(type_constructor, new_indent) 33 | ) >> 34 | (Statement::TypeDeclaration(name, definition)) 35 | ) 36 | ); 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | 41 | use ast::statement::core::*; 42 | use ast::statement::type_declaration::*; 43 | use ast::type_::core::Type; 44 | use nom::types::CompleteStr; 45 | 46 | fn tvar(name: &str) -> Type { 47 | Type::TypeVariable(name.to_string()) 48 | } 49 | 50 | fn tapp(a: Type, b: Type) -> Type { 51 | Type::TypeApplication(Box::new(a), Box::new(b)) 52 | } 53 | 54 | fn tcon(name: &str, v: Vec) -> Type { 55 | Type::TypeConstructor(vec![name.to_string()], v) 56 | } 57 | 58 | #[test] 59 | fn can_parse_empty_record_aliases() { 60 | assert_eq!( 61 | type_alias_declaration(CompleteStr("type alias A = {}")), 62 | Ok(( 63 | CompleteStr(""), 64 | Statement::TypeAliasDeclaration( 65 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 66 | Type::TypeRecord(vec![]) 67 | ) 68 | )) 69 | ); 70 | } 71 | 72 | #[test] 73 | fn can_parse_aliases_of_unit() { 74 | assert_eq!( 75 | type_alias_declaration(CompleteStr("type alias A = ()")), 76 | Ok(( 77 | CompleteStr(""), 78 | Statement::TypeAliasDeclaration( 79 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 80 | Type::TypeTuple(vec![]) 81 | ) 82 | )) 83 | ); 84 | } 85 | 86 | #[test] 87 | fn multi_line_record() { 88 | assert_eq!( 89 | type_alias_declaration(CompleteStr( 90 | "type alias A =\n { a : String\n , b : String\n }" 91 | )), 92 | Ok(( 93 | CompleteStr(""), 94 | Statement::TypeAliasDeclaration( 95 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 96 | Type::TypeRecord(vec![ 97 | ( 98 | "a".to_string(), 99 | Type::TypeConstructor(vec!["String".to_string()], vec![]), 100 | ), 101 | ( 102 | "b".to_string(), 103 | Type::TypeConstructor(vec!["String".to_string()], vec![]), 104 | ), 105 | ]) 106 | ) 107 | )) 108 | ); 109 | } 110 | 111 | #[test] 112 | fn multi_line_record_with_tuple_list() { 113 | assert_eq!( 114 | type_alias_declaration(CompleteStr( 115 | "type alias A =\n { a : String\n , b : List ( String, String )\n }" 116 | )), 117 | Ok(( 118 | CompleteStr(""), 119 | Statement::TypeAliasDeclaration( 120 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 121 | Type::TypeRecord(vec![ 122 | ( 123 | "a".to_string(), 124 | Type::TypeConstructor(vec!["String".to_string()], vec![]), 125 | ), 126 | ( 127 | "b".to_string(), 128 | Type::TypeConstructor( 129 | vec!["List".to_string()], 130 | vec![Type::TypeTuple(vec![ 131 | tcon("String", vec![]), 132 | tcon("String", vec![]), 133 | ])], 134 | ), 135 | ), 136 | ]) 137 | ) 138 | )) 139 | ); 140 | } 141 | 142 | #[test] 143 | fn can_parse_simple_type_declaration() { 144 | assert_eq!( 145 | type_declaration(CompleteStr("type A = A"), 0), 146 | Ok(( 147 | CompleteStr(""), 148 | Statement::TypeDeclaration( 149 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 150 | vec![Type::TypeConstructor(vec!["A".to_string()], vec![])] 151 | ) 152 | )) 153 | ); 154 | } 155 | 156 | #[test] 157 | fn can_parse_multi_type_declaration() { 158 | assert_eq!( 159 | type_declaration(CompleteStr("type A = A | B | C"), 0), 160 | Ok(( 161 | CompleteStr(""), 162 | Statement::TypeDeclaration( 163 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 164 | vec![ 165 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 166 | Type::TypeConstructor(vec!["B".to_string()], vec![]), 167 | Type::TypeConstructor(vec!["C".to_string()], vec![]), 168 | ] 169 | ) 170 | )) 171 | ); 172 | } 173 | 174 | #[test] 175 | fn can_parse_multiline_type_declaration() { 176 | assert_eq!( 177 | type_declaration(CompleteStr("type A\n = A | B\n | C\n | D"), 0), 178 | Ok(( 179 | CompleteStr(""), 180 | Statement::TypeDeclaration( 181 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 182 | vec![ 183 | Type::TypeConstructor(vec!["A".to_string()], vec![]), 184 | Type::TypeConstructor(vec!["B".to_string()], vec![]), 185 | Type::TypeConstructor(vec!["C".to_string()], vec![]), 186 | Type::TypeConstructor(vec!["D".to_string()], vec![]), 187 | ] 188 | ) 189 | )) 190 | ); 191 | } 192 | 193 | #[test] 194 | fn can_parse_complex_function_alias() { 195 | assert_eq!( 196 | type_alias_declaration(CompleteStr( 197 | "type alias LanguageHelper a b = 198 | ({ b 199 | | commonErrorName : a 200 | , commonErrorDescription : a 201 | , kpiErrorCalculationType : a 202 | , kpiErrorAggregationType : a 203 | } 204 | -> a 205 | ) 206 | -> String" 207 | )), 208 | Ok(( 209 | CompleteStr(""), 210 | Statement::TypeAliasDeclaration( 211 | tcon("LanguageHelper", vec![tvar("a"), tvar("b")]), 212 | tapp( 213 | tapp( 214 | Type::TypeRecordConstructor( 215 | Box::new(tvar("b")), 216 | vec![ 217 | ("commonErrorName".to_string(), tvar("a")), 218 | ("commonErrorDescription".to_string(), tvar("a")), 219 | ("kpiErrorCalculationType".to_string(), tvar("a")), 220 | ("kpiErrorAggregationType".to_string(), tvar("a")), 221 | ], 222 | ), 223 | tvar("a"), 224 | ), 225 | tcon("String", vec![]), 226 | ) 227 | ) 228 | )) 229 | ); 230 | } 231 | 232 | // #[test] 233 | // fn fails_with_trailing_character() { 234 | // assert!(type_declaration(CompleteStr("type A\n = A | B\n | C\n | D\n\nf"), 0).is_err()); 235 | // } 236 | } 237 | -------------------------------------------------------------------------------- /src/ast/type_/core.rs: -------------------------------------------------------------------------------- 1 | use ast::helpers::{Name, QualifiedType}; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub enum Type { 5 | TypeConstructor(QualifiedType, Vec), 6 | TypeVariable(Name), 7 | TypeRecordConstructor(Box, Vec<(Name, Type)>), 8 | TypeRecord(Vec<(Name, Type)>), 9 | TypeTuple(Vec), 10 | TypeApplication(Box, Box), 11 | } 12 | -------------------------------------------------------------------------------- /src/ast/type_/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod core; 2 | 3 | use ast::helpers::{ 4 | lo_name, spaces_and_newlines, spaces_or_new_lines_and_indent, up_name, Name, IR, 5 | }; 6 | use ast::type_::core::Type; 7 | 8 | use nom::types::CompleteStr; 9 | 10 | named!(type_variable, 11 | map!(re_matches!(r#"^([a-z][a-z1-9_]*)"#), |c| Type::TypeVariable(c[0].to_string())) 12 | ); 13 | 14 | named!(type_constant, 15 | map!( 16 | separated_nonempty_list!( 17 | char!('.'), 18 | up_name 19 | ), 20 | |v| Type::TypeConstructor(v, vec![]) 21 | ) 22 | ); 23 | 24 | named_args!(type_tuple(indentation: u32), 25 | map!( 26 | delimited!( 27 | preceded!(char!('('), opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE))), 28 | separated_list!( 29 | delimited!( 30 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), 31 | char!(','), 32 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)) 33 | ), 34 | call!(type_, indentation) 35 | ), 36 | terminated!(opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), char!(')')) 37 | ), 38 | Type::TypeTuple 39 | ) 40 | ); 41 | 42 | named_args!(type_record_pair(indentation: u32), 43 | do_parse!( 44 | name: lo_name >> 45 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)) >> 46 | char!(':') >> 47 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)) >> 48 | type_annotation: call!(type_annotation, indentation) >> 49 | (name, type_annotation) 50 | ) 51 | ); 52 | 53 | named_args!(type_record_pairs(indentation: u32)>, 54 | separated_list!( 55 | delimited!( 56 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), 57 | char!(','), 58 | opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)) 59 | ), 60 | call!(type_record_pair, indentation) 61 | ) 62 | ); 63 | 64 | named_args!(type_record_constructor(indentation: u32), 65 | delimited!( 66 | char!('{'), 67 | do_parse!( 68 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 69 | var: type_variable >> 70 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 71 | char!('|') >> 72 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 73 | record_pairs: call!(type_record_pairs, indentation) >> 74 | call!(spaces_or_new_lines_and_indent, indentation, IR::GTE) >> 75 | (Type::TypeRecordConstructor(Box::new(var), record_pairs)) 76 | ), 77 | char!('}') 78 | ) 79 | ); 80 | 81 | named_args!(type_record(indentation: u32), 82 | delimited!( 83 | preceded!(char!('{'), opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE))), 84 | map!( 85 | call!(type_record_pairs, indentation), 86 | Type::TypeRecord 87 | ), 88 | terminated!(opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), char!('}')) 89 | ) 90 | ); 91 | 92 | named_args!(type_parameter(indentation: u32), 93 | alt!( 94 | type_variable 95 | | type_constant 96 | | call!(type_record_constructor, indentation) 97 | | call!(type_record, indentation) 98 | | call!(type_tuple, indentation) 99 | | delimited!( 100 | preceded!(char!('('), opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE))), 101 | call!(type_annotation, indentation), 102 | terminated!(opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), char!(')')) 103 | ) 104 | ) 105 | ); 106 | 107 | named_args!(pub type_constructor(indentation: u32), 108 | do_parse!( 109 | first: separated_nonempty_list!( 110 | char!('.'), 111 | up_name 112 | ) >> 113 | second: many0!( 114 | do_parse!( 115 | new_indent: call!(spaces_or_new_lines_and_indent, indentation, IR::GT) >> 116 | type_: call!(type_parameter, new_indent) >> 117 | (type_) 118 | ) 119 | ) >> 120 | (Type::TypeConstructor(first, second)) 121 | ) 122 | ); 123 | 124 | named_args!(pub type_(indentation: u32), 125 | alt!( 126 | call!(type_constructor, indentation) 127 | | type_variable 128 | | call!(type_record_constructor, indentation) 129 | | call!(type_record, indentation) 130 | | call!(type_tuple, indentation) 131 | | delimited!( 132 | preceded!(char!('('), opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE))), 133 | call!(type_annotation, indentation), 134 | terminated!(opt!(call!(spaces_or_new_lines_and_indent, indentation, IR::GTE)), char!(')')) 135 | ) 136 | ) 137 | ); 138 | 139 | named_args!(pub type_annotation(indentation: u32), 140 | map_res!( 141 | separated_nonempty_list!( 142 | delimited!(spaces_and_newlines, tag!("->"), spaces_and_newlines), 143 | call!(type_, indentation) 144 | ), 145 | |mut v: Vec| { 146 | if v.is_empty() { 147 | Err("Empty type".to_string()) 148 | } 149 | else if v.len() == 1 { 150 | Ok(v[0].clone()) 151 | } else { 152 | let last = v.pop(); 153 | let second_to_last = v.pop(); 154 | match (second_to_last, last) { 155 | (Some(t1), Some(t2)) => { 156 | let mut app = Type::TypeApplication(Box::new(t1), Box::new(t2)); 157 | for entry in v.iter().rev() { 158 | app = Type::TypeApplication(Box::new(entry.clone()), Box::new(app)) 159 | } 160 | Ok(app) 161 | } 162 | _ => { 163 | Err("Expected at least 2 entries for match".to_string()) 164 | } 165 | } 166 | } 167 | } 168 | ) 169 | ); 170 | 171 | #[cfg(test)] 172 | mod tests { 173 | 174 | use ast::type_::core::Type; 175 | use ast::type_::type_annotation; 176 | use nom::types::CompleteStr; 177 | 178 | fn tvar(name: &str) -> Type { 179 | Type::TypeVariable(name.to_string()) 180 | } 181 | 182 | fn tapp(a: Type, b: Type) -> Type { 183 | Type::TypeApplication(Box::new(a), Box::new(b)) 184 | } 185 | 186 | fn tcon(name: &str, v: Vec) -> Type { 187 | Type::TypeConstructor(vec![name.to_string()], v) 188 | } 189 | 190 | #[test] 191 | fn constant() { 192 | assert_eq!( 193 | type_annotation(CompleteStr("Int"), 0), 194 | Ok(( 195 | CompleteStr(""), 196 | Type::TypeConstructor(vec!["Int".to_string()], vec![]) 197 | )) 198 | ); 199 | } 200 | 201 | #[test] 202 | fn variables() { 203 | assert_eq!( 204 | type_annotation(CompleteStr("a"), 0), 205 | Ok((CompleteStr(""), tvar("a"))) 206 | ); 207 | } 208 | 209 | #[test] 210 | fn variables_with_numbers() { 211 | assert_eq!( 212 | type_annotation(CompleteStr("a1"), 0), 213 | Ok((CompleteStr(""), tvar("a1"))) 214 | ); 215 | } 216 | 217 | #[test] 218 | fn empty_record() { 219 | assert_eq!( 220 | type_annotation(CompleteStr("{}"), 0), 221 | Ok((CompleteStr(""), Type::TypeRecord(vec![]))) 222 | ); 223 | } 224 | 225 | #[test] 226 | fn empty_tuple() { 227 | assert_eq!( 228 | type_annotation(CompleteStr("()"), 0), 229 | Ok((CompleteStr(""), Type::TypeTuple(vec![]))) 230 | ); 231 | } 232 | 233 | #[test] 234 | fn basic_record() { 235 | assert_eq!( 236 | type_annotation(CompleteStr("{ a : String }"), 0), 237 | Ok(( 238 | CompleteStr(""), 239 | Type::TypeRecord(vec![("a".to_string(), tcon("String", vec![]))]) 240 | )) 241 | ); 242 | } 243 | 244 | #[test] 245 | fn two_entry_record() { 246 | assert_eq!( 247 | type_annotation(CompleteStr("{ a : String, b : String }"), 0), 248 | Ok(( 249 | CompleteStr(""), 250 | Type::TypeRecord(vec![ 251 | ("a".to_string(), tcon("String", vec![])), 252 | ("b".to_string(), tcon("String", vec![])), 253 | ]) 254 | )) 255 | ); 256 | } 257 | 258 | #[test] 259 | fn multi_line_record() { 260 | assert_eq!( 261 | type_annotation( 262 | CompleteStr( 263 | "{ 264 | a : String 265 | , b : String 266 | }" 267 | ), 268 | 0 269 | ), 270 | Ok(( 271 | CompleteStr(""), 272 | Type::TypeRecord(vec![ 273 | ("a".to_string(), tcon("String", vec![])), 274 | ("b".to_string(), tcon("String", vec![])), 275 | ]) 276 | )) 277 | ); 278 | } 279 | 280 | #[test] 281 | fn multi_line_record_with_comments() { 282 | assert_eq!( 283 | type_annotation( 284 | CompleteStr( 285 | "{ 286 | -- First comment 287 | a : String 288 | 289 | -- Second comment 290 | , b : String -- Third comment 291 | }" 292 | ), 293 | 0 294 | ), 295 | Ok(( 296 | CompleteStr(""), 297 | Type::TypeRecord(vec![ 298 | ("a".to_string(), tcon("String", vec![])), 299 | ("b".to_string(), tcon("String", vec![])), 300 | ]) 301 | )) 302 | ); 303 | } 304 | 305 | #[test] 306 | fn list_of_tuples() { 307 | assert_eq!( 308 | type_annotation(CompleteStr("List ( String, String )"), 0), 309 | Ok(( 310 | CompleteStr(""), 311 | tcon( 312 | "List", 313 | vec![Type::TypeTuple(vec![ 314 | tcon("String", vec![]), 315 | tcon("String", vec![]), 316 | ])] 317 | ) 318 | )) 319 | ); 320 | } 321 | 322 | #[test] 323 | fn application() { 324 | assert_eq!( 325 | type_annotation(CompleteStr("a -> b"), 0), 326 | Ok((CompleteStr(""), tapp(tvar("a"), tvar("b")))) 327 | ); 328 | } 329 | 330 | #[test] 331 | fn application_associativity() { 332 | assert_eq!( 333 | type_annotation(CompleteStr("a -> b -> c"), 0), 334 | Ok((CompleteStr(""), tapp(tvar("a"), tapp(tvar("b"), tvar("c"))))) 335 | ); 336 | } 337 | 338 | #[test] 339 | fn application_parens() { 340 | assert_eq!( 341 | type_annotation(CompleteStr("(a -> b) -> c"), 0), 342 | Ok((CompleteStr(""), tapp(tapp(tvar("a"), tvar("b")), tvar("c")))) 343 | ); 344 | } 345 | 346 | #[test] 347 | fn qualified_types() { 348 | assert_eq!( 349 | type_annotation(CompleteStr("Html.App Msg"), 0), 350 | Ok(( 351 | CompleteStr(""), 352 | Type::TypeConstructor( 353 | vec!["Html".to_string(), "App".to_string()], 354 | vec![Type::TypeConstructor(vec!["Msg".to_string()], vec![])] 355 | ) 356 | )) 357 | ); 358 | } 359 | 360 | #[test] 361 | fn multi_line_type() { 362 | assert_eq!( 363 | type_annotation( 364 | CompleteStr( 365 | "CssModules.Helpers 366 | { a : String 367 | , b : String 368 | } 369 | msg" 370 | ), 371 | 0 372 | ), 373 | Ok(( 374 | CompleteStr(""), 375 | Type::TypeConstructor( 376 | vec!["CssModules".to_string(), "Helpers".to_string()], 377 | vec![ 378 | Type::TypeRecord(vec![ 379 | ( 380 | "a".to_string(), 381 | Type::TypeConstructor(vec!["String".to_string()], vec![]), 382 | ), 383 | ( 384 | "b".to_string(), 385 | Type::TypeConstructor(vec!["String".to_string()], vec![]), 386 | ), 387 | ]), 388 | Type::TypeVariable("msg".to_string()), 389 | ] 390 | ) 391 | )) 392 | ); 393 | } 394 | 395 | #[test] 396 | fn can_parse_simple_type_record_constructor() { 397 | assert_eq!( 398 | type_annotation(CompleteStr("{ b | commonErrorName : a }"), 0), 399 | Ok(( 400 | CompleteStr(""), 401 | Type::TypeRecordConstructor( 402 | Box::new(tvar("b")), 403 | vec![("commonErrorName".to_string(), tvar("a"))] 404 | ) 405 | )) 406 | ); 407 | } 408 | 409 | #[test] 410 | fn can_parse_multi_line_type_record_constructor() { 411 | assert_eq!( 412 | type_annotation( 413 | CompleteStr( 414 | "{ b 415 | | commonErrorName : a 416 | , commonErrorDescription : a 417 | , kpiErrorCalculationType : a 418 | , kpiErrorAggregationType : a 419 | }" 420 | ), 421 | 0 422 | ), 423 | Ok(( 424 | CompleteStr(""), 425 | Type::TypeRecordConstructor( 426 | Box::new(tvar("b")), 427 | vec![ 428 | ("commonErrorName".to_string(), tvar("a")), 429 | ("commonErrorDescription".to_string(), tvar("a")), 430 | ("kpiErrorCalculationType".to_string(), tvar("a")), 431 | ("kpiErrorAggregationType".to_string(), tvar("a")), 432 | ] 433 | ) 434 | )) 435 | ); 436 | } 437 | 438 | #[test] 439 | fn can_parse_multi_line_type_record_constructor_in_function() { 440 | assert_eq!( 441 | type_annotation( 442 | CompleteStr( 443 | "({ b 444 | | commonErrorName : a 445 | , commonErrorDescription : a 446 | , kpiErrorCalculationType : a 447 | , kpiErrorAggregationType : a 448 | } 449 | -> a 450 | ) 451 | -> String" 452 | ), 453 | 0 454 | ), 455 | Ok(( 456 | CompleteStr(""), 457 | tapp( 458 | tapp( 459 | Type::TypeRecordConstructor( 460 | Box::new(tvar("b")), 461 | vec![ 462 | ("commonErrorName".to_string(), tvar("a")), 463 | ("commonErrorDescription".to_string(), tvar("a")), 464 | ("kpiErrorCalculationType".to_string(), tvar("a")), 465 | ("kpiErrorAggregationType".to_string(), tvar("a")), 466 | ] 467 | ), 468 | tvar("a") 469 | ), 470 | tcon("String", vec![]) 471 | ) 472 | )) 473 | ); 474 | } 475 | 476 | #[test] 477 | fn multi_line_with_parens() { 478 | assert_eq!( 479 | type_annotation( 480 | CompleteStr( 481 | "List 482 | ({ viewReportPage : String } -> String 483 | )" 484 | ), 485 | 0 486 | ), 487 | Ok(( 488 | CompleteStr(""), 489 | tcon( 490 | "List", 491 | vec![tapp( 492 | Type::TypeRecord(vec![( 493 | "viewReportPage".to_string(), 494 | tcon("String", vec![]), 495 | )]), 496 | tcon("String", vec![]), 497 | )] 498 | ) 499 | )) 500 | ); 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /src/check.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nom; 3 | extern crate regex; 4 | 5 | use nom::types::CompleteStr; 6 | use std::env; 7 | use std::fs::File; 8 | use std::io::prelude::*; 9 | 10 | mod ast; 11 | 12 | use ast::{file, ExportSet, Statement}; 13 | 14 | fn get_export_set_imports(export_set: &ast::ExportSet) -> Vec { 15 | match export_set { 16 | ExportSet::SubsetExport(sub_sets) => { 17 | sub_sets.iter().flat_map(get_export_set_imports).collect() 18 | } 19 | ExportSet::FunctionExport(name) => vec![name.clone()], 20 | _ => vec![], 21 | } 22 | } 23 | 24 | fn get_imported_names(ast: &[ast::Statement]) -> Vec { 25 | ast.iter() 26 | .flat_map(|statement| match statement { 27 | Statement::ImportStatement(_, _, Some(export_sets)) => { 28 | get_export_set_imports(export_sets) 29 | } 30 | _ => vec![], 31 | }) 32 | .collect() 33 | } 34 | 35 | fn main() { 36 | let args: Vec = env::args().collect(); 37 | 38 | if args.len() < 2 { 39 | println!("Usage: {} [elm file to check]", args[0]); 40 | std::process::exit(1); 41 | } 42 | 43 | let filename = &args[1]; 44 | 45 | let mut f = File::open(filename).expect("file not found"); 46 | 47 | let mut contents = String::new(); 48 | f.read_to_string(&mut contents) 49 | .expect("something went wrong reading the file"); 50 | 51 | let result = file(CompleteStr(&contents)); 52 | 53 | match result { 54 | Ok((_, ast)) => { 55 | let imported_names = get_imported_names(&ast); 56 | println!("{:#?}", imported_names) 57 | } 58 | Err(error) => println!("{:#?}", error), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/dump.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nom; 3 | extern crate clap; 4 | extern crate regex; 5 | extern crate walkdir; 6 | 7 | use clap::{App, Arg}; 8 | use nom::types::CompleteStr; 9 | use walkdir::WalkDir; 10 | 11 | use std::env; 12 | use std::ffi::OsStr; 13 | use std::fs::File; 14 | use std::io::prelude::*; 15 | 16 | mod ast; 17 | 18 | use ast::file; 19 | 20 | fn dump_file(filename: &str, quiet: bool) { 21 | let mut f = File::open(filename).expect("file not found"); 22 | 23 | let mut contents = String::new(); 24 | f.read_to_string(&mut contents) 25 | .expect("something went wrong reading the file"); 26 | 27 | let result = file(CompleteStr(&contents)); 28 | 29 | match result { 30 | Ok((_, ast)) => { 31 | if quiet { 32 | println!("{:?} parsed successfully", filename) 33 | } else { 34 | println!("{:#?}", ast) 35 | } 36 | } 37 | Err(error) => { 38 | if quiet { 39 | println!("{:?} failed to parse", filename) 40 | } else { 41 | println!("{:#?}", error) 42 | } 43 | } 44 | } 45 | } 46 | 47 | fn dump_directory(path: &str, quiet: bool) -> walkdir::Result<()> { 48 | for entry in WalkDir::new(path) { 49 | let ent = entry?; 50 | 51 | if ent.path().is_dir() { 52 | continue; 53 | } 54 | 55 | if ent.path().extension() != Some(OsStr::new("elm")) { 56 | continue; 57 | } 58 | 59 | match ent.path().to_str() { 60 | Some(path) => dump_file(path, quiet), 61 | None => {} 62 | } 63 | } 64 | 65 | Ok(()) 66 | } 67 | 68 | fn main() { 69 | let matches = App::new("elm-parser-dump") 70 | .version("0.1") 71 | .arg(Arg::with_name("quiet").short("q").long("quiet")) 72 | .arg(Arg::with_name("path").index(1)) 73 | .get_matches(); 74 | 75 | let args: Vec = env::args().collect(); 76 | 77 | match matches.value_of("path") { 78 | Some(path) => { 79 | let quiet = matches.is_present("quiet"); 80 | 81 | let attr = std::fs::metadata(path).expect("file not found"); 82 | 83 | if attr.is_dir() { 84 | dump_directory(path, quiet); 85 | } else { 86 | dump_file(path, quiet); 87 | } 88 | } 89 | None => {} 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nom; 3 | extern crate regex; 4 | 5 | mod ast; 6 | --------------------------------------------------------------------------------