1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
# 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 <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
|