├── README.md └── assets └── error.png /README.md: -------------------------------------------------------------------------------- 1 | # Neve 2 | 3 | Neve is a hybrid-paradigm interpreted programming language that favors functional programming. It is designed 4 | to be not only easy, but *fun* to use, while also being predictable and expressive. It is designed to *never crash*. 5 | 6 | I’m writing this as an informal specification for the language because I really wanted to get my thoughts out. I’ve 7 | been spending a couple years coming up with a language that would be fun to use for my non-performance critical 8 | side projects, and that could also hopefully be fun to use for you too! And I’d really like to know what your thoughts 9 | are. If I’ve missed anything or something seems confusing, please kindly let me know! 10 | 11 | And, just in case you’re curious–development started a couple months ago, but it’s been going pretty slowly. 12 | Neve barely supports anything, and I’m working on rewriting the whole bytecode compiler from Python to Kotlin 13 | because Python’s been causing a mess, so I wouldn’t recommend trying to write any programs yourself just yet. If 14 | you still want to check it out, you’re welcome to visit [nevec](https://github.com/neve-lang/nevec) and 15 | [neve](https://github.com/neve-lang/neve). 16 | 17 | # Table of Contents 18 | 19 | * [Core Philosophy](#core-philosophy) 20 | * [Quick Syntactical Overview](#quick-syntactical-overview) 21 | * [Main Features and Semantics](#main-features-and-semantics) 22 | * [Conclusion](#conclusion) 23 | 24 | ## Core Philosophy 25 | 26 | With every new language comes a same question: **why use it over another language**? This essentially comes down 27 | to what the language itself brings, why it was made in the first place, and what makes it unique. This section 28 | will mainly involve words of mouth. 29 | 30 | Neve’s core principles are the following: **expressiveness**, **safety and predictability**, and **awesome DEVX**. 31 | 32 | ### Expressiveness 33 | 34 | Neve tries to achieve expressiveness by **favoring functional programming**. It still supports `while` and `for` loops 35 | due to its hybrid nature, but it makes it clear that functional constructs are preferred wherever possible, by doing 36 | tiny things like [requiring the `for` loop variable to be mutable](#variable-mutability). 37 | 38 | Neve also tries to achieve expressiveness by using what I like to call its **soothing syntax**, but it’s difficult to 39 | hit the mark with syntax–it’s not objective, and it spends a lot of the language’s strangeness budget. But for 40 | Neve, I tried sacrificing some of that strangeness budget to bring something fresh and that isn’t found as often 41 | as in other languages–that is, Lua and Ruby-like syntax. 42 | 43 | ### Safety and Predictability 44 | 45 | Neve values safety and predictability *a lot*, so much so that it intends to be *a language that never crashes*. 46 | Now, of course, Neve *will* crash if you run into fatal issues like a heap corruption error, or if you manually write 47 | a valid Neve bytecode file that does unsafe things. But in a regular setting, your code should *never crash* because of 48 | a runtime error–those don’t exist in Neve, thanks to its [refinement types](#refinement-types) and deep value analysis. 49 | 50 | Now, I understand that those are bold claims to make, and it’s easy to ask yourself, “if Neve plans to 51 | do it, why haven’t other languages already done it?” But my answer to this question is, I think it’s expected to have 52 | new ideas emerge over time. For example, Java is a widely used language, and yet, it didn’t support null safety until Java 53 | 1.8, introducing its `Optional` class. And, most languages just don’t have the goal to be a language that never crashes, or 54 | at least don’t prioritize it. 55 | 56 | And, if you’re still skeptical, I completely understand. This `README` aims to answer every question you might be 57 | asking yourself right now, and if you see an issue I hadn’t caught, please let me know! 58 | 59 | ### Awesome DEVX 60 | 61 | The last of those core points can’t really be proven by language features, and with a compiler and VM still under heavy 62 | development, the best I can show you are Neve’s compiler error messages. So here’s one: 63 | 64 | ![image](assets/error.png) 65 | 66 | ## Quick Syntactical Overview 67 | 68 | In this section, I’ll try to give you a quick taste for Neve’s syntax, without dumping too many language 69 | features at once and without talking about syntax forever. This means that I’ll have to keep the example simple, 70 | but that’s okay. With that said, here’s a simple Neve program: 71 | 72 | ```rb 73 | fun main 74 | let numbers = 0..10 75 | 76 | # Initial numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 77 | puts "Initial numbers: #{numbers}" 78 | 79 | let evens = numbers.filter with Int.is_even 80 | let doubled = evens.map |x| x * 2 81 | 82 | puts "Then: " if doubled.is_empty = "No doubled evens!" else doubled.show 83 | end 84 | ``` 85 | 86 | It’s a silly program, but hopefully, it gives you a clear vision of what Neve aims to achieve. 87 | 88 | A couple things you might’ve noticed: 89 | 90 | * Newlines are significant in Neve. [How are they treated at the parser level?](#newlines-in-neve) 91 | * Parentheses to call a function are optional in Neve. [How does the parser handle that?](#function-calls-with-optional-parentheses-in-neve) 92 | 93 | These questions dive into how the parser and lexer themselves work, so I’ll leave them at the bottom of this 94 | `README`, just in case you’re interested! 95 | 96 | ## Main Features and Semantics 97 | 98 | This section dives into Neve’s essential features and semantics, like immutability, pattern 99 | matching, refinement types, and more. 100 | 101 | ### Variable Mutability 102 | 103 | In Neve, variables are declared using either a `let` keyword or a `var` keyword. À la Swift, `let` is used to specify 104 | immutability, whereas `var` is used for mutable variables. 105 | 106 | ```rb 107 | fun main 108 | let name = "Name" 109 | var age = 20 110 | 111 | # okay: 112 | age += 1 113 | # not okay: 114 | name = "Other name" 115 | end 116 | ``` 117 | 118 | It is also worth noting that `for` loops require mutable variables: 119 | 120 | ```rb 121 | fun main 122 | var i = 0 123 | for i < 10 where i += 1 124 | puts i 125 | end 126 | end 127 | ``` 128 | 129 | ### First-class Functions 130 | 131 | As any other functional programming language, Neve supports first-class functions. However, there’s a problem: as we 132 | mentioned before, Neve allows functions to be called without parentheses, so how is this ambiguity solved? Neve 133 | introduces a `with` keyword that returns the given function as a first-class citizen: 134 | 135 | ```rb 136 | fun is_even(n Int) 137 | n mod 2 == 0 138 | end 139 | 140 | fun main 141 | let numbers = 0..10 142 | 143 | # this fails: there’s missing arguments! 144 | let evens = numbers.filter is_even 145 | # this is okay 146 | let evens = numbers.filter with is_even 147 | end 148 | ``` 149 | 150 | However, it is worth nothing that `with` is unnecessary for anonymous functions; those are treated as first-class citizens 151 | by default. 152 | 153 | ```rb 154 | fun main 155 | let numbers = 0..10 156 | 157 | # this fails! 158 | let evens = numbers.filter with |x| x mod 2 == 0 159 | # this is okay 160 | let evens = numbers.filter |x| x mod 2 == 0 161 | end 162 | ``` 163 | 164 | ### Type System 165 | 166 | Neve features a type system similar to those found in many programming languages, taking a lot inspiration from 167 | Rust’s type system, as well as common and successful approaches to union types. This `README` does not go over every 168 | single type available in Neve’s type system–I’ll try keeping only the most essential ideas without making things confusing. 169 | 170 | #### Records 171 | 172 | Records are the same as the `struct`s you love in other languages. In Neve, a record is declared using the `rec` keyword, 173 | followed by the record’s name, and its fields. 174 | 175 | ```rb 176 | rec Hero 177 | name Str 178 | sword Sword 179 | end 180 | ``` 181 | 182 | Records are constructed using the `with ... end` syntax, where all fields must be instantiated, separated by newlines. 183 | 184 | ```rb 185 | fun main 186 | let sword = Sword.Iron 187 | 188 | let hero = Hero with 189 | name = "Name" 190 | sword # shorthand syntax 191 | end 192 | end 193 | ``` 194 | 195 | Empty records are also valid: 196 | 197 | ```rb 198 | rec Empty 199 | end 200 | 201 | fun main 202 | let empty = Empty with .. 203 | end 204 | ``` 205 | 206 | #### Ideas 207 | 208 | Neve’s `idea`s are analogous to Rust’s `trait`s: there’s basically no difference between them, except for the keyword. 209 | An `idea` is must have associated functions; it can also have associated types, and multiple generic types. 210 | 211 | ```rb 212 | idea Speak 213 | fun speak Str 214 | 215 | fun scream 216 | # immutable self can be implicit 217 | self.speak.uppercase 218 | end 219 | end 220 | ``` 221 | 222 | To let a type share an idea, you use the `idea I for T` syntax, where `I` is the idea, and `T` refers to the type. 223 | 224 | ```rb 225 | rec Dog 226 | end 227 | 228 | idea Speak for Dog 229 | fun speak 230 | "Bark!" 231 | end 232 | end 233 | 234 | fun main 235 | let my_dog = Dog with .. 236 | 237 | # Bark! 238 | puts my_dog.speak 239 | # BARK! 240 | puts my_dog.scream 241 | end 242 | ``` 243 | 244 | You can also use the `idea for T` syntax to attach associated functions to a type without needing an explicit idea. 245 | 246 | ```rb 247 | idea for Dog 248 | fun walk 249 | puts "Walking" 250 | end 251 | end 252 | ``` 253 | 254 | #### Errors and Null Safety in Neve 255 | 256 | Neve takes a very similar approach to Zig and Kotlin’s when it comes to what I like to call *dangerous union types*: those 257 | being optional types and error types. 258 | 259 | ##### Optional Types 260 | 261 | In Neve, a possibly `nil` type is written as `T?`, where `T` refers to the type. For example, retrieving a value from a table 262 | will result in an optional type: 263 | 264 | ```rb 265 | const My 266 | # Table has type [Str: Int] 267 | Table = ["a": 1, "b": 2] 268 | end 269 | 270 | fun main 271 | let my_value = My.Table["x"] 272 | # my_value has type Int? 273 | # NOTE: in this particular example, the compiler will be able to infer 274 | # that my_value is always nil; its type remains an Int?, however. 275 | end 276 | ``` 277 | 278 | Optional types can be assigned freely without needing a type wrapper or typeclass, as long as the assignment is valid: 279 | 280 | ```rb 281 | fun main 282 | var mystery Int? = mystery_fun 283 | 284 | mystery = 10 285 | mystery = nil 286 | end 287 | ``` 288 | 289 | ##### Error Types 290 | 291 | In Neve, errors are treated as values. Neve also provides an *error union type*, written as `T!E`, where `T` is the type and 292 | `E` is the error. 293 | 294 | Notice how it is `T!E` and not `T | E`–that’s because `T | E` doesn’t allow using the `.or` and `.else` associated 295 | functions; which we’ll get to soon, whereas `T!E` does. 296 | 297 | ```rb 298 | fun read(filename Str) 299 | # file has type `File!FileErr` 300 | let file = File.read filename 301 | # ... 302 | end 303 | ``` 304 | 305 | Error types can be defined as follows: 306 | 307 | ```rb 308 | union IsClosedErr for ! 309 | | CameTooEarly 310 | | CameTooLate 311 | end 312 | ``` 313 | 314 | Weird syntax, right? But there’s reasons behind it. I felt like introducing an `err` or `error` keyword would get in 315 | the way of people trying to write `let err = dangerous_fun`. I’ve even considered the silly approach of checking if 316 | the union’s name ends with `Err` or `Error`... In the end, I decided to go with `union E for !`, which shouldn’t be that bad. 317 | 318 | An advantage of *dangerous union types* is that they come bundled with the `.or` and `.else` associated functions, which 319 | don’t show up anywhere else. Normally, `or` and `else` would be considered reserved keywords, but when used with `T?` or 320 | `T!E`, they work just like Rust’s `.unwrap_or()` or `.unwrap_or_else()` functions, respectively. Here’s an example: 321 | 322 | ```rb 323 | union SomeErr for ! 324 | | SomethingWentWrong 325 | end 326 | 327 | fun maybe_nil Int? 328 | random.either for Int? 10, nil 329 | end 330 | 331 | fun maybe_err Int!SomeErr 332 | random.either for Int!SomeErr 10, SomeErr.SomethingWentWrong 333 | end 334 | 335 | fun main 336 | let a = maybe_nil.or 20 337 | let b = maybe_nil.else do 338 | puts "I don’t want a nil!" 339 | 20 340 | end 341 | 342 | let c = maybe_err.or 20 343 | let d = maybe_err.else |e| do 344 | puts "To #{e}: May I ask what went wrong?" 345 | 20 346 | end 347 | end 348 | ``` 349 | 350 | #### Refinement Types 351 | 352 | Refinement types are Neve’s main selling point. In Neve, a refinement type is a type that must fulfill a certain condition 353 | *at compile time*. They’re essential for input validation and are what makes Neve’s goal of being a language that never 354 | crashes possible. 355 | 356 | Refinement types are defined using `let`, followed by the name of the type, and a `where` clause. Here’s an example: 357 | 358 | ```rb 359 | let Nat = Int where self >= 0 360 | ``` 361 | 362 | > Using `let` here isn’t ambiguous, because *variable declarations aren’t allowed at top level code*; only `const` 363 | declarations are. 364 | 365 | This defines a refinement type called `Nat`, which is an integer that *must* be above or equal to zero. `self` can also 366 | be renamed in case of name conflicts: 367 | 368 | ```rb 369 | let Nat = Int where |i| i >= 0 370 | ``` 371 | 372 | Now, **how is this checked at compile time**? Neve plans to implement a **value analysis** phase in the compiler that 373 | runs alongside the type checker; the type checker validates a node, and then hands it over to the **value analyzer** that 374 | gathers just enough information to help out the type checker. Implementing the value analyzer won’t be easy, but it’s 375 | definitely possible. I’ll share the implementation once it’s been proven to work! 376 | 377 | What’s awesome about refinement types, is that they allow us to validate *anything* at compile time, without needing 378 | runtime checks that lead to a crash. For example; say goodbye to the old “index out of bounds” error, because this 379 | prevents it now: 380 | 381 | ```rb 382 | let InBoundsOf(list List) = Nat where self < list.len 383 | 384 | idea Subscript 385 | let Index 386 | let Out 387 | 388 | fun subscript(index Index) Out 389 | end 390 | 391 | idea Subscript for List T 392 | fun subcript(index I) T 393 | with I = InBoundsOf self 394 | alien # C implementation 395 | end 396 | end 397 | ``` 398 | 399 | So now, this: 400 | 401 | ```rb 402 | fun main 403 | let list = 0..10 404 | puts list[5] 405 | end 406 | ``` 407 | 408 | Is okay, because the compiler will be smart enough to know that `5` is proven for `ListIndex 0..10`, but this: 409 | 410 | ```rb 411 | fun main 412 | let list = 0..10 413 | let index Int = fun_with_unknown_return_range 414 | 415 | puts list[index] 416 | end 417 | ``` 418 | 419 | Is not–the compiler cannot prove `ListIndex 0..10` for `index`. This can be solved with an `if` statement, which 420 | alters `index`’s possible values due to **timeline** (just a fancy name for branching scopes, hehe) analysis: 421 | 422 | ```rb 423 | fun main 424 | let list = 0..10 425 | let index Int = fun_with_unknown_return_range 426 | 427 | # syntactical sugar for checking the `where` clause 428 | if index is not InBoundsOf list 429 | puts "Okay, we won’t check list" 430 | return 431 | end 432 | 433 | puts list[index] 434 | end 435 | ``` 436 | 437 | ## Conclusion 438 | 439 | I hope this was an interesting read! I tried to keep this `README` concise enough while still being thorough, and I’d like 440 | to thank you if you’ve read this far! Sincerely, you’re the best. 441 | 442 | I know the whole “never crashing idea” is ambitious, and I definitely expect some skepticism about everything else–this is 443 | all a lot of unproven theory, after all. But I think I’d really appreciate it if you could be supportive, even if you 444 | can’t shake off your skepticism easily. Neve is my passion project, and even if I can’t bring my ideas to life, at least 445 | it’ll have inspired someone else to create something perhaps equally awesome! 446 | 447 | --- 448 | 449 | ### Newlines in Neve 450 | 451 | As mentioned before, newlines in Neve are significant whitespace. But there seems to be a lot of discussion 452 | regarding the best way to implement a parser that supports them. I think Neve’s approach to this problem is interesting, 453 | and felt like sharing it just in case it inspires someone’s approach to the same problem! You’re welcome to skip this section 454 | if you’re not interested. 455 | 456 | Neve’s approach is simple–it ignores any type of whitespace *unless it’s required*. Now, before you roll your eyes: I 457 | promise it’s not just like JavaScript does. Let me explain in detail. 458 | 459 | During the parsing phase, the Neve Lexer skips all kind of whitespace *except* for newlines. For these kinds of characters, 460 | it emits a **newline token** that is passed over to the parser, just like a normal token. 461 | 462 | However, this is where the interesting part happens–because Neve uses a single-pass parser, the parser keeps two tokens 463 | in memory at every state: a `previous` token and a `current` token. They’re pretty straightforward–when the `current` token 464 | changes, the `previous` token is set to `current`’s previous value, and then we continue. However, the parser treats 465 | **newline tokens** a bit differently. Whenever it calls `advance()` or a function that moves the parser’s position forward, 466 | it *skips every newline token*, and once they’re all skipped, the last newline token stays in `previous`, because `current` 467 | just skipped it. This lets the parser skip every newline token it doesn’t need, and it just has to check if the `previous` 468 | token is a newline token to know if there was a newline. This can easily be abstracted away in a tiny `hadNewline()` 469 | function. 470 | 471 | ### Function Calls with Optional Parentheses in Neve 472 | 473 | Optional parentheses are another can of worms, and I thought clarifying how Neve handles that could also be valuable. Same 474 | as before–you’re free to skip this section if you want to. 475 | 476 | The decision to keep parentheses optional when calling a function in Neve makes its syntax ambiguous. For example, take 477 | a look at this tiny snippet: 478 | 479 | ```rb 480 | let seeds = plant 481 | ``` 482 | 483 | Is this a declaration of a variable `seeds` that is assigned the value of some *other variable* named `plant`, or is it 484 | a declaration of a variable `seeds` that is assigned the returned value of some *function* named `plant`, that takes in 485 | no arguments? 486 | 487 | Neve solves this ambiguity **during its semantic resolving** phase, where it takes the original AST and outputs a 488 | corrected version, solving ambiguities that can only be solved if we have extra information about symbols. In this case, 489 | `plant` is considered a simple access of the variable `plant` *if it is, indeed, a variable*, but it will be considered a 490 | function call *if it is a function call*. 491 | 492 | Then, we have less ambiguous function calls: those are function calls that have arguments provided. The Neve parser 493 | considers something a function call if it sees an identifier followed by: 494 | * An **expression starter token** *with no newline token before it*. 495 | * A parenthesis *with no newline token before it*. 496 | 497 | This means that the following will be considered function calls in Neve: 498 | 499 | ```rb 500 | let a = call 501 | let b = call x, y, z 502 | let c = call(x, y, z) 503 | let d = call( 504 | x, 505 | y, 506 | z 507 | ) 508 | let d = some.call.this.and_that(x, y, z) 509 | # ---- ---- -------- will be called if they are associated functions 510 | # (this is done by the semantic resolver, which 511 | # examines the AST and outputs a corrected version.) 512 | ``` 513 | 514 | But the following *won’t*: 515 | 516 | ```rb 517 | # doesn’t capture the arguments 518 | let a = call 519 | ("Hello") 520 | 521 | # the following example is weird: 522 | let a = call var x = 10 523 | # this actually gets parsed as: 524 | let a = call 525 | var x = 10 526 | # because `var` is not an expression starter token. 527 | ``` 528 | 529 | Neve considers the following tokens **expression starters**: 530 | * Opening bracket tokens: `(`, `[`, `|` 531 | * Identifiers 532 | * Strings, integers and floats 533 | * Expression or expression starter keywords: `true`, `false`, `nil`, `not`, `self`, `with` 534 | -------------------------------------------------------------------------------- /assets/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neve-lang/neve-overview/a25957fab7bd8cc23afe0c06528e190ba9abc28c/assets/error.png --------------------------------------------------------------------------------