-
Notifications
You must be signed in to change notification settings - Fork 0
Home
The word block you will hear very often in any Novika conversation.
Blocks are primary in Novika the Set of Ideas.
Novika the Language is a convenient way to write these ideas down, in a human-readable form, and to parse and manipulate them.
Finally, Novika the Interpreter is the expert on bringing blocks and all sorts of other ideas to life, on a personal computer. The interpreter makes these ideas useful for someone who does programming rather than simply discusses it.
These three components of Novika The Project are not separate. They interact with each other. They "vote" on ideas and propose new ones to reach project-wide equilibrium, filling conceptual gaps or gaps in the machinery as the project progresses.
Long time ago, there was no such thing as a stack in Novika the Set of Ideas. Then came my decision to use the postfix notation for Novika the Language.
The most convenient and popular way to work with the postfix notation is to combine it with a data structure called stack. Shortly thereafter, I introduced and implemented stacks in Novika the Interpreter.
The more and more I worked on stacks, the more footprint they left on Novika the Set of Ideas. They generated idea after idea, further influencing and balancing the other parts of Novika the Project.
This is only a single example of the interactions between the three parts of Novika. There are many more, although I personally might not be aware of them.
In my opinion, the best way to compare Novika to other languages is to compare it component-wise.
- Novika the Set of Ideas intersects heavily with JavaScript's.
- Novika the Language is similar to that of most Lisp dialects, Forth, and Factor.
- Novika the Interpreter... well, it's just a pretty naïve interpreter. No other mainstream language I know of has such a crappy interpreter :)
I find it very hard to describe Novika linearly, from the bottom to the very top. The reason is that many concepts are recursive, or assume the reader knows about something that will only become explainable later on.
What this means for you personally is that you should skim over terms you do not understand. By "skim" I mean you should mark them as "variables" or "holes" to fill in with knowledge later. These "holes" are constrained by the paragraphs around them. The more you read, the more constraints you collect. With enough constraints, you should be able to understand the terms.
In other words, you should try to infer the words that you do not understand while reading rather than trying to get them the first time.
What blocks do is they glue forms together.
-
One way to glue two forms, let's say A and B, is to connect them in a (unidirectional) association relationship, as in hash tables. What I mean by this is that when the A form is seen, the B form is assumed.
-
Another way to glue two forms is to create a sequence, for example by saying that the A form precedes the B form (and/or that the B form follows the A form).
The tape of a block glues forms sequentially and bidirectionally. This is expressed by simply listing the forms — one after another, separated by whitespace, as in A B
or 1 2 3
.
The tape also offers a "cutpoint" or insertion point called the cursor, and in most Novika words it is abbreviated as simply |
. You can move the cursor left and right programmatically, similarly to how you can do it in a textbox or an HTML input
element. Although things do get a lot more complex and high-level with words like |afterOrToEnd
and |hydrate
!
The dictionary of a block allows to create association relationships between forms. The individual associations (as in "when you see A, assume B") are called dictionary entries, or entries for short.
There are two kinds of dictionary entries — and therefore, two kinds of association relationships.
The first kind of association relationships is the opens relationship. A dictionary entry that has its two forms in an opens relationship is called an opener entry.
Opener entries are generally created using the word @:
. Try typing help @:
in the REPL.
The second kind of association relationships is the pushes relationship. A dictionary entry that has its two forms in an pushes relationship is called a pusher entry.
Pusher entries are generally created using the word $:
. Try typing help $:
in the REPL.
Sometimes it is useful to change the B form in an association relationship. That is, in the phrase "when you see A, assume B" we may want to replace B with C. In the context of Novika and dictionary entries, this is called submission to an entry. Note that the kind of association relationship is not changed, what changes is only the B or the right-hand side of it.
The word =:
is generally used to submit to an entry. Try typing help =:
in the REPL.
Form is a generic name (and a kind of supertype) given to a suite of data types and literals provided by Novika. Note that there is no distinction between parse-time and runtime in Novika. This is to say a form is a literal and an AST node as well as a data type that you operate on at runtime.
Blocks are also forms, making it possible to glue blocks to blocks in sequential and/or associative relationships as described above, by turning to higher and higher-order blocks.
In fact, there are two kinds of forms: value forms, and blocks. Let's take a look at the kinds of value forms present in Novika.
Decimal is the main and very high-level number type. There are no floats, no doubles, no integers and no bytes in Novika. In exchange for performance, there is only the decimal type. It allows you to express arbitrarily large or small numbers and do precise computations on them, effortlessly.
>>> 1 2 +
[ 3 ]
>>> 2 2 * 2 +
[ 3 6 ]
>>> 0.1 0.2 +
[ 3 6 0.3 ]
>>> *
[ 3 1.8 ]
>>> +
[ 4.8 ]
Quotes are the strings of Novika. They are named so because they are essentially raw, uninterpreted (although this isn't exactly true) quotations of the source code.
>>> 'Hello World!'
[ 'Hello World!' ]
>>> ' Hello from Novika!'
[ 'Hello World!' ' Hello from Novika!' ]
>>> ~
[ 'Hello World! Hello from Novika!' ]
>>> echo
Hello World! Hello from Novika!
[ ]
Booleans are probably the simplest kind of form. Normally written as true
or false
, Novika booleans stand for whether an expression is truthy or falsey
>>> [] empty?
[ true ]
>>> [1 2 3] all: even?
[ true false ]
>>> and
[ false ]
Exactly what the name suggests, color forms represent RGBA colors. The alpha component is often omitted to get an opaque color. Various color and color space conversion words exist that take and/or leave color forms, for instance 0 255 0 rgb getLCH
.
Builtins are native code as seen from Novika. They implement performance-critical functionality or functionality that cannot be implemented in Novika directly. For example, +
is a builtin. You can check this for yourself by typing #+ here
in the REPL. You will see that it's [native code for: '+']
. You can follow with typedesc
if you're still unsure: it will confirm that it's a builtin
after all.
>>> 1 2 + echo
3
[ ]
>>> "Hmm, I wonder how + is implemented..."
>>> #+ here
[ [native code for: '+'] ]
>>> typedesc
[ 'builtin' ]
If you open any Novika source file, almost everything you will see there is words and block brackets []
. Words are the forms which most often participate as the left-hand side in association relationships. That is, in the phrase "when you see the A form, assume the B form", the A form is most probably a word form (and the B form is most probably a block, by the way).
Words are just sequences of characters. Almost any characters, in fact, other than whitespace. For instance, swap
, 1AmA-zuůk
, ±
or even {foobra=zoobra}
are all single words.
During parsing, word is the fallback literal. That is, instead of getting parse errors or something like that, you will get words. A missing (or misplaced) whitespace, wrong kind of character — anything otherwise unrecognizable by the Novika classifier (or scissors) becomes a word.
When a block is opened, all words in it are consecutively opened as well.
To counter that, you may want to quote your word. Unless you have to quote at runtime (which is possible, of course), you'll find the quoted word literal useful.
For example, in #+ here
we must quote the word +
because we don't want to add there (which would be the case were the +
word opened without the #
which quotes it). Rather, we want to refer to the word — so that perhaps whomever we give it to (here
in our case) can use it somehow, for instance to read the associated ("B") form as here
does.
In other words, by using #
we talk about words as symbols rather than words as things that they stand for.
Note that quoted words do not keep track of the block the resulting word exists in. They are simply literals, that's it. Giving the quoted word #foobra
to another block may end up with a different meaning for the word foobra
were the recipient block resolve it.
The amount of #
(depth of quotation) is not limited in any way. That is, ##foobra
, when opened, will become #foobra
. On the next open
it in turn will become the word literal foobra
.
Words obtained from quoted words cannot be opened using open
. This restriction exists to underline the fact that words are unaware of their block of origin.
To open a word literal at runtime, you must either enclose it in a block and open that block, or use words such as entry:open
or here
which explicitly require or explicitly assume a block where the associated form should be searched for.
The easiest one is here
, but there are some caveats.
>>> 100 $: foobra
[ ]
>>> ##foobra
[ #foobra ]
>>> open
[ foobra ]
>>> open
[ foobra ] "That's it, no more quoting."
>>> here
[ 100 ]