Cymbal

From Adam Colton
Jump to navigation Jump to search

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.