load help; @[name] module.standard.math @[name] @[title] Math Module @[title] @[name] module.standard.coroutine @[name] @[title] Coroutine and Generator @[title] @[text] Coroutine is a generic data type that can turn a routine into coroutine or generator. It has the following type form, @[code] coroutine<@RESUME,@YIELD> @[code] where @[green]@RESUME@[green] is the type holder for the type that can be passed to its @[green]resume()@[green] method, and @[green]@YIELD@[green] is the type holder for the type that can be passed to its @[green]yeild()@[green] method. To create a coroutine that can be resume with a integer value, and yield a string value, the coroutine type should be created in following way, @[code] co = coroutine() @[code] The routine that will be used to create the coroutine need to be defined with the first parameter named @[green]self@[green] with the coroutine type, @[code] routine Test( self :coroutine, initpar : string ) { for( i = 1 : 5 ) io.writeln( self.yield( initpar + 'abc' ) ) } @[code] In such routine, @[green]self.yield()@[green] can be used yield the control of the execution and pass its parameter to the caller of @[green]self.yield()@[green] or @[green]self.resume()@[green]. Then the coroutine can be started by, @[code] a = co.start( Test, 'test' ) @[code] which will start the coroutine and execute it until the first yield (if any). It will return the first yielded value. The coroutie can be resumed using, @[code] a = co.resume( 100 ) @[code] The parameter passed to resume() will become the returned value of yield(). And the parameter passed to yield() will become the returned value of resume(). The status of coroutine can be checked with, @[code] status = co.status() @[code] A complete example, @[code] load coroutine routine Test( self :coroutine, par : int ) { for( i = 1 : 5 ) io.writeln( i, self.yield( 'index_' + (string) (par*i) ) ) return 'done' } co = coroutine() io.writeln( co ) a = co.start( Test, 100 ) io.writeln( a ) for( i = 1 : 6 ) io.writeln( co.resume( 100*i ), co.status() ) @[code] @[text] @[name] module.standard.protobject @[name] @[title] Protobject @[title] @[text] @[green]Protobject@[green] is a data type to provide support for prototype-based OOP. Arbitrary fields can be set to or gotten from a @[green]Protobject@[green] object. A delegator can also be set for such object. When a @[green]Protobject@[green] has a delegator, setting a field for this object will only affect this object, not the delegator. @[code] load protobject obj = Protobject() obj.name = 'FirstObject' # set a field; io.writeln( obj.name ) # get a field; obj2 = Protobject() obj2.__proto__ = obj # set a delegator; io.writeln( obj2.name ) obj2.name = 'SecondObject' io.writeln( obj.name, obj2.name ) @[code] @[text] @[name] module.standard.meta @[name] @[title] Meta @[title] @[text] TODO @[text] @[name] module.standard.serializer @[name] @[title] Serializer @[title] @[text] Serializer is the module to provide methods for serializing data structures into strings and deserializing from strings. It also provide methods to serialize and deserialize an entire namespace. These methods include: @[list] --@[code]serialize(value:any)=>string@[code] Serialize a value to string. Primitive types (such as int, string) and container types (such as array, list, map and tuple) are directly serialized. Objects of C data types and Dao classes can be serialized if they define method named @[green]serialize()@[green] that return data that can be directly serialized. --@[code]deserialize(text:string)=>any@[code] Deserialize a string into a value. Serializations from primitive types (such as int, string) and container types (such as array, list, map and tuple) can be directly deserialized into values. Serializations from objects of C data types and Dao classes will be deserialized into primitive values or container values first. If the C data types and Dao classes has defined constructors that can take these values as parameters, such constrcuts will be used to construct proper C data objects and Dao objects as the deserialized values. --@[code]backup(tofile='backup.sdo',limit=0)@[code] Backup the current namespace by serialization to a file. --@[code]restore(fromfile='backup.sdo')@[code] Restore a namespace from a backup file. @[list] Upon loading this module, these methods are imported to the @[green]std@[green] namespace. Examples, @[code] load serializer; list1 = { 1.3, 2.5, 3.6 } list2 = { (any)(name=>'dao',year=>2006), (123+456C, 'abc'), [1.2, 3.4; 5.6, 7.8] } s1 = std.serialize( list1 ); s2 = std.serialize( list2 ); io.writeln( s2 ) io.writeln( s1, std.deserialize( s1 ) ) io.writeln( s2, std.deserialize( s2 ) ) map1 = { 'abc'->123, 'def'->{} } s3 = std.serialize( map1 ); io.writeln( s3, std.deserialize( s3 ) ) class Klass { var index = 123; var name = 'abc'; routine Klass( ){ index = 789; } routine Klass( i : int, s : string ){ index = i; name = s; } routine Klass( tup : tuple ){index = tup[0]; name = tup[1]; } routine serialize(){ return index, name } } object = Klass( 456, 'def' ); io.writeln( object.serialize() ); ss = std.serialize( object ); io.writeln( ss ); object = (Klass)std.deserialize( ss ) io.writeln( object, object.index, object.name ); @[code] @[text] @[name] module.standard.jit @[name] @[title] DaoJIT - JIT Compiler Based on LLVM @[title] @[text] DaoJIT is a JIT compiler for Dao based on LLVM. It provides JIT compiling for a subset of Dao virtual instructions. DaoJIT is implemented as a automatically loadable module. After it is enabled (by command line option @[green]-j@[green] or @[green]--jit@[green]), it will search for a chunk of consecutive compilable instructions and compile them into native code, and replace them with a single instruction (DVM_JITC) which can invoke the JIT compiled code. Currently, DaoJIT can only achieve good speedups for numeric intensive programs. @[text] @[name] module.standard.sync @[name] @[title] Synchronization @[title] @[text] Mutex, conditional variable and semaphore are the convetional way for thread synchronization. These constructs synchronize tasklet by block the native threads, so they are not the recommended way for synchronization and communication channels are the preferrable way. @[subsection]Mutex@[subsection] Mutex can be used to synchronize the accessing of shared data structures. It has two state: locked and unlocked. A mutex can be locked by only one thread. A thread is suspended when it attempt to lock a mutex which has been locked by another thread. @[code(dao)] mutex = mutex(); @[code(dao)] Then it can be locked or unlocked by, @[code(dao)] mutex.lock(); mutex.unlock(); mutex.trylock(); @[code(dao)] By calling @[cyan]lock()@[cyan], the calling thread will be block if the mutex is already locked by another thread. If the mutex is locked by the same thread, a second calling of @[cyan]lock()@[cyan] may cause a deadlock. @[cyan]trylock()@[cyan] is the same as @[cyan]lock()@[cyan] except that it will return immediately instead of blocking the calling thread if the mutex is already locked. @[subsection]Condition Variable@[subsection] Condition variable is a synchronization device which allow a thread to be suspended if a condition is not satisified and resume execution when it is signaled. The basic operations on a condition is: wait on the condition, or signal the condition. @[code(dao)] condvar = condition(); @[code(dao)] Condition variable should always be used together with a mutex. To wait on a condition, @[code(dao)] mtx.lock() condvar.wait( mtx ); mtx.unlock(); @[code(dao)] To wait on a condition for a maximum time, @[code(dao)] mtx.lock() condvar.timedwait( mtx, seconds ); mtx.unlock(); @[code(dao)] @[cyan]seconds@[cyan] can be a decimal, for example, @[cyan]condvar.timedwait( mtx, 0.005 )@[cyan] will wait for five miliseconds. @[subsection]Semaphore@[subsection] Semaphore can be used to set a limit on resources. It maintains a count for the resource, and allows a thread to proceed, when it attempts to decrease a non-zero count. If the count already reaches 0 before the decrement, the thread will be suspended until the count becomes non-zero. When the thread finished using the resource, it should increase the count of semaphore. A semaphore must be created with an initial count, @[code(dao)] sema = semaphore( count ); @[code(dao)] To access a resource guarded by a semaphore, use, @[code(dao)] sema.wait() @[code(dao)] If the resource is acquired, the count of @[cyan]sema@[cyan] will be decreased. To release the resource, use @[code(dao)] sema.post() @[code(dao)] which will increase the count. @[subsection]State@[subsection] State is an abstraction layer for multiple condition variables coupled with the principles of atomic operations. Semantically, it represent a state of certain process or object which can be set, altered or waited upon by arbitrary number of concurrent tasks. A state contains single value which can be assigned or modified using test-and-set and fetch-and-add principles. One or more threads can wait until a state reaches certain value, in this case the synchronization is similar to using conditional variables with broadcasting. A state can represent an @[cyan]int@[cyan], @[cyan]float@[cyan], @[cyan]double@[cyan], @[cyan]complex@[cyan] or @[cyan]enum@[cyan] value: @[code(dao)] st = state(0); type status = enum st2 = state($on + $idle); @[code(dao)] To perform basic read and write, @[cyan]value()@[cyan] and @[cyan]set()@[cyan] are provided, the latter returns the old value of a state: @[code(dao)] if (st.value() == 0) old = st2.set($off); @[code(dao)] @[cyan]alter(old, new)@[cyan] can be used to assign @[cyan]new@[cyan] value to a state if it currently contains @[cyan]old@[cyan] value. The routine returns non-zero if the operation has succeeded: @[code(dao)] success = st.alter(0, 1); if (success){ ... #will be executed by the only succeeded thread } @[code(dao)] @[cyan]add()@[cyan] and @[cyan]sub()@[cyan] are meant to add/substract for numeric values and append/remove symbols for a combined enum. The old value of state is returned: @[code(dao)] old = st.sub(1); st2.add($finished); @[code(dao)] To block the current thread until a state is assigned the desired value, @[cyan]wait()@[cyan] is to be used. A timeout may specified, in this case the routine will return zero if time-outed. To determine what values are currently awaited by other threads, @[cyan]waitlist()@[cyan] is provided: @[code(dao)] if (2 not in st.waitlist()) success = st.wait(1, 5.0); @[code(dao)] @[text] @[name] module.standard.dataframe @[name] @[title] Data Frame @[title] @[text] A data frame is a matrix-like data structure that organizes data values into table(s). But unlike a matrix, a data frame can contain any types of values, though the values of the same column are required to be the same. Also, the rows and columns can be attached with arbitrary sets of labels, and such labels can be used to retrieve the rows or columns. Dao data frame supports both 2D and 3D data frames. A 2D data frame contains only one data table, with data items indexed by row and column indices or labels. A 3D data frame contains multiple data tables, where each table is indexed by a depth index or multiple depth labels. Here is a simple example to create and print a data frame: @[code] load DataFrame matrix = [ [ -1.0D : -1 : 4] : 1.5 : 5 ] dframe = DataFrame( matrix ) io.writeln( dframe ); @[code] which will print something like the following: @[code] DataFrame[0x7f82f34a1700] Dimensions: Rows=5; Cols=4; Deps=1; Depth: 0; | Columns from 0 to 3: | 0 1 2 3 |--------------------------| 0: -1.0 -2.0 -3.0 -4.0 1: 0.5 -0.5 -1.5 -2.5 2: 2.0 1.0 0.0 -1.0 3: 3.5 2.5 1.5 0.5 4: 5.0 4.0 3.0 2.0 @[code] More on data frame creation: @[code] # Create a 2D matrix: matrix = [ [ -1.0D : -1 : 8] : 1.51 : 16 ] # Create a 2D dataframe from the matrix: dframe2d = DataFrame( matrix ) # Create a 3D matrix: matrix3d = [ matrix : 1 : 3 ] matrix3d.permute( [ 2, 1, 0 ] ) # Create 3D dataframe: dframe3d = DataFrame( matrix3d ) @[code] Attaching labels to data frames: @[code] # Add row labels: dframe2d.AddLabels( $row, { 'RR1' => 0, 'R2' => 1, 'RRRR5' => 5 } ) dframe2d.AddLabels( $row, { 'RR1' => 0, 'R2' => 1, 'RRRR5' => 6 } ) # Add column labels: dframe2d.AddLabels( $column, { 'CC1' => 0, 'C2' => 1, 'CCCCCCCCC3' => 4 } ) @[code] Adding additional columns: @[code] # Add string column: dframe2d.AddColumn( { 'ABC', 'DEF', 'SSSS', 'Hello\t world!' }, 'String' ) # Add column of arbitrary type: dframe2d.AddColumn( { {'ABC'}, {'DEF', 'SSSS'}, 'Hello\n world!' }, 'Any' ) # Add integer column: dframe2d.AddColumn( [ 123, 456, 7890], 'Number' ) @[code] Operation on data frame: @[code] # Update one cell: dframe2d[0,1] = 1000000.23 # Update one column: dframe2d[:,2] += [[0]:100:9] # Add the first column to the second: dframe2d[:,1] += dframe2d[:,0] @[code] Using code section methods of data frame: @[code] # Scan cells: dframe2d[1:5,:3].ScanCells { [value,row,column] io.writeln( row, column, value ) } # Scan rows: dframe2d[1:5,:3].ScanRows { [value,row] io.writeln( row, value ) } # Scan columns: dframe2d[1:5,:3].ScanColumns { [value,column] io.writeln( column, value ) } @[code] @[text]