aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormryouse2022-07-30 01:02:43 +0000
committermryouse2022-07-30 01:02:43 +0000
commit1e2e0a307ee515ddf4a6de4012c02f0e8ef6d3f4 (patch)
tree22873fbd82ee7539c9c19cafaea7f08303502b6e
parent85298ce8bf9ce54da5d0918c9267e59431acd3ee (diff)
flesh out README
-rw-r--r--README.md360
1 files changed, 216 insertions, 144 deletions
diff --git a/README.md b/README.md
index 980cd0f..2093aa3 100644
--- a/README.md
+++ b/README.md
@@ -1,152 +1,224 @@
# neb
### an attempt at a language
-## ideas
- - **Lisp-y**: I hope you like parentheses!
- - **Strongly typed**: types are Good, and could enable future compilation
- - **We <3 Linux**: strong support for pipelines and shell-ing out
- - **Immutable variables**: mutability is scary and makes for strange bugs
- - **Pure functions**: side effects are also scary
-
-## things that (hopefully) work
-
-### housekeeping
- - TODO `(exit [[status :int]]) => :bool`
-
-### io
- - `(input [prompt :string]) => :string`
- - `(print [out :string]) => :bool ; print to stdout`
-
-### file system
- - `(exists? [filename :string]) => :bool`
- - `(glob [regex :string]) => :list`
- - `(read-lines [filename :string]) => :list`
- - `(unlink [filename :string]) => :list ; returns empty list on success`
- - `(with-write [filename :string] [[many :expr]]) => :any ; close file after block ends`
- - `(write [content :string] [file :handle(?)]) => :list ; not yet sure how to handle, uh, handles`
-
-### comparison and boolean
- - `(and [arg :bool] [many :bool]) => :bool`
- - `(or [arg :bool] [many :bool]) => :bool`
- - `(eq? [arg1 :literal] [arg2 :literal]) => :bool`
- - `(> [left :number] [right :number]) => :bool`
- - `(>= [left :number] [right :number]) => :bool`
- - `(< [left :number] [right :number]) => :bool`
- - `(<= [left :number] [right :number]) => :bool`
- - `(not [arg :bool]) => :bool`
-
-### math
- - `(+ [arg :number] [many :number]) => :number`
- - `(- [arg :number] [many :number]) => :number`
- - `(* [arg :number] [many :number]) => :number`
- - `(/ [num :number] [denom :number]) => :number`
-
-### string
- - `(first-char [arg :string]) => :string`
- - `(concat [arg :string] [many :string]) => :string`
- - `(join [arg :list] [join-val :string]) => :string`
- - `(newline) => :string`
- - `(rest-char [arg :string]) => :string`
- - `(split [arg :string] [split-val :string]) => :list`
- - `(strip [arg :string]) => :string`
-
-### flow control
- - `(if [cond :bool] [t-branch :any|:expr] [[f-branch :any|:expr]]) => :any`
- - `(for-count [count :int] [many :expr]) => :any ; creates 'idx' variable with loop count`
- - `(for-each [items :list] [many :expr]) => :any ; creates '_item_' variable with current item`
- - `(| [first :expr] [many :expr]) => :any ; creates 'items' variable`
- - `(branch ([cond1 :bool] [expr1 :any]) [([condN: :bool] [exprN :any])]) => :any`
- - `(block [expr1 :any] ... [exprN :any]) => :any`
- - `(while [cond :bool] [many :expr]) => :any`
-
-### type checking
- - TODO `(string? [arg :any]) => :bool`
- - TODO `(int? [arg :any]) => :bool`
- - TODO `(float? [arg :any]) => :bool`
- - TODO `(number? [arg :any]) => :bool ; returns #true for :int or :float`
- - TODO `(bool? [arg :any]) => :bool`
- - `(list? [arg :any]) => :bool`
-
-### type conversion
- - TODO `(int->string [arg :int]) => :string`
- - TODO `(float->string [arg :float]) => :string`
- - TODO `(number->string [arg :number]) => :string`
- - TODO `(bool->string [arg :bool]) => :string`
- - `(->string [arg :literal]) => :string ; works for all literals`
- - `(string->int [arg :string]) => :int`
+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
- - `(def [name :string] [value :expr]) => ? ; lexically scoped`
- - `(redef [name :string] [value :expr]) => ? ; must already be defined`
-
-### shell
- - `($ [command :string]) => :list ; doesn't support pipes, first item is return code`
- - TODO `($| [commands :list]) => :bool ; pipes together multiple shell commands`
-
-### functions
- - `(lambda (args) (expr1) (expr2) ... (exprN)) => :any`
- - `(func name (args) (expr1) (expr2) ... (exprN)) => :any`
-
-### lists
- - `(append [arg :list] [value :any]) => :list`
- - `(empty? [arg :list]) => :bool`
- - `(first [arg :list]) => :any ; car`
- - `(in? [candidate :literal] [target :list]) => :bool`
- - `(last [arg :list]) => :any`
- - `(list [arg :any] [many :any]) => :list`
- - `(list-length [arg :list]) => :int`
- - `(list-reverse [arg :list]) => :int`
- - `(remove [arg :list] [key :literal]) => :list ; this is actually for records`
- - `(rest [arg :list]) => :any ; cdr`
- - `(shuf [arg :list]) => :list ; shuffle the elements of the list`
- - `(slice [arg :list] [start-idx :int] [[length :int]]) => :list`
- - `(zip [arg1 :list] [arg2 :list]) => :list`
-
-### "higher order"
- - `(apply [func :symbol] [target :list]) => :any`
- - `(map [func :symbol] [target :list]) => :list`
-
-### other
- - pretty much nothing else
-
-## TODO (this section may be incomplete)
-
-### types
-- [ ] revisit type system when i understand *gestures broadly*
-
-### parsing
-- [x] build an AST like a real person (sorta?)
-
-### math
-- [x] division (float)
-- [x] division (int)
-- [ ] mod (int)
-- [ ] exponent
-
-### strings
-- [x] concat
-- [ ] substring
-- [ ] lower/uppercase
-
-### flow control
-- [x] if
-- [x] if with empty else
-- [x] branch
-- [x] pipe
+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
-- [x] lex
-- [x] parse
-- [x] evaluate
-
-### other structure things
-- [ ] generic struct?
-- [ ] dict?
+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}`.
-### symbols
-- [x] define
-- [x] access
-
-### shell
-- [x] call out
-- [x] pipe
+### 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 <name> [<return type>] ([<arg> [<arg type>]]*) <expressions>)`
+
+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