Cymbal
I think I'll name it Cymbal.
Variables
strHello = "Hello World" intFoo = 12 fltPi = 3.1415
References
intFoo = 12 intBar = intFoo intGlorp = &intFoo intFoo = 3 $print intBar intGlorp //12 3
Works the same with strings. They are immutible, so anytime a string changes, so does its reference
strFoo = "Bob" strBar = strFoo strGlorp = &strFoo strFoo = "Jim" $print strBar, strGlorp // Bob Jim
Functions
Functions are represented by $. This is both the function, the return and the execution depending on context.
fooFunc = ${
$print "Hello"
}
foo = $fooFunc //executes
copyFF = fooFunc //copies
Returns
To return from a function, you assign the return value to $
myName = ${
$ = "Adam"
}
$print $myName //prints Adam
Arguments
sq = x ${
$ = x*x
}
$print $sq 10
Multiple Returns
sqAndCube = x ${
s = x*x
$ = s s*x
}
s c = $sqAndCube 3
$print s //9
$print c //27
Anonymous
In one aspect, all functions are anonymous as they don't have names. In another, it's kind of difficult to do. If you want to define a function and execute it in place.
squareOfThree = $ x ${$ = x*x} 3
This is ugly because it's discouraged. Much better is
sq = x ${
$ = x*x
}
squareOfThree = $sq 3
Types
Types are mostly inferred. Syntax exists to enforce typing, but it should be rarely used. Typing is strong, so if a variable references an int, it can't later be assigned a string. Even function signatures are strongly typed, and if a variable (such as a method) referes to a function, it can only be assigned another function with the same signature.
A function signature has three parts; arguments, returns and if it's a method, it's closure.
Loops
Loops are represented by @. This is both the loop and the break. A loop can execute one expression whose scope will last over all iterations of the loop. The syntax to break out of loop is @=False. Think of it as assigning the loop a continue value. When that value is false, we leave the loop.
Counting to 9
@ i=0 {
@= i<10
$print i
i++
}
Conditionals
The conditional is represened by ?. Unlike functions and loops it is not used in the block.
Basic
? a>3 {
$print "A is bigger than 3"
}
If...Else
? a>3 {
$print "A is bigger than 3"
} ? {
$print "A is less than or equal to 3"
}
If...elif...else
? a>3 {
$print "A is bigger than 3"
}? a<3 {
$print "A is less than 3"
}? {
$print "A is 3"
}
Threads
Threads are represented by ~. When you start a thread, it passes a single channel. This may be a problem, I have to work on it.
printInThread = ~{
@ i=0 {
@ = i<10
$print i
++i
}
}
~printInThread
Note that this could be done as follows to just run the thread annonymously
~~{
@ i=0 {
@= i<10
$print i
i++
}
}
Thread with Channel
yield = n ~{
@ i=0 {
@= i<n
i -> ~
i++
}
}
@ channel = ~yield {
@ = !channel
$print <-channel
}
In this example, ~yeild returns a channel, assigned to channel. The operation !channel returns a bool indicating if the channel has been closed. The channel will automatically close when the thread terminates. A value can also be written to the channel using ~= VAL which will cause the channel to close. The thread will not terminate, but any further attempts to write to the channel will cause an error. A thread can check it's own channel with !~.
Blocking and Non-Blocking
Blocking is simple when you try to write to a channel, execution stops until another thread reads. It also works the other way, if you try to read, you're stuck until someone writes. Blocking read is channel-> and blocking write is channel<-.
Non-Blocking operations will attempt to read or write and if there isn't a waiting thread on the other end, they continue. A non-blocking write is: writeVal => channel If there was no thread waiting to read, the data simply isn't transmitted. This returns a boolean that can be captured: writeSuccess = writeVal => channel
Non-Blocking read: readSuccess = readValue <= channel
Closures
They are supported as in this example used to create an iterator for a loop.
range = n ${
c = -1
$= ${
c++
$ = c<n c
}
}
@ iterator = $range 10 {
@ i = $iterator
print i
}
The value c is closed over.
As objects
Closures are also the way that objects are created. A scope block can be passed around like an object. Values in a scope can be accessed with the dot operator.
bob = {
name = "Bob"
age = 27
occupation = "engineer"
}
$print bob.name
What about privates? I was considering some kind of "access token" system. Not really thought out yet, but it would allow you to cue the compiler as to what has access (and what level of access) to different objects.
My other thoughts are that public and private exist as means of dev-to-dev communication. I don't make something private to prevent later errors - I do it to communicate to anyone using that code that certain values should not be manipulated. How do we setup these communication channels for developers.
Methods
bob = {
name = "Bob"
age = 27
occupation = "engineer"
sayHello = $(){
$print "Hello, my name is " + .name
}
}
Instead of null, should methods return their object by default? This would allow for easy method chaining. How would you do namespaces? If you just use a nested struct, how would access the parent? Would you want to?
Operators
Operators can be defined for objects. An operator is defined by what object it is following. Sadly this means no prefixed operators. Operators are defined like funcitons, only the operator comes after the $ and the arg (must be zero or one) come before.
bob = {
name = "Bob"
age = 27
occupation = "engineer"
years $+{
$ = age+years
}
$++{
.age++
}
}
bob++
$print bob.age //28
$print bob + 10 //38
Order of operations
Operators are executed first by precidence then right to left. Precidence is set .... I don't know how yet.
Interface
Interfaces are used much like Go, however, unlike Go, you should never really need one.
A very generic interface for person would be:
person = {{
name
{{
{{}} $+
$++
}} age
occupation
{{}} $+
$++
}}
This isn't very readable, and it's not really meant to be. For instance that stuff in the middle with a lot of curly braces is saying "age is a variable that has the + operator defined for any object and has the ++ operator and uniary operation". Way too detailed.
A more specific one would be
person = {{
string name
int age
string occupation
int $+
$++
}}
Since we can add floats and ints (though it's no longer commutitive; 5+5.5 = 11 but 5.5 + 5 = 10.5)
Some Tests
List = ${
$ = {
head = $node _
tail = $node _
head.next = tail
tail.prev = head
insert = v ${
i = $node v
tail.prev.next = i
i.next = tail
i.prev = tail.prev
tail.prev = i
}
i $[]{
n = head.next
@c = range i{
@ _ = $c
? n == tail {$ = _ 0}
n = n.next
}
$ = n.val 1
}
}
}
node = v ${
$ = {
prev = _
next = _
val = v
}
}
More Tests
thread = filename text ~{
file = $open filename
buf = int*1024 //this creates a memory block for 1024 ints
@ ch = $chunk text buffer {
file.$write ch
}
"Done" -> ~
}
channel = ~thread "sayHi.txt" "Hello World"
$print <- channel
//use lists and dicts
intToWord = "zero","one","two","three","four"... $print intToWord[2]
letterToFood = "a":"apple", "b":"bannan", "c":"cantaloup"... print letterToFood["a"] //Note that for this example the : operator takes two strings and returns a key value pair. The sequence of keypairs constructs a dictionary
//how do we do this?
open("test.txt).readBuf(100).split("~")
//cuz it would be
open "test.txt" .$readBuf 100 .$split "~"
//Struct
foo = {
x = 10
bar = y $(
$ = ($float .x) * ($float y) // returns a float
)
}
foo.x = 5
print foo.$sq //25
// the sq method will evaluate to Template:Int x ${} float
Function signatures and interfaces?
Node = v ${
$= {
val = v
prev = _ //will this work, can the compiler identify that prev will point to another node?
next = _
disp = ${
? prev != _ { prev.disp }
print val
? next != _ { next.disp }
}
insert = v ${
? v < .val {
? prev == _ {
prev = Node v
}? {
prev.insert v
}
}? v > .val {
? next == _ {
next = Node v
}? {
next.insert v
}
}
}
}
}
Tree = ${
return {
root = null
insert = v $(
root.insert v
)
mappedInsert = m ${
@ i = range(m){
@,v = i
root.insert v
}
}
}
}
tree = Tree tree.insert 3 tree.insert 1 tree.insert 4 tree.insert 5 tree.insert (7,8,9,2)
But how? We could do it with the braces
? bA {
print "A is true"
}? bB {
print "B is true (but not A)"
}? bC {
print "C is true (but not A or B)"
}? bD {
print "D is true (But not A,B or C)"
}? {
print "There is no truth, but this is not so bad"
}
We can just do a look-ahead each time we come out of an if block, if the next thing is another if, then it's actually an else.
${
intsToSquare = 1,4,3,7,6
map(intsToSquare, squareFunc)
}
squareFunc = x ${
<= x*x
}
map = arr, f ${
r = ,
@ i <= range(arr) {
@ , v = i
r += f(v)
}
<= r
}
Notes
When compiled, the anonymous function would be identified as an entry point and started as a thread (in this case, the only one). The commas create an array - the copiler has everything it needs to know that it's creating an [5]int and intsToSquare is now of that type. The map function is called with interface{[int]} and interface{(int)} - anything that can be indexed and anything that can be called. We drop into map. r = (,) sets the scope for r and says that it will be an array. @ starts a loop and i <= range(arr) assigns i to the function returned by range(arr) and it's scope is the lifetime of the loop. range(arr) (not shown here) returns an iterator over arr. Each time i is called it returns a bool (eof) and the next value (or null). @<- sends a signal to the loop - @<-0 is a break statement. r += f(v) passes v into f (squareFunc) and appends it to r. The += operator (also not shown) takes r allocates an array one larger than r, copies r into that space and puts f(v) at the end. <= returns the array r.
Operators:
- !
- %
- ^
- +
- -
- *
- /
- <
- >
- |
- :
- =
operators are one or two of any of the above. The operator is always defined by the object that comes after it.
Delimiters:
- []
- ""
- ``
Delimiters are any of the above sets
Sequences:
- ,
- ;
Reserved:
- () - overrides order of operation
- {} - creates scope
- . - creates floating point numbers, accesses namespaces and values in structs
- $ - function stuff
- & - pointer stuff
- @ - loops
- ~ - thread
- ? - conditionals
- _ - Null
- \ - comments
How to define operators, delimiters and sequences? Only defined in a struct, definition belongs to the class on the left.
More tests
${
test = $fibb 30
print <-test
}
fibb = x ${
ch = channel
?(x<2) ~{
ch <- 1
}? ~{
ch1 = $fibb x-1
ch2 = $fibb x-2
ch <- (<-ch1 + <-ch2)
}
$= ch
}
I'm not sure if I like the ability to execute the conditional right in a thread.
More Tests
gcd = a b ${
l = $sorted a b
? l[0] == 0 { $= l[1] }
$= $gcd l[1]-l[0] l[0]
}
More Tests
file = open "words.txt" words = file.read.split file.close
number = [ "b":["6","8"], "e":["3"], "f":["4"], "g":["9"], "l":["1"], "o":["0"], "s":["5"], "t":["7"] ]
symbol = {
"a":["@"],
"c":["("],
"d":[")"],
"h":["#"],
"i":["!",":"],
"j":[";"],
"k":["|<"],
"o":["[]"],
"s":["$"],
"t":["+"],
"v":["\/"]
}
count = 0
@{
@= (count < 10)
word = random.choice words
? word.len < 8 continue
hasNumber = hasSymbol = hasCapital = False
password = ""
@ i = range word{
@,letter = i
? letter in number && random.random() >.7 {
password += random.choice(number[letter])
hasNumber = True
}? letter in symbol ^^ random.random()>.7 {
password += random.choice(symbol[letter])
hasSymbol = True
}? random.random()<.3 {
password += letter.capitalize()
hasCapital = True
}? {
password += letter
}
? hasNumber ^^ hasSymbol ^^ hasCapital {
print password
count += 1
}
}
}
More Tests
Prefix or Postfix?
The way I'm leaning I can only choose one. Because if I have TypeA + TypeB And both impliment the + operator (possibly as both a uniary and binary) there is no clear answer as to which I should use.
prefix operators
- ++
- --
- ! (as in not, and very common)
postfix operators
- ++
- --
- ! (as in factorial, only in math)
I think I have to go with prefix only.
The alternative to explicitly state which operators are uniary and which are binary, but I'd rather not.
More Tests
A technical note. Due to the "prefix" decision when
$ = x
or
@ = false
or
~ = i
happens, what actually needs to happen is that the the thing on the left needs to resolve to an address of type -thing-on-right- so that we can do a pointer assignment. Not sure how to do that with the loop since that will then have to trigger a check to execute the loop AFTER the assignment. Also, that's the most important one because I want to maintain the:
@ v = $i
syntax. With $ and ~ when we return that address we can also flip the switch to exit at that point.