# neb ### an attempt at a language Neb is a gradually typed language with Lisp-like syntax, with strong influences from both Lisp-y languages and Python. It is a hobby language in the fullest sense of the word -- it is designed and used for the pure pleasure of desiging and using a programming language. It is a playground within which crazy ideas can be dreamt up and implemented. ## high-level goals - **Readability**: neb code should be straightforward to read, both in terms of program clarity, but also in terms of the literal names of things. It should ideally be self-evident what a function does by it's name alone (i.e. while neb is in the Lisp family, it uses `first` and `rest` instead of `car` and `cdr`) - **Self-documenting**: this dovetails off readability, but the neb interpreter should be able to tell the user how to call a function and what it should return, without any extra steps from the programmer. - **Interactive**: neb has a REPL (as most languages do nowadays, and Lisps always have), but ideally the REPL should be able to function as a fully equipped neb programming environment, complete with documentation, tab completion, as well as additional niceties for both writing and inspecting neb code. - **Adaptable**: neb should be easy and flexible enough to write one-off scripts, but also robust enough to support them in their transition from one-offs to small or medium sized programs. ## install ```shell git clone https://git.rawtext.club/neb/neb-python.git cd neb make ``` ## coding in neb ### Hello World! The `print` function in neb takes in a single `:string` as an argument, and prints it to the screen: ```scheme (print "Hello, World!") ; prints "Hello, World!" to the screen ``` ### comments Comments begin with the `;` character, and last until the end of the line. There are no multi-line comments -- each line of the comment must begin with `;`. ### variables You can initiate variables using the `def` keyword: ```scheme (def greeting "Hello") ; defines the symbol 'greeting' (print greeting) ; prints "Hello" to the screen ``` Assign a new value to an existing variable using `redef`. `def` will attempt to create a new variable with the desired name in the current lexical scope, whereas `redef` will update the value of a variable defined in the current lexical scope or parent scopes. ```scheme (def greeting "Hello!") greeting ; "Hello!" (block (def greeting "Goodbye!") ; 'greeting' is in the scope of 'block' greeting) ; "Goodbye!" greeting ; "Hello!" (unchanged) (block (redef greeting "See ya") greeting) ; "See ya" greeting ; "See ya" ``` ### expressions Generally speaking, everything between `()` is an expression in neb, and expressions all evaluate to a value. Expressions that are purely side-effect oriented, and thus don't have a good "value" to return, may return :nil, which is equivalent to an empty list. There is nothing that differentiates :nil from an empty list. Some expressions may wrap multiple expressions, as `block` does above. These expressions evaluate to the value of their last evaluated expression. ### scopes Neb is lexically scoped, so variables refer to their innermost definition, and may be shadowed. A scope can be manually created using the `block` function. Some functions automatically create blocks of scope: - `func` and `lambda` - loop constructs (`for-each`, `while`) - the catch clause of the `try` expression (specifically for a magic variable, see below) Note: statements that branch, such as `if` and, uh, `branch`, do not create their own scope (at this time), as they expect only a single expression as their branch clause. To evaluate multiple expressions within these constructs, use `block`. ### magic variables Neb has a few magic variables to cut down on some code. These may not be around forever. `for-each` sets the magic variable `_item_`, which is the current item in iteration: ```scheme (def list-of-strings (list "Hello" "there" "friends")) (for-each list-of-strings (print _item_)) ; Hello ; there ; friends ``` `for-count` sets the magic variable `_idx_`, the index (one-based) of the iteration: ```scheme (def list-of-strings (list "Hello" "there" "friends")) (for-count (length list-of-strings) (print (->string _idx_))) ; 1 ; 2 ; 3 ``` `try` sets the magic variable `_panic_`, available in the catch clause, which contains a :string with the error message: ```scheme (def list-of-strings (list "Hello" "there" "friends")) (try (+ list-of-strings) ; error (print _panic_)) ; '+' expects a :number, received :[:any] (got ...) ``` #### wait, rewind, one-based indexes? Yep! It's not as much of an adjustment as you would think, especially since if you're using functional constructs, you generally won't care about the index of an element in a list. Zero-based is confusing, and exacerbates off-by-one errors, and is legacy we don't need to perpetuate. Plus, it's nice to start counting at 1. Did I mention this is a space for crazy ideas? ### builtin types Types in neb begin with a `:` character. There are many built-in types: - `:any`: this is the type from which all other types descend. It matches values of all other types, *including types*. - `:literal`: this is the parent of all atomic types, including - `:string`: strings, enclosed with double quotes - `:bool`: boolean, either `#true` or `#false` - `:int`: integers - `:float`: floating point numbers, contains a period - `:number`: a super-type of both `:int` and `:float` - `:handle`: a file handle - `:type`: the type of types ### lists The basic collection type in neb is a list. A list can contain any number of any type of item, including zero. The base type of all lists is `:[:any]`. A list may be defined to contain an inner type -- so, for example, something that conforms to the type `:[:string]` is a list of zero or more elements, each of which is a `:string`. #### nil The `:nil` type is a subtype of `:[:any]`, and the only thing that is of this type is a list with exactly zero elements. #### non-empty lists The counterpart of `:nil` is denoted using curly braces -- something that conforms to the type `:{:string}` is a list of at least one item, where every item is a `:string`. `:[:any]` can be thought of as the type encompassing both `:nil` and `:{:any}`. ### functions Functions are defined using either the `lambda` or `func` keywords -- `func` specifies a named function, and `lambda` an anonymous one. Functions are defined as such: `(func [] ([ []]*) )` This breaks down to: - *name*: the name of the function - *return type*: the return type of the function. This will be enforced in future versions of neb (i.e. if a function evaluates to something incompatable with the return type, an error will be thrown). This is optional. - *arg list*: this is in opening and closing parentheses, even if there are zero arguments. Each argument must have a distinct name, and may optionally be followed by a type. If a type is included, the interpreter will ensure that the value being passed as that argument conforms to the specified type. - *expressions*: one or more expressions to be evaluated. A new scope is created in which the arguments are defined. #### overloading Functions can be overloaded by both type and arity. For example, in the standard library, the function `reverse` is defined in two ways: - `(reverse :[:any]) ; => :[:any]` - `(reverse :string) ; => :string` This is an example of overloading by type -- one implementation of `reverse` works on `:[:any]`, and the other works on `:string`. The interpreter evaluates the argument passed in, and dispatches according to the type of the argument. If a matching implementation is not found, an error is thrown. Another example is `exit`, defined as: - `(exit) ; => :nil` - `(exit :int) ; => :nil` It will accept either zero or one argument, and dispatch accordingly. Built-in functions can be overloaded by user-defined functions, and user-defined functions can be overloaded as well. #### ambiguity In the case where there are multiple defined functions that can satisfy the arguments passed, an error is thrown. An example of this: - `(my-func :[:string]) ; => :[:string]` - `(my-func :[:number]) ; => :[:number]` `my-func` above is ambiguously defined for the case of an empty list, because both `:[:string]` and `:[:number]` are compatible with an empty list (note: this will *only* throw an error when passed an empty list -- if a non-empty list is passed, the function will succeed). To define this unambiguously, define it as such: - `(my-func :{:string}) ; => :{:string}` - `(my-func :{:number}) ; => :{:number}` - `(my-func :nil) ; => :nil` Note: the return types in this example are unimportant. ### truthiness In neb, the only values that evaluate as either True or False are the `:bool` values `#true` and `#false`. So, anything that expects a `:bool` will throw an error if it receives anything else. I only call this out because many languages interpret different values a "truthy" or "falsy", i.e. `while(1)` being an infinite loop, as 1 evaluates "truthy". ## things that don't work, but hopefully will eventually - return type checking - closures - real namespaces - recursively nested types - docstrings for functions