Dclang
Powerful & minimalist RPN stack language influenced by dc and forth
Install / Use
/learn @akjmicro/DclangREADME
dclang
TO INSTALL AND SETUP:
-
Have the gcc (Linux) or clang (Mac) compiler on your machine.
-
Install
portmidiand theportmidi.handporttime.hheaders onto your system. -
Copy the appropriate
Makefile.[linux|mac]based on your OS toMakefile:# Linux cp Makefile.linux Makefile # MacOS cp Makefile.mac Makefile# clone the repo and build git clone https://github.com/akjmicro/dclang cd dclang sudo make install # try running the primes example: dclang -i examples/some_primes.dc -
make installputs the executabledclanginto/usr/local/binby default. -
Experiment as you wish with compiler optimizations in the Makefile, particularly with float-point options, since 'dclang' is heavily reliant on them.
-
You'll need to set
DCLANG_LIBSto the location of your dclang source folder.export DCLANG_LIBS=/<this>/<source>/<folder>/<location>/libIf you use the
Makefiledirectivemake install; it will link the libs to/usr/local/dclang/lib, so you'd do:export DCLANG_LIBS=/usr/local/dclang/libYou can add this export statement to your shell (
bash,zsh, etc.) startup script, of course. -
For interaction, it's nice to use 'rlwrap' to get readline line-history:
rlwrap ./dclangOne can also create an alias to
dclangthat usesrlwrap:alias dclang='rlwrap dclang` -
Interested in using an RPN language for MIDI coding? For MIDI instructions, check out the details in
examples/midi/README.md
ABOUT:
dclang is an RPN, stack-based language with near-zero syntax. It is in the
spirit and tradition of forth and the grand ol' RPN calculator dc, which is
oft-found on a UNIX/LINUX system near you! You can think of it as a dialect of
forth, much in the same way scheme is a leaner dialect of lisp. Why dclang
and not gforth? For the same reasons one would choose scheme instead of lisp!
Smaller, easier to learn, in some ways, better in terms of usability and syntactical
and naming improvements. I wanted to take what I like about forth, shave off what I
didn't like, and make a more user-friendly idealized version of forth -- one that forth
folks would recognize, but also would perhaps be friendlier to new users.
There are two constant goals of dclang:
- to present hackers with a USABLE tool that they will enjoy!
- to create a lean, performant tool that utterly smokes most interpreted languages.
I do not want to get stuck in exploring CS theory (although that
is respectable and interesting) so much that I have a "Turing Tarpit Tool" that
does nothing. dclang is slowly gathering features that means you can use it like
you'd use python, bash, gforth, etc...and I have an eye to be guided by some
of the key "daily use" functionality that is for instance offered by glibc in C.
In fact, you might say that I'll know dclang is really done when every (or almost every)
aspect/feature of glibc is somehow reflected in the in-built capabilities of dclang.
RPN means "Reverse Polish Notation". That means everything uses a
'point-free-form', and there are no parenthesis, since there is a completely
level order of operation. Words operate on stack operands immediately,
and leave the result on the stack immediately. This makes the
interpreter/parser not only simple but faster than one that has to do
computational gymnastics around parsing things like braces or parenthesis,
etc. It also saves memory, since you don't have runaway linked-list
creation that you have to later garbage-collect. All actions happen on the
stack. Like forth, this is not and never will be a garbage collected
language, but there will be operations to create variables and other data
structures like lists and hashes (dictionaries) and so on, but they will be
manually destroyed in memory to make room for other structures with other
keywords ('free'). No garbage collection means things are kept simple, and
the programmer is assumed to be a thoughtful and responsible adult. :)
forth is a great language, and I mean to follow that lead, even as I simplify
certain aspects of the forth standard in this dialect.
The trade-off for that simplicity is that one has to get used to how order of
operations work in this world (everything being immediate and w/o parenthesis).
And also, one has to get used to manipulating the stack such that defined words
make sensible, efficient use of the stack. It takes some getting used to. I direct
the user to the internet or books to search for things relating to the fine art
of programming forth, etc. Everything said there applies here.
Anyway, due to RPN, things will look like this, when you do math:
4 5 + .
9
20 5 / .
4
0.523 sin .
0.4994813555186418
3 2.54 pow .
16.28875859622752
1 2 3 5 + 7 16 / .s
<4> 1 2 8 0.4375
# a function!
: testif 1 if "true" else "false" endif print cr ;
testif
true
# times/again -- basic, fastest loop type, starts at zero, ascends to cutoff parameter (minus one).
: looptest 7 times i . again ;
looptest
0 1 2 3 4 5 6
# for/next loop, a little slower than basic 'times/again', but gives step options.
# Parameters are to/from/step.
# Let's add the first 20 million integers!
: for_test 0
20000001 1 1 for
i +
next . cr ;
for_test
200000010000000
# this is a comment
"This is a string!" print
This is a string!
# create a variable and store a value at it:
var mynum
4.321 3 / mynum !
mynum @ .
1.3773333333333333
# low-level approach to do the same -- store a value at slot 11:
1.15123 11 !
0 @ .
1.15123
Notice the '.' character, which pops/prints the top-of-stack (TOS). This comes
from forth, as does '.s', which non-destructively shows the stack contents.
This is different from 'dc', where 'p' pops/prints the TOS.
In the looping examples, the block has access to up to 3 hidden variables, 'i', 'j', and 'k' which you can use to test conditionally and escape the loop. This allows nested loops up to three counters deep. Going any futher is a code-smell anyway, and you should refactor to a different implementation if you need something more.
The user is encouraged to preuse the examples/ and tests/ directory to
get a feel for the style and syntax of dclang. When you first launch an
interactive commandline session by typing dclang (or rlwrap dclang to
enable comand history, if you have rlwrap), you will see a categorized
listing of all the primitives. You can always see the list again by
calling the primitives word, which will show the info listing again.
A non-exhaustive notes on some of the things implemented thus far:
- Math:
+,-,*,/,%,<<,>>abs,min,max,round,ceil,floor(float-versions only)pow,sqrt,log,log2,log10(float-versions only)sin,cos,tan,pi,e(float-versions only)rand(float-versions only)
- Logic:
and,or,not,xor=,<>,>,<,>=,<=
- Stack operations:
drop,dup,over,swap,pick,2drop,2dup,2oversvpush,svpop,svdrop,svpickdepth,clear,svdepth,svclear
- Control structures:
if-else-endiftimes/again;for/next(looping)- user-defined words (functions)
- Strings:
- simple string printing w/
print - fancier right-justified numeric output fields:
.rj strtok,mempcpy,memset,mkbuf,free- strong comparison with:
strlen,str=,str<,str> - find a substring with
strfind - '#' to end-of-line for comments
uemit, a unicode-character emitter which can help to contruct strings that need them.- convert character bytes to equivalent numerical value with
ord - convert integers to hex-string with
tohex. isalnum,isalpha,iscntrl,isdigit,isgraph,islower,isprintispunct,isspace,isupper,isxdigit-- all of these can take the integer output fromordand return-1(true) or0(false) for determining the class of a given character. (N.B.: If given a string of len > 1,orduses the first character of the string by default.)- a
regex.dclibrary, which uses the private regex primitives:_regcomp,_regexec, and_regreadin amatchword, giving basic regex functionality.
- simple string printing w/
- Variables/Arrays:
- Declare a constant with
const:1 pi 2 * / const INV2PI - Declare a variable with
var:
orvar myvar
or# This declares _and_ initializes: var myvar 42 myvar !
There is also# Declare a variable and advance the variable pointer such # that the variable owns 16 slots, making it an array. You # are responsible for knowing the bounds of the array yourself. # there are no protections keeping you from writing into neighboring # cells: var myarr 16 allotcreate, which does something similar, but is paired typically with,which is an operator to place a stack value immediately into a storage location. So, to initialize an array of four values to1, you'd do:
Note that the comma operator is an actual operator-word, it's not a delimiter!create myarr 1 , 1 , 1 , 1 , !(poke a value to a given slot, e.g.5 funvar !puts the value 5 intofunvar)@(peek a value, copy it to the stack, e.g.funvar @will put our previously saved '5' onto the top of the stack.- Since the variabl
- Declare a constant with
