Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
This repository was archived by the owner on Dec 20, 2018. It is now read-only.
/monkeyPublic archive

Interpreter with support for class, linq, sql, net, http, fmt, json and A realtime syntax highlighting REPL.

License

NotificationsYou must be signed in to change notification settings

haifenghuang/monkey

Repository files navigation

This project is no longer being maintained. All the contents have beenmoved tomagpie.

Monkey Programming Language

Chinese version:中文

Table of Contents

Summary

Monkey is a toy language interpreter, written in Go. It has C-style syntax, and is largely inspired by Ruby, Python, Perl and c#It support the normal control flow, functional programming and object oriented programming.It also has a REPL with realtime syntax highlighter.

This is a sample program using monkey language:

//Declare annotation class//Note: In the body, you must use property, not method.class@MinMaxValidator{  property MinLength  property MaxLength default10 //Same as 'property MaxLength = 10'}//This is a marker annotationclass@NoSpaceValidator{}class@DepartmentValidator{  property Department}//The 'Request' classclass Request{@MinMaxValidator(MinLength=1)  property FirstName; //getter and setter are implicit. It is equal to 'property FirstName { get; set; }'@NoSpaceValidator  property LastName;@DepartmentValidator(Department=["Department of Education","Department of Labors"])  property Dept;}//This class is responsible for processing the annotation.class RequestHandler{static fn handle(o){    props= o.getProperties()forpin props{      annos= p.getAnnotations()forannoin annos{if anno.instanceOf(MinMaxValidator){          //p.value is the property real value.iflen(p.value)> anno.MaxLength ||len(p.value)< anno.MinLength{printf("Property '%s' is not valid!\n", p.name)}} elseif anno.instanceOf(NoSpaceValidator){forcin p.value{if c=="" || c=="\t"{printf("Property '%s' is not valid!\n", p.name)break}}} elseif anno.instanceOf(DepartmentValidator){          found=falsefordin anno.Department{if p.value== d{              found=true}}if !found{printf("Property '%s' is not valid!\n", p.name)}}}}}}class RequestMain{static fn main(){    request=new Request()    request.FirstName="Haifeng123456789"    request.LastName="Huang"    request.Dept="Department of Labors"RequestHandler.handle(request)}}RequestMain.main()

Below is the result:

Property 'FirstName' not valid!Property 'LastName' not valid!

Below is the REPL with real time syntax highlight:

REPL

Below is the html document generated using themdoc tool:

HTML DOC

Overview

This project is based on mayoms's projectmonkey with some bug fixes and a lot of new features including:

  • Added simple class support(Indexer, operator overloading, property, static method/property/field and class annotation)
  • Modified string module(which can correctly handle utf8 character encoding)
  • Addedfile module(with some new methods).
  • Addedmathtime,sort,os,log,net,http,filepath,fmt,sync,list,csv,regexp,template, etc...
  • sql(db) module(which can correctly handing null values)
  • flag module(for handling command line options)
  • json module(for json marshaling and unmarshaling)
  • linq module(Code come fromlinq with some modifications)
  • decimal module(Code come fromdecimal with some minor modifications)
  • Regular expression literal support(partially like perls)
  • channel support(like golang's channel)
  • more operator support(&&, ||, &, |, ^, +=, -=, ?:, ??, etc.)
  • utf8 support(e.g. you could use utf8 character as variable name)
  • more flow control support(e.g. try/catch/finally, for-in, case, c-like for loop)
  • defer support
  • spawn support(goroutine)
  • enum support
  • using support(like C#'susing)
  • pipe operator support(see demo for help)
  • function with default value and variadic parameters
  • list comprehension and hash comprehension support
  • user defined operator support
  • Using method of Go Package(RegisterFunctions andRegisterVars)

There are a number of tasks to complete, as well as a number of bugs. The purpose of this project was to dive deeper into Go, as well as get a better understanding of how programming languages work. It has been successful in those goals. There may or may not be continued work - I do plan on untangling a few messy spots, and there are a few features I'd like to see implemented. This will happen as time and interest allows.

Installation

Just download the repository and run./run.sh

Basic use

To access the REPL, simply run the following:

~ » monkeyMonkey programming language REPL>>

or, to run a program:

monkey path/to/file

Language Tour

Comments

Monkey support two kinds of single line comment and also block comment.

// this is a single line comment# this is another single line comment/* This is a    block comment.*/

Data Types

Monkey supports 9 basic data types:String,Int,UInt,Float,Bool,Array,Hash,Tuple andNil

s1="hello, 黄"       # strings are UTF-8 encodeds2= `hello,"world"`  # raw stringi=10                 # intu=10u                # uintf=10.0               # floatb=true               # boola=[1,"2"]           # arrayh={"a":1,"b":2}   # hasht=(1,2,3)            # tuplen=nil

Constants(Literal)

In monkey, there are mainly eleven types of constants(Literals).

  • Integer
  • UInteger
  • Float
  • String
  • Regular expression
  • Array
  • Hash
  • Tuple
  • Nil
  • Boolean
  • Function
// Integer literalsi1=10i2=20_000_000     //for more readablei3=0x80           // hexi4=0b10101        // binaryi5=0o127          // octal// Unsigned Integer literalsui1=10uui2=20_000_000u     //for more readableui3=0x80u           // hexui4=0b10101u        // binaryui5=0o127u          // octal// Float literalsf1=10.25f2=1.02E3f3=123_456.789_012 //for more readable// String literalss1="123"s2="Hello world"// Regular expression literalsr=/\d+/.match("12")if(r){prinln("regex matched!")}// Array literalsa=[1+2,3,4,"5",3]// Hash literalsh={"a":1,"b":2,"c":2}//Tuple literalst=(1,2+3,"Hello",5)// Nil literaln=nil// Boolean literalst=truef=false// Function literalsletf1=add(x, y){return x+ y}println(f1(1,2))//fat-arrow function literalsletf2=(x, y)=> x+ yprintln(f2(1,2))

Variables

Variables in Monkey could start with the keywordlet, or nothing with theformvariable=value.

leta,b,c=1,"hello world",[1,2,3]d=4e=5=""

You can also useDestructuring assignment.Note, the left-hand side must be included using the '()'.

//righ-hand side is an arraylet(d,e,f)=[1,5,8]//d=1, e=5, f=8//right-hand side is a tuplelet(g, h, i)=(10,20,"hhf")//g=10, h=20, i=hhf//righ-hand side is a hashlet(j, k, l)={"j":50,"l":"good"}//j=50, k=nil, l=good

Note however, if you do not use the keywordlet, you could not do multiple variable assignments.Below code is not correct:

//Error, multiple variable assignments must be use `let` keyworda, b, c=1,"hello world",[1,2,3]

Note:Starting from Monkey 5.0,when the decalared variable already exists, it's value will be overwritten:

letx,y=10,20;letx,y= y,x //Swap the value of x and yprintf("x=%v, y=%v\n", x, y)  //result: x=20, y=10

let also support the placeholder(_), when assigned a value, it will just ignore it.

letx, _,y=10,20,30printf("x=%d, y=%d\n", x, y) //result: x=10, y=30

Reserved keywords

Keywords are predefined, reserved identifiers that have special meanings to the compiler. They cannot be used as identifiers. Below is a list of reserved keywords

  • fn
  • let
  • true false nil
  • if elsif elseif elif else
  • unless
  • return
  • include
  • and or
  • enum
  • struct # reserved, not used
  • do while for break continue where
  • grep map
  • case is in
  • try catch finally throw
  • defer
  • spawn
  • qw
  • using
  • class new property set get static default
  • interface public private protected # reserved, not used

Type conversion

You can use the builtinint(),uint(),float(),str(),array(),tuple(),hash,decimal functions for type conversion.

leti=0xaletu=uint(i)                 // result: 10lets=str(i)                  // result: "10"letf=float(i)                // result: 10leta=array(i)                // result: [10]lett=tuple(i)                // result:(10,)leth=hash(("key","value"))  // result: {"key": "value}letd=decimal("123.45634567") // result: 123.45634567

You could create a tuple from an array:

lett=tuple([10,20])   //result:(10,20)

Similarly, you could also create an array from a tuple:

letarr=array((10,20))  //result:[10,20]

You could only create a hash from an array or a tuple:

//create an empty hashleth1=hash()  //same as h1 = {}//create a hash from an arrayleth1=hash([10,20])     //result: {10 : 20}leth2=hash([10,20,30])   //result: {10 : 20, 30 : nil}//create a hash from a tupleleth3=hash((10,20))     //result: {10 : 20}leth4=hash((10,20,30))   //result: {10 : 20, 30 : nil}

qw(Quote word) keyword

Theqw keyword is like perl'sqw keyword. When you want to use a lot of quoted strings,theqw keyword can make it a lot easier for those strings.

for str inqw<abc,def,ghi,jkl,mno>{ //allowed 'qw' pair is '{}', '<>', '()'println('str={str}')}newArr=qw(1,2,3.5) //array with string values, not number values.fmt.printf("newArr=%v\n", newArr)

enum keyword

In mokey, you can use enum to define constants.

LogOption=enum{    Ldate=1 <<0,    Ltime=1 <<1,    Lmicroseconds=1 <<2,    Llongfile=1 <<3,    Lshortfile=1 <<4,    LUTC=1 <<5,    LstdFlags=1 <<4 |1 <<5}opt=LogOption.LstdFlagsprintln(opt)//get all names of the `enum`forsinLogOption.getNames(){ //not orderedprintln(s)}//get all values of the `enum`forsinLogOption.getValues(){ //not orderedprintln(s)}// get a specific name of the `enum`println(LogOption.getName(LogOption.Lshortfile))

Meta-Operators

Monkey has some meta-operators borrowed from perl6.There are strict rules for meta-operators:

  • Meta-operators can only operator on arrays.
  • Each array's element must be number type(uint, int, float) or string type.
  • If the meat-operators serve as an infix operator, and if the left and right are all arrays, they must have the same number of elements.
letarr1=[1,2,3]~*[4,5,6]letarr2=[1,2,3]~*4letarr3=[1,2,"HELLO"]~*2letvalue1=~*[10,2,2]letvalue2=~+[2,"HELLO",2]println(arr1)   //result: [4, 10, 18]println(arr2)   //result: [4, 8, 12]println(arr3)   //result: [2,4,"HELLOHELLO"]println(value1) //result: 40println(value2) //result: 2HELLO2

At the moment, Monkey has six meta-operators:

  • ~+

  • ~-

  • ~*

  • ~/

  • ~%

  • ~^

The six meta-operators could be served as either infix expression or prefix expression.

The meta-operator for infix expression will return an array.The meta-operator for prefix expression will return a value(uint, int, float, string).

Below talbe give an example of meta-operator and their meanings:(only~+ is showed):

Meta-OperatorExpressionExampleResult
~+Infix Expression[x1, y1, z1] ~+ [x2, y2, z2][x1+x2, y1+y2, z1+z2] (Array)
~+Infix Expression[x1, y1, z1] ~+ 4[x1+4, y1+4, z1+4] (Array)
~+Prefix Expression~+[x1, y1, z1]x1+y1+z1 (Note: a value, not an array)

Control flow

  • if/if-else/if-elif-else/if-elsif-else/if-elseif-else/if-else if-else
  • unless/unless-else
  • for/for-in
  • while
  • do
  • try-catch-finally
  • case-in/case-is
// if-elseleta,b=10,5if(a> b){println("a > b")}elseif a== b{ // could also use 'elsif', 'elseif' or 'elif'println("a = b")}else{println("a < b")}//unless-elseunless b> a{println("a >= b")} else{println("b > a")}// fori=9for{ // forever loop    i= i+2if(i>20){break}println('i={i}')}i=0for(i=0; i<5; i++){  // c-like for, '()' is a mustif(i>4){break}if(i==2){continue}    println('i is{i}')}i=0for(; i<5; i++){  // no initialization statement.if(i>4){break}if(i==2){continue}    println('i is{i}')}i=0for(; i<5;;){  // no updater statement.if(i>4){break}if(i==2){continue}    println('i is{i}')    i++ // Updater statement}i=0for(;;;){  // same as 'for { block }'if(i>4){break}    println('i is{i}')    i++ //update the 'i'}for i in range(10){println('i={i}')}a=[1,2,3,4]for i in awhere i%2!=0{println(i)}hs={"a":1,"b":2,"c":3,"d":4,"e":5,"f":6,"g":7}for k, v in hswhere v%2==0{println('{k}:{v}')}for i in1..5{println('i={i}')}for item in10..20where $_%2==0{ // $_ is the indexprintf("idx=%d, item=%d\n", $_, item)}for c in"m".."a"{println('c={c}')}for idx, v in"abcd"{printf("idx=%d, v=%s\n", idx, v)}for idx,v in["a","b","c","d"]{printf("idx=%d, v=%s\n", idx, v)}for item in["a","b","c","d"]where $_%2==0{ // $_ is the indexprintf("idx=%d, item=%s\n", $_, v)}//for loop is an expression, not statement, so it could be assigned to a variablelet plus_one=for i in[1,2,3,4]{ i+1}fmt.println(plus_one)// whilei=10while(i>3){    i--println('i={i}')}// doi=10do{    i--if(i==3){break}}// try-catch-finally(only support string type)let exceptStr="SUMERROR"try{letth=1+2if(th==3){throw exceptStr}}catch"OTHERERROR"{println("Catched OTHERERROR")}catch exceptStr{println("Catched is SUMERROR")}catch{println("Catched ALL")}finally{println("finally running")}// case-in/case-islet testStr="123"case testStr in{ // in(exact/partial match), is(only exact match)"abc","mno"{println("testStr is 'abc' or 'mno'")}"def"{println("testStr is 'def'")}    `\d+`{println("testStr contains digit")}    else{println("testStr not matched")}}let i=[{"a":1,"b":2},10]let x=[{"a":1,"b":2},10]case i in{1,2{println("i matched 1, 2")}3{println("i matched 3")}    x{println("i matched x")}    else{println("i not matched anything")}}

using statement

In monkey, if you have some resources you want to release/free/close, e.g. close opended file, close network connection etc,you can use theusing statement just likec#.

// Here we use 'using' statement, so we do not need to call infile.close().// When finished running 'using' statement, it will automatically call infile.close().using(infile=newFile("./file.demo","r")){if(infile==nil){println("opening 'file.demo' for reading failed, error:", infile.message())        os.exit(1)}letline;letnum=0    //Read file by using extraction operator(">>")while(infile>>line!=nil){        num++printf("%d%s\n", num, line)}}

User Defined Operator

In monkey, you are free to define some operators, but you cannotoverwrite predefined operators.

Note: Not all operators could be user defined.

Below is an example for showing how to write User Defined Operators:

//infix operator '=@' which accept two parameters.fn=@(x, y){return x+ y* y}//prefix operator '=^' which accept only one parameter.fn=^(x){return-x}letpp=10=@5 // Use the '=@' user defined infix operatorprintf("pp=%d\n", pp) // result: pp=35lethh==^10 // Use the '=^' prefix operatorprintf("hh=%d\n", hh) // result: hh=-10
fn.^(x, y){    arr=[]while x<= y{        arr+= x        x+=2}return arr}letpp=10.^20printf("pp=%v\n", pp) // result: pp=[10, 12, 14, 16, 18, 20]

Below is a list of predefined operators and user defined operators:

Predefined OperatorsUser Defined Operators
==
=~
=>
=X
++
+=
+X
--
-=
->
-X
>=
<>
>X
<=
<<
<X
!=
!~
!X
*=
**
*X
..
..
.X
&=
&&
&X
|=
||
|X
^=^X

In the table above,X could be.=+-*/%&,|^~<,>},!?@#$

Integer

In monkey, integer is treated as an object, so you could call it's methods.Please see below examples:

x=(-1).next()println(x) //0x=-1.next() //equals 'x = -(1.next())println(x) //-2x=(-1).prev()println(x) //-2x=-1.prev() //equals 'x = -(1.prev())println(x) //0x=[i for i in10.upto(15)]println(x) //[10, 11, 12, 13, 14, 15]foriin10.downto(5){print(i,"") //10 9 8 7 6 5}println()if10.isEven(){println("10 is even")}if9.isOdd(){println("9 is odd")}

Float

In monkey, float is also treated as an object, so you could call it's methods.Please see below examples:

f0=15.20println(f0)f1=15.20.ceil()println(f1)f2=15.20.floor()println(f2)

Decimal

In monkey, decimal is Arbitrary-precision fixed-point decimal numbers.And the code mainly based ondecimal.

Please see below examples:

d1= decimal.fromString("123.45678901234567")  //create decimal from stringd2= decimal.fromFloat(3)  //create decimal from float//set decimal division precision.//Note: this will affect all other code that followsdecimal.setDivisionPrecision(50)fmt.println("123.45678901234567/3 =", d1.div(d2))  //print d1/d2fmt.println(d1.div(d2)) //same as abovefmt.println(decimal.fromString("123.456").trunc(2)) //truncate decimal//convert string to decimald3=decimal("123.45678901234567")fmt.println(d3)fmt.println("123.45678901234567/3 =", d3.div(d2))

Array

In monkey, you could use [] to initialize an empty array:

emptyArr=[]emptyArr[3]=3 //will auto expand the arrayprintln(emptyArr)

You can create an array with the given size(or length) using below two ways:

//create an array with 10 elements initialized to nil.//Note: this only support integer literal.letarr=[]10println(arr)//use the builtin 'newArray' method.letanotherArr=newArray(len(arr))println(anotherArr) //result: [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]letarr1=["1","a5","5","5b","4","cc","7","dd","9"]letarr2=newArray(6, arr1,10,11,12) //first parameter is the sizeprintln(arr2) //result: ["1", "a5", "5", "5b", "4", "cc", "7", "dd", "9", 10, 11, 12]letarr3=newArray(20, arr1,10,11,12)println(arr3) //result : ["1", "a5", "5", "5b", "4", "cc", "7", "dd", "9", 10, 11, 12, nil, nil, nil, nil, nil, nil, nil, nil]

Array could contain any number of different data types.Note: the last comma before the closing ']' is optional.

mixedArr=[1,2.5,"Hello",["Another","Array"],{"Name":"HHF","SEX":"Male"}]

You could use index to access array element.

println('mixedArr[2]={mixedArr[2]}')println(["a","b","c","d"][2])

Because array is an object, so you could use the object's method to operate on it.

if([].empty()){println("array is empty")}emptyArr.push("Hello")println(emptyArr)//you could also use 'addition' to add an item to an arrayemptyArr+=2println(emptyArr)//You could also use `<<(insertion operator)` to add item(s) to an array, the insertion operator support chain operation.emptyArr <<2 <<3println(emptyArr)

Array could be iterated usingfor loop

numArr=[1,3,5,2,4,6,7,8,9]foritemin numArrwhere item%2==0{println(item)}letstrArr=["1","a5","5","5b","4","cc","7","dd","9"]foritemin strArrwhere/^\d+/.match(item){println(item)}foritemin["a","b","c","d"]where $_%2==0{  //$_ is the indexprintf("idx=%d, v=%s\n", $_, item)}

You could also use the builtinreverse function to reverse array element:

letarr=[1,3,5,2,4,6,7,8,9]println("Source Array =", arr)revArr=reverse(arr)println("Reverse Array =", revArr)

Array also supportarray multiplication operator(*):

letarr=[3,4]*3println(arr) // result: [3,4,3,4,3,4]

String

In monkey, there are three types ofstring:

  • Raw string
  • Double quoted string(Could not contains newline)
  • Single quoted string(Interpolated String)

Raw string literals are character sequences between back quotes, as infoo. Within the quotes, any character may appear except back quote.

See below for some examples:

normalStr="Hello"+"world!"println(normalStr)println("123456"[2])rawStr= `Welcome tovisit us!`println(rawStr)//when you use single quoted string, and want variable to be interpolated,//you just put the variable into '{}'. see below:str="Hello world"println('str={str}') //output: "Hello world"str[6]="W"println('str={str}') //output: "Hello World"

In monkey, strings are utf8-encoded, you could use utf-8 encoded name as a variable name.

=3=5println(+) //output : 8

strings are also object, so you could use some of the methods provided bystrings module.

upperStr="hello world".upper()println(upperStr) //output : HELLO WORLD

string could also be iterated:

foridx, vin"abcd"{printf("idx=%d, v=%s\n", idx, v)}forvin"Hello World"{printf("idx=%d, v=%s\n", $_, v) //$_ is the index}

You could concatenate an object to a string:

joinedStr="Hello"+"World"joinedStr+="!"println(joinedStr)

You could also can use the builtinreverse function to reverse character of the string:

letstr="Hello world!"println("Source Str =", str)revStr=reverse(str)println("Reverse str =", revStr)

If you hava a string, you want to convert it to number, you could add a "+" prefix before the string.

a=+"121314" // a is an intprintln(a) // result: 121314// Integer also support "0x"(hex), "0b"(binary), "0o"(octal) prefixa=+"0x10" // a is an intprintln(a) // result: 16a=+"121314.6789" // a is a floatprintln(a) // result: 121314.6789

Hash

In monkey, the builtin hash will keep the order of keys when they are added to the hash, just like python's orderedDict.

You could use {} to initialize an empty hash:

emptyHash={}emptyHash["key1"]="value1"println(emptyHash)

Hash's key could be string, int, boolean:

hashObj={12:"twelve",true:1,"Name":"HHF"}println(hashObj)

Note: the last comma before the closing '}' is optional.

You could use '+' or '-' to add or remove an item from a hash:

hashObj+={"key1":"value1"}hashObj+={"key2":"value2"}hashObj+={5:"five"}hashObj-="key2"hashObj-=5println(hash)

In monkey, Hash is also an object, so you could use them to operate on hash object:

hashObj.push(15,"fifteen") //first parameter is the key, second is the valuehashObj.pop(15)keys= hashObj.keys()println(keys)values= hashObj.values()println(values)

You could also use the builtinreverse function to reverse hash's key and value:

leths={"key1":12,"key2":"HHF","key3":false}println("Source Hash =", hs)revHash=reverse(hs)println("Reverse Hash =", revHash)

Tuple

In monkey,tuple is just like array, but it could not be changed once it has been created.

Tuples are constructed using parenthesized list notation:

//Create an empty tuplelett1=tuple()//Same as above.lett2=()// Create a one element tuple.// Note: the trailing comma is necessary to distinguish it from the//       parenthesized expression (1).// 1-tuples are seldom used.lett3=(1,)//Create a two elements tuplelett4=(2,3)

Any object may be converted to a tuple by using the built-intuple function.

lett=tuple("hello")println(t)  // result: ("hello")

Like arrays, tuples are indexed sequences, so they may be indexed and sliced.The index expression tuple[i] returns the tuple element at index i, and the sliceexpression tuple[i:j] returns a subsequence of a tuple.

lett=(1,2,3)[2]print(t) // result:3

Tuples are iterable sequences, so they may be used as the operand of a for-loop,a list comprehension, or various built-in functions.

//for-loopforiin(1,2,3){println(i)}//tuple comprehensionlett1=[x+1 for x in(2,4,6)]println(t1) //result: [3, 5, 7]. Note: Result is array, not tuple

Unlike arrays, tuples cannot be modified. However, the mutable elements of a tuple may be modified.

arr1=[1,2,3]t=(0, arr1,5,6)println(t)    // result: (0, [1, 2, 3], 5, 6)arr1.push(4)println(t)    //result:  (0, [1, 2, 3, 4], 5, 6)

Tuples are hashable (assuming their elements are hashable), so they may be used as keys of a hash.

key1=(1,2,3)key2=(2,3,4)letht={key1:10, key2:20}println(ht[key1]) // result: 10println(ht[key2]) // result: 20//Below is not supported(will issue a syntax error):letht={(1,2,3):10,(2,3,4):20} //error!println(ht[(1,2,3)])  //error!println(ht[(2,3,4)])  //error!

Tuples may be concatenated using the + operator, it will create a new tuple.

lett=(1,2)+(3,4)println(t) // result: (1, 2, 3, 4)

A tuple used in a Boolean context is considered true if it is non-empty.

lett=(1,)if t{println("t is not empty!")}else{println("t is empty!")}//result : "t is not empty!"

Tuple's json marshaling and unmarshaling will be treated as array:

lettupleJson=("key1","key2")lettupleStr= json.marshal(tupleJson)//Result:[//        "key1",//        "key2",//       ]println(json.indent(tupleStr,""))lettupleJson1= json.unmarshal(tupleStr)println(tupleJson1) //result: ["key1", "key2"]

Tuple plus an array will return an new array, not a tuple

t2=(1,2,3)+[4,5,6]println(t2) // result: [(1, 2, 3), 4, 5, 6]

You could also use the builtinreverse function to reverse tuples's elements:

lettp=(1,3,5,2,4,6,7,8,9)println(tp) //result: (1, 3, 5, 2, 4, 6, 7, 8, 9)revTuple=reverse(tp)println(revTuple) //result: (9, 8, 7, 6, 4, 2, 5, 3, 1)

class

Monkey has limited support for the oop concept, below is a list of features:

  • inheritance and polymorphism
  • operator overloading
  • property(with getter or setter or both)
  • static member/method/property
  • indexer
  • class category
  • class annotations(limited support)
  • constructor method and normal methods support default value and variadic parameters

The monkey parser could parsepublic,private,protected, but it has no effect in the evaluation phase.That means monkey do not support access modifiers at present.

You useclass keyword to declare a class and usenew class(xxx) to create an instance of aclass.

class Animal{letname=""    fn init(name){    //'init' is the constructor        //do somthing}}

In monkey, all class is inherited from the root classobject.object class include some common method liketoString(),instanceOf(),is_a(),classOf(),hashCode.

Above code is same as:

classAnimal:object{letname=""    fn init(name){    //'init' is the constructor        //do somthing}}

inheritance and polymorphism

You can inherit a class using::

classDog:Animal{ //Dog inherits from Animal}

In the child class, you can use theparent to access parent class's members or methods.

please see below for an example:

class Animal{letName;    fn MakeNoise(){        println("generic noise")}fn ToString(){return"oooooooo"}}class Cat:Animal{    fn init(name){        this.Name= name}    fn MakeNoise(){println("Meow")}fn ToString(){return Name+" cat"}}class Dog:Animal{    fn init(name){        this.Name= name}    fn MakeNoise(){println("Woof!")}fn ToString(){return Name+" dog"}fn OnlyDogMethod(){println("secret dog only method")}}cat=new Cat("pearl")dog=new Dog("cole")randomAnimal=new Animal()animals=[cat, dog, randomAnimal]foranimalin animals{println("Animal name:"+ animal.Name)    animal.MakeNoise()println(animal.ToString())ifis_a(animal,"Dog"){        animal.OnlyDogMethod()}}

The result is:

Animal name: pearlMeowpearl catAnimal name: coleWoof!cole dogsecret dog only methodAnimal name: nilgeneric noiseoooooooo

operator overloading

class Vector{letx=0;lety=0;    // constructor    fn init(a, b, c){if(!a){ a=0;}if(!b){b=0;}        x= a; y= b}    fn+(v){ //overloading '+'        if(type(v)=="INTEGER"{returnnew Vector(x+ v, y+ v);} elseif v.is_a(Vector){returnnew Vector(x+ v.x, y+ v.y);}        return nil;}    fn String(){return fmt.sprintf("(%v),(%v)", this.x, this.y);}}fn Vectormain(){    v1=new Vector(1,2);    v2=new Vector(4,5);        // call + function in the vector object    v3= v1+ v2 //same as 'v3 = v1.+(v2)'    // returns string "(5),(7)"println(v3.String());        v4= v1+10 //same as v4 = v1.+(10);    //returns string "(11),(12)"println(v4.String());}Vectormain()

property(like c#)

classDate{letmonth=7;  // Backing store    property Month{        get{return month}set{if((value>0) &&(value<13)){                month= value}else{println("BAD, month is invalid")}}}    property Year; // same as 'property Year { get; set;}'property Day{ get;}property OtherInfo1{ get;}property OtherInfo2{ set;}fn init(year, month, day){        this.Year= year        this.Month= month        this.Day= day}fn getDateInfo(){printf("Year:%v, Month:%v, Day:%v\n", this.Year, this.Month, this.Day) //note here, you need to use 'this.Property', not 'Property'}}dateObj=new Date(2000,5,11)//printf("Calling Date's getter, month=%d\n", dateObj.Month)dateObj.getDateInfo()println()dateObj.Month=10printf("dateObj.Month=%d\n", dateObj.Month)dateObj.Year=2018println()dateObj.getDateInfo()//Below code will raise an execution error! Because OtherInfo1 is a READONLY property.//dateObj.OtherInfo1 = "Other Date Info"//println(dateObj.OtherInfo1)//Below code will raise an execution error! Because OtherInfo2 is a WRITEONLY property.//dateObj.OtherInfo2 = "Other Date Info2"//println(dateObj.OtherInfo2)//Below code will raise an execution error! Because Day is a READONLY property.//dateObj.Day = 18

indexer

Monkey has support for classindexer(like c#).An indexer is a member that enables an object to be indexed in the same way as an array.

You declare an Indexer usingproperty this[parameter].

property this[index]{get{ xxx}set{ xxx}}

Please see the example code:

classIndexedNames{letnamelist=[]letsize=10    fn init(){leti=0        for(i=0; i< size; i++){            namelist[i]="N. A."}}    fn getNameList(){println(namelist)}    property this[index]{get{lettmp;if( index>=0 && index<= size-1){               tmp=namelist[index]}else{               tmp=""}return tmp}set{if( index>=0 && index<= size-1){namelist[index]= value}}}}fn Main(){    namesObj=new IndexedNames()    //Below code will call Indexer's setter functionnamesObj[0]="Zara"namesObj[1]="Riz"namesObj[2]="Nuha"namesObj[3]="Asif"namesObj[4]="Davinder"namesObj[5]="Sunil"namesObj[6]="Rubic"    namesObj.getNameList()    for(i=0; i< namesObj.size; i++){println(namesObj[i]) //Calling Indexer's getter function}}Main()

static members/methods/properties

class Test{staticletx=0;staticlety=5;static fn Main(){      println(Test.x);println(Test.y);Test.x=99;println(Test.x);}}Test.Main()

Note:Non-static variable/method/property could access static variable/method/property.On the other hand, static variable/method/property cannot access Non-static variable/method/property.

Class Category

Monkey also support class Category like objective-c(C# is called 'extension methods').

class Animal{    fn Walk(){        println("Animal Walk!")}}//Class category like objective-cclass Animal(Run){ //Create an 'Run' category of Animal class.fn Run(){println("Animal Run!")        this.Walk() //can call Walk() method of Animal class.}}animal=new Animal()animal.Walk()println()animal.Run()

Annotations

Monkey also has very simple annotation support like java:

  • Only mehotd and property of class can have annotations(not class itself, or other simple functions)
  • In the body ofAnnotation class, only support property, do not support methods.
  • When use annotations, you must create an object.

You could useclass @annotationName {} to declare an annotation class.Monkey also include some builtin annotations:

  • @Override annotation(just like java's @Override).
  • @NotNull
  • @NotEmpty

Please see below example:

//Declare annotation class//Note: In the body, you must use property, not method.class@MinMaxValidator{  property MinLength  property MaxLength default10 //Same as 'property MaxLength = 10'}//This is a marker annotationclass@NoSpaceValidator{}class@DepartmentValidator{  property Department}//The 'Request' classclass Request{@MinMaxValidator(MinLength=1)  property FirstName; // getter and setter is implicit@NoSpaceValidator  property LastName;@DepartmentValidator(Department=["Department of Education","Department of Labors","Department of Justice"])  property Dept;}//This class is responsible for processing the annotation.class RequestHandler{static fn handle(o){    props= o.getProperties()forpin props{      annos= p.getAnnotations()forannoin annos{if anno.instanceOf(MinMaxValidator){          //p.value is the property real value.iflen(p.value)> anno.MaxLength ||len(p.value)< anno.MinLength{printf("Property '%s' is not valid!\n", p.name)}} elseif anno.instanceOf(NoSpaceValidator){forcin p.value{if c=="" || c=="\t"{printf("Property '%s' is not valid!\n", p.name)break}}} elseif anno.instanceOf(DepartmentValidator){          found=falsefordin anno.Department{if p.value== d{              found=true}}if !found{printf("Property '%s' is not valid!\n", p.name)}}}}}}class RequestMain{static fn main(){    request=new Request()    request.FirstName="Haifeng123456789"    request.LastName="Huang"    request.Dept="Department of Justice"RequestHandler.handle(request)}}RequestMain.main()

Below is the result:

Property 'FirstName' is not valid!Property 'LastName' is not valid!

Standard input/output/error

There are three predefined object for representing standard input, standard output, standard error.They arestdin,stdout,stderr.

stdout.writeLine("Hello world")//same as abovefmt.fprintf(stdout,"Hello world\n")print("Please type your name:")name= stdin.read(1024)  //read up to 1024 bytes from stdinprintln("Your name is"+ name)

You can also using Insertion operator (<<) and Extraction operator(>>) just like c++ to operate stdin/stdout/stderr.

// Output to stdout by using insertion operator("<<")// 'endl' is a predefined object, which is "\n".stdout <<"hello" <<"world!" <<" How are you?" << endl;// Read from stdin by using extraction operator(">>")letname;stdout <<"Your name please:";stdin >> name;printf("Welcome, name=%v\n", name)

Insertion operator (<<) and Extraction operator(>>) can also be used for operating file object。

//Read file by using extraction operator(">>")infile=newFile("./file.demo","r")if(infile==nil){println("opening 'file.demo' for reading failed, error:", infile.message())    os.exit(1)}letline;letnum=0while( infile>>line!=nil){    num++printf("%d%s\n", num, line)}infile.close()//Writing to file by using inserttion operator("<<")outfile=newFile("./outfile.demo","w")if(outfile==nil){println("opening 'outfile.demo' for writing failed, error:", outfile.message())    os.exit(1)}outfile <<"Hello" << endloutfile <<"world" << endloutfile.close()

Error Handling of standard library

When a standard library function returnsnil orfalse, you can use the return value's message() function for the error message:

file=newFile(filename,"r")if(file==nil){println("opening", filename,"for reading failed, error:", file.message())}//do something with the file//close the filefile.close()letret= http.listenAndServe("127.0.0.1:9090")if(ret==false){println("listenAndServe failed, error:", ret.message())}

Maybe you are curious about whynil orfalse have message() function? Because in monkey,nil andfalseboth are objects, so they have method to operate on it.

Aboutdefer keyword

A defer statement defers the execution of a function until the surrounding function returns.

The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

letadd=fn(x,y){defer println("I'm defer1")println("I'm in add")defer println("I'm defer2")return x+ y}println(add(2,2))

The result is as below:

I'm in addI'm defer2I'm defer14
file=newFile(filename,"r")if(file==nil){println("opening", filename,"for reading failed, error:", file.message())returnfalse}defer file.close()//do other file related stuff, and not need to worry about the file close.//when any file operation error occurs, it will close the file before it returns.

Concatenation of different types

In monkey, you could concatenate of different types. See below for examples:

// Number plus assignmentnum=10num+=10+15.6num+=20println(num)// String plus assignmentstr="Hello"str+="world!"str+=[1,2,3]println(str)// Array plus assignmentarr=[]arr+=1arr+=10.5arr+=[1,2,3]arr+={"key":"value"}println(arr)// Array comparearr1=[1,10.5,[1,2,3],{"key":"value"}]println(arr1)if arr== arr1{ //support ARRAY compareprintln("arr1 = arr")}else{println("arr1 != arr")}// Hash assignment("+=", "-=")hash={}hash+={"key1":"value1"}hash+={"key2":"value2"}hash+={5:"five"}println(hash)hash-="key2"hash-=5println(hash)

Comprehensions

Monkey support list(array,string, range, tuple) comprehensions.list comprehension will return an array.please see following examples:

//array comprehensionx=[[word.upper(), word.lower(), word.title()] for word in["hello","world","good","morning"]]println(x) //result: [["HELLO", "hello", "Hello"], ["WORLD", "world", "World"], ["GOOD", "good", "Good"], ["MORNING", "morning", "Morning"]]//string comprehension (here string is treated like an array)y=[ c.upper() for c in"huanghaifeng"where $_%2!=0] //$_ is the indexprintln(y) //result: ["U", "N", "H", "I", "E", "G"]//range comprehensionw=[i+1 for i in2..10]println(w) //result: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]//tuple comprehensionv=[x+1 for x in(12,34,56)]println(v) //result: [13, 35, 57]//hash comprehensionz=[v*10 for k,v in{"key1":10,"key2":20,"key3":30}]println(z) //result: [100, 200, 300]

Monkey also support hash comprehension.hash comprehension will return a hash.please see following examples:

//hash comprehension (from hash)z1={v:k for k,v in{"key1":10,"key2":20,"key3":30}} //reverse key-value pairprintln(z1) // result: {10 : "key1", 20 : "key2", 30 : "key3"}. Order may differ//hash comprehension (from array)z2={x:x**2 for x in[1,2,3]}println(z2) // result: {1 : 1, 2 : 4, 3 : 9}. Order may differ//hash comprehension (from .. range)z3={x:x**2 for x in5..7}println(z3) // result: {5 : 25, 6 : 36, 7 : 49}. Order may differ//hash comprehension (from string)z4={x:x.upper() for x in"hi"}println(z4) // result: {"h" : "H", "i" : "I"}. Order may differ//hash comprehension (from tuple)z5={x+1:x+2 for x in(1,2,3)}println(z5) // result: {4 : 5, 2 : 3, 3 : 4}. Order may differ

grep and map

Thegrep andmap operators are just like perl'sgrep andmap.

The grep operator takes a list of values and a "testing expression." For each item in the list of values,the item is placed temporarily into the $_ variable, and the testing expression is evaluated. If theexpression results in a true value, the item is considered selected.

The map operator has a very similar syntax to the grep operator and shares a lot of the same operational steps.For example, items from a list of values are temporarily placed into $_ one at a time. However,the testing expression becomes a mapping expression.

letsourceArr=[2,4,6,8,10,12]letm= grep  $_>5,sourceArrprintln('m is{m}')letcp= map $_*2,sourceArrprintln('cp is{cp}')//a little bit more complex exampleletfields={"animal":"dog","building":"house","colour":"red","fruit":"apple"}letpattern= `animal|fruit`// =~(match), !~(unmatch)letvalues=map{fields[$_]} grep{ $_=~ pattern} fields.keys()println(values)

Function

Function in monkey is a first-class object. This means the language supports passing functions as arguments toother functions, returning them as the values from other functions, and assigning them to variables or storingthem in data structures.

Function also could have default parameters and variadic parameters.

//define a functionletadd=fn(){[5,6]}letn=[1,2]+[3,4]+ add()println(n)letcomplex={"add": fn(x, y){returnfn(z){x+ y+ z}}, //function with closure"sub": fn(x, y){ x- y},"other":[1,2,3,4]}println(complex["add"](1,2)(3))println(complex["sub"](10,2))println(complex["other"][2])letwarr=[1+1,3,fn(x){ x+1}(2),"abc","def"]println(warr)println("\nfor i in 5..1 where i > 2 :")foriinfn(x){ x+1}(4)..fn(x){ x+1}(0)where i>2{if(i==3){continue}println('i={i}')}// default parameter and variadic parametersadd=fn(x, y=5, z=7, args...){    w= x+ y+ zforiin args{        w+= i}return w}w=add(2,3,4,5,6,7)println(w)

You could also declare named function like below:

fn sub(x,y=2){return x- y}println(sub(10)) //output : 8

You could also create a function using thefat arrow syntax:

letx=()=>5+5println(x())  //result: 10lety=(x)=> x*5println(y(2)) //result: 10letz=(x,y)=> x* y+5println(z(3,4)) //result :17letadd=fn(x, factor){  x+ factor(x)}result=add(5,(x)=> x*2)println(result)  //result : 15

If the function has no parameter, then you could omit the parentheses. e.g.

println("hhf".upper)  //result: "HHF"//Same as aboveprintln("hhf".upper())

Before ver5.0, Monkey do not support multiple return values, But there are many ways to do it.

Below suggest a way of doing it:

fn div(x, y){if y==0{return[nil,"y could not be zero"]}return[x/y,""]}ret=div(10,5)ifret[1]!=""{println(ret[1])}else{println(ret[0])}

Starting from ver5.0, Monkey support multiple return values using 'let'.The returned values are wrapped as a tuple.

fn testReturn(a, b, c, d=40){return a, b, c, d}let(x, y, c, d)=testReturn(10,20,30)// let x, y, c, d = testReturn(10, 20, 30)  same as aboveprintf("x=%v, y=%v, c=%v, d=%v\n", x, y, c, d)//Result: x=10, y=20, c=30, d=40

Note:You must uselet to support multiple return values, below statement will issue a compile error.

(x, y, c, d)=testReturn(10,20,30) // no 'let', compile errorx, y, c, d=testReturn(10,20,30)   // no 'let', compile error

Pipe Operator

The pipe operator, inspired byElixir.And thanks for the projectAria, I got the idea and some code from this project.

See below for examples:

# Test pipe operator(|>)x=["hello","world"]|> strings.join("")|> strings.upper()|> strings.lower()|> strings.title()printf("x=<%s>\n", x)letadd=fn(x,y){return x+ y}letpow=fn(x){return x**2}letsubtract=fn(x){return x-1}letmm=add(1,2)|>pow()|>subtract()printf("mm=%d\n", mm)"Hello %s!\n"|> fmt.printf("world")

Spawn and channel

You can usespawn to create a new thread, andchan to communicate with the thread.

letaChan=chan()spawn fn(){letmessage= aChan.recv()println('channel received message=<{message}>')}()//send message to threadaChan.send("Hello Channel!")

You could use channel and spawn togeter to support lazy evaluation:

// XRange is an iterator over all the numbers from 0 to the limit.fn XRange(limit){    ch=chan()spawn fn(){        //for (i = 0; i <= limit; i++)  // Warning: Never use this kind of for loop, or else you will get weird results.foriin0..limit{            ch.send(i)}        // Ensure that at the end of the loop we close the channel!        ch.close()}()return ch}foriinXRange(10){    fmt.println(i)}

Usego language modules

Monkey has experimental support for working withgo modules.

If you need to usegos language package fucntion, you first need to useRegisterFunctions' orRegisterVarsto registergo` language functions or types into monkey language.

Below is an example ofmain.go(extracted):

// Because in monkey we already have built in module `fmt`, here we use `gfmt` for package name.eval.RegisterFunctions("gfmt",[]interface{}{    fmt.Errorf,    fmt.Println, fmt.Print, fmt.Printf,    fmt.Fprint, fmt.Fprint, fmt.Fprintln, fmt.Fscan, fmt.Fscanf, fmt.Fscanln,    fmt.Scan, fmt.Scanf, fmt.Scanln,    fmt.Sscan, fmt.Sscanf, fmt.Sscanln,    fmt.Sprint, fmt.Sprintf, fmt.Sprintln,})eval.RegisterFunctions("io/ioutil",[]interface{}{    ioutil.WriteFile, ioutil.ReadFile, ioutil.TempDir, ioutil.TempFile,    ioutil.ReadAll, ioutil.ReadDir, ioutil.NopCloser,})eval.Eval(program, scope)

Now, in your monkey file, you could use it like below:

gfmt.Printf("Hello %s!\n","go function");//Note Here: we use 'io_ioutil', not 'io/ioutil'.letfiles,err= io_ioutil.ReadDir(".")if err!=nil{    gfmt.Println(err)}forfilein files{if file.Name()==".git"{continue}    gfmt.Printf("Name=%s, Size=%d\n", file.Name(), file.Size())}

For more detailed examples, please seegoObj.my.

Standard module introduction

In monkey, there are some standard modules provided for you. e.g. json, sql, sort, fmt, os, logger, time, flag, net, http, etc...This is a brief introduction of some of the monkey standard modules, don't expect it to be thorough.If you are curious, please see the source code.

fmt module

leti,f,b,s,aArr,aHash=108,25.383,true,"Hello, world",[1,2,3,4,"a","b"],{"key1":1,"key2":2,"key3":"abc"}// Use '%v (value)' to print variable value, '%_' to print the variable's typefmt.printf("i=[%05d, %X], b=[%t], f=[%.5f], s=[%-15s], aArr=%v, aHash=%v\n", i, i, b, f, s, aArr, aHash)fmt.printf("i=[%_], b=[%t], f=[%f], aArr=%_, aHash=%_, s=[%s]\n", i, b, f, aArr, aHash, s)sp= fmt.sprintf("i=[%05d, %X], b=[%t], f=[%.5f], s=[%-15s]\n", i, i, b, f, s)fmt.printf("sp=%s", sp)fmt.fprintf(stdout,"Hello %s\n","world")

time module

t1=newTime()format= t1.strftime("%F %R")println(t1.toStr(format))Epoch= t1.toEpoch()println(Epoch)t2= t1.fromEpoch(Epoch)println(t2.toStr(format))

logger module

#Log to stdoutlog=newLogger(stdout,"LOGGER-", logger.LSTDFLAGS | logger.LMICROSECONDS)log.printf("Hello, %s\n","logger")fmt.printf("Logger: flags =<%d>, prefix=<%s>\n", log.flags(), log.prefix())#Log to filefile=newFile("./logger.log","a+")log.setOutput(file)foriin1..5{    log.printf("This is <%d>\n", i)}file.close() //do not forget to close the file

flag module(for handling of command line options)

letverV= flag.bool("version",false,"0.1")letageV= flag.int("age",40,"an int")letheightV= flag.float("height",120.5,"a float")letnameV= flag.string("name","HuangHaiFeng","a string")lethobbiesV= flag.string("hobbies","1,2,3","a comma-delimited string")flag.parse()println("verV =", verV)println("ageV =", ageV)println("heightV =", heightV)println("nameV =", nameV)println("hobbies =", hobbiesV.split(","))if(flag.isSet("age")){println("age is set")}else{println("age is not set")}

json module(for json marshal & unmarshal)

lethsJson={"key1":10,"key2":"Hello Json %s %s Module","key3":15.8912,"key4":[1,2,3.5,"Hello"],"key5": true,"key6":{"subkey1":12,"subkey2":"Json"},"key7": fn(x,y){x+y}(1,2)}lethashStr= json.marshal(hsJson) //same as `json.toJson(hsJson)`println(json.indent(hashStr,""))lethsJson1= json.unmarshal(hashStr)println(hsJson1)letarrJson=[1,2.3,"HHF",[],{"key":10,"key1":11}]letarrStr= json.marshal(arrJson)println(json.indent(arrStr))letarr1Json= json.unmarshal(arrStr)  //same as `json.fromJson(arrStr)`println(arr1Json)

net module

//A simple tcp clientletconn=dialTCP("tcp","127.0.0.1:9090")if(conn==nil){println("dailTCP failed, error:", conn.message())    os.exit(1)}letn= conn.write("Hello server, I'm client")if(n==nil){println("conn write failed, error:", n.message())    os.exit(1)}letret= conn.close()if(ret==false){println("Server close failed, error:", ret.message())}//A simple tcp serverletln=listenTCP("tcp",":9090")for{letconn= ln.acceptTCP()if(conn==nil){println(conn.message())}else{printf("Accepted client, Address=%s\n", conn.addr())}spawn fn(conn){ //spawn a thread to handle the connectionprintln(conn.read())}(conn)} //end forletret= ln.close()if(ret==false){println("Server close failed, error:", ret.message())}

linq module

In monkey, thelinq module support seven types of object:

  • File object (create usingnewFile builtin function)
  • Csv reader object (created usingnewCsvReader builtin function)
  • String object
  • Array object
  • Tuple object
  • Hash object
  • Channel object (created usingchan builtin function)
letmm=[1,2,3,4,5,6,7,8,9,10]println('before mm={mm}')result= linq.from(mm).where(fn(x){    x%2==0}).select(fn(x){    x= x+2}).toSlice()println('after result={result}')result= linq.from(mm).where(fn(x){    x%2==0}).select(fn(x){    x= x+2}).last()println('after result={result}')letsortArr=[1,2,3,4,5,6,7,8,9,10]result= linq.from(sortArr).sort(fn(x,y){return x> y})println('[1,2,3,4,5,6,7,8,9,10] sort(x>y)={result}')result= linq.from(sortArr).sort(fn(x,y){return x< y})println('[1,2,3,4,5,6,7,8,9,10] sort(x<y)={result}')thenByDescendingArr=[{"Owner":"Google","Name":"Chrome"},{"Owner":"Microsoft","Name":"Windows"},{"Owner":"Google","Name":"GMail"},{"Owner":"Microsoft","Name":"VisualStudio"},{"Owner":"Google","Name":"GMail"},{"Owner":"Microsoft","Name":"XBox"},{"Owner":"Google","Name":"GMail"},{"Owner":"Google","Name":"AppEngine"},{"Owner":"Intel","Name":"ParallelStudio"},{"Owner":"Intel","Name":"VTune"},{"Owner":"Microsoft","Name":"Office"},{"Owner":"Intel","Name":"Edison"},{"Owner":"Google","Name":"GMail"},{"Owner":"Microsoft","Name":"PowerShell"},{"Owner":"Google","Name":"GMail"},{"Owner":"Google","Name":"GDrive"}]result= linq.from(thenByDescendingArr).orderBy(fn(x){returnx["Owner"]}).thenByDescending(fn(x){returnx["Name"]}).toOrderedSlice()    //Note: You need to use toOrderedSlice//use json.indent() for formatting the outputletthenByDescendingArrStr= json.marshal(result)println(json.indent(thenByDescendingArrStr,""))//test 'selectManyByIndexed'println()letselectManyByIndexedArr1=[[1,2,3],[4,5,6,7]]result= linq.from(selectManyByIndexedArr1).selectManyByIndexed(fn(idx, x){if idx==0{return linq.from([10,20,30])}return linq.from(x)},fn(x,y){return x+1})println('[[1,2,3],[4,5,6,7]] selectManyByIndexed()={result}')letselectManyByIndexedArr2=["st","ng"]result= linq.from(selectManyByIndexedArr2).selectManyByIndexed(fn(idx,x){if idx==0{return linq.from(x+"r")}return linq.from("i"+ x)},fn(x,y){return x+"_"})println('["st","ng"] selectManyByIndexed()={result}')

Linq for file

Now, monkey has a powerfullinq for file support. it can be used to operatefiles a little bit like awk. See below for example:

//test: linq for "file"file=newFile("./examples/linqSample.csv","r") //open linqSample.csv file for readingresult= linq.from(file,",",fn(line){ //the second parameter is field separator, the third is a selector functionif line.trim().hasPrefix("#"){ //if line start '#'returntrue // return 'true' means we ignore this line}else{returnfalse}}).where(fn(fields){    //The 'fields' is an array of hashes, like below:    //  fields = [    //      {"line": LineNo1, "nf": line1's number of fields, 0: line1, 1: field1, 2: field2, ...},    //      {"line": LineNo2, "nf": line2's number of fields, 0: line2, 1: field1, 2: field2, ...}    //  ]int(fields[1])>300000 //only 1st Field's Value > 300000}).sort(fn(field1,field2){returnint(field1[1])>int(field2[1]) //sort with first field(descending)}).select(fn(fields){fields[5]  //only output the fifth field})println(result)file.close() //do not forget to close the file//another test: linq for "file"file=newFile("./examples/linqSample.csv","r") //open linqSample.csv file for readingresult= linq.from(file,",",fn(line){ //the second parameter is field separator, the third is a selector functionif line.trim().hasPrefix("#"){ //if line start '#'returntrue //return 'true' means we ignore this line}else{returnfalse}}).where(fn(fields){int(fields[1])>300000 //only 1st Field's Value > 300000}).sort(fn(field1,field2){returnint(field1[1])>int(field2[1]) //sort with first field(descending)}).selectMany(fn(fields){    row=[[fields[0]]] //fields[0] is the whole line, we need two "[]"s, otherwise selectMany() will flatten the output.    linq.from(row)  //output the whole records})println(result)file.close() //do not forget to close the file//test: linq for "csv"r=newCsvReader("./examples/test.csv") //open test.csv file for readingr.setOptions({"Comma":";","Comment":"#"})result= linq.from(r).where(fn(x){    //The 'x' is an array of hashes, like below:    //  x = [    //      {"nf" : line1's number of fields, 1: field1, 2: field2, ...},    //      {"nf" : line2's number of fields, 1: field1, 2: field2, ...}    //  ]x[2]=="Pike"//only 2nd Field = "Pike"}).sort(fn(x,y){returnlen(x[1])>len(y[1]) //sort with length of first field})println(result)r.close() //do not forget to close the reader

csv module

//test csv readerletr=newCsvReader("./examples/test.csv")if r==nil{printf("newCsv returns err, message:%s\n", r.message())}r.setOptions({"Comma":";","Comment":"#"})ra= r.readAll()if(ra==nil){printf("readAll returns err, message:%s\n", ra.message())}forlinein ra{println(line)forrecordin line{println("", record)}}r.close() //do not forget to close the reader//test csv writerletofile=newFile("./examples/demo.csv","a+")letw=newCsvWriter(ofile)w.setOptions({"Comma":""})w.write(["1","2","3"])w.writeAll([["4","5","6"],["7","8","9"],["10","11","12"]])w.flush()ofile.close() //do not forget to close the file

template module

Thetemplate module contains 'text' and 'html' template handling.

UsenewText(...) orparseTextFiles(...) to create a new 'text' template.

UsenewHtml(...) orparseHtmlFiles(...) to create a new 'html' template.

arr=[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"},{"key":"key3","value":"value3"}]//use parseTextFiles(), write to a stringtemplate.parseTextFiles("./examples/looping.tmpl").execute(resultValue, arr)println('{resultValue}')//use parseTextFiles(), write to a filefile=newFile("./examples/outTemplate.log","a+")template.parseTextFiles("./examples/looping.tmpl").execute(file, arr)file.close() //do not to forget to close the file//use parse()//Note here: we need to use "{{-" and "-}}" to remove the newline from the outputtemplate.newText("array").parse(`Looping{{- range.}}        key={{.key}}, value={{.value-}}{{- end}}`).execute(resultValue, arr)println('{resultValue}')

sql module

Thesql module provides a lower abstraction layer for working with database.

It should correctly handle database null values, though not throughly tested.

For testingsql module, you need to do following:

  1. Download sql driver source.

  2. Include the package in 'sql.go' like below:

_"github.com/mattn/go-sqlite3"
  1. Recompile monkey source.

Below is a complete source of theexamples/db.my:

letdbOp=fn(){    os.remove("./foo.db") //delete `foo.db` fileletdb=dbOpen("sqlite3","./foo.db")if(db==nil){println("DB open failed, error:", db.message())returnfalse}defer db.close()letsqlStmt= `create table foo(id integer not null primary key, name text);delete from foo;`letexec_ret= db.exec(sqlStmt)if(exec_ret==nil){println("DB exec failed! error:", exec_ret.message())returnfalse}lettx= db.begin()if(tx==nil){println("db.Begin failed!, error:", tx.message())returnfalse}letstmt= tx.prepare(`insert into foo(id, name) values(?,?)`)if(stmt==nil){println("tx.Prepare failed!, error:", stmt.message())returnfalse}defer stmt.close()leti=0    for(i=0; i<105; i++){letname="您好"+ iif(i>100){            //insert `null` value. There are seven predefined values:INT_NULL,UINT_NULL,FLOAT_NULL,STRING_NULL,BOOL_NULL,TIME_NULL, DECIMAL_NULL.letrs= stmt.exec(i, sql.STRING_NULL)}else{letrs= stmt.exec(i, name)}if(rs==nil){println("statement exec failed, error:", rs.message())returnfalse}} //end for    tx.commit()letid,name=0,""letrows= db.query("select id, name from foo")if(rows==nil){println("db queue failed, error:", rows.message())returnfalse}defer rows.close()while(rows.next()){        rows.scan(id, name)if(name.valid()){ //check if it's `null`println(id,"|", name)}else{println(id,"|","null")}}returntrue}letret=dbOp()if(ret==nil){    os.exit(1)}os.exit()

About regular expression

In monkey, regard to regular expression, you could use:

  • Regular expression literal
  • 'regexp' module
  • =~ and !~ operators(like perl's)
//Use regular expression literal( /pattern/.match(str) )letregex=/\d+\t/.match("abc 123mnj")if(regex){println("regex matched using regular expression literal")}//Use 'regexp' moduleif regexp.compile(`\d+\t`).match("abc 123mnj"){println("regex matched using 'regexp' module")}//Use '=~'(str =~ pattern)if"abc 123mnj"=~ `\d+\t`{println("regex matched using '=~'")}else{println("regex not matched using '=~'")}
Note: For detailed explanation of'Regular Expression' pattern matching, you could see golang's regexp module for reference.

Useful Utilities

Included has some useful utilities likeformatter andhighlighter.

The formatter utility can format the monkey language.The highlighter utility can highlight the monkey language to console or html.

You could also combine the two utilities:

./fmt xx.my| ./highlight  //output to console(console highlight not support windows)

Document generator

Included also has a tool(mdoc) for generating documentation in markdown format or html format

The tool only support below statement for document generator:

  • let statement
  • enum statement
  • function statement
  • class statement
    • let statement
    • function statement
    • property statement
//generate markdown file, the generated file is named'doc.md'./mdoc examples/doc.my//generate html file, the generated file is named'doc.html'./mdoc -html examples/doc.my//generate html file, also generatesource code of classes and functions. the generated file is named'doc.html'./mdoc -html -showsource examples/doc.my//Use the somebuiltin css typesfor styling the generated html//    0 - GitHub//    1 - Zenburn//    2 - Lake//    3 - Sea Side//    4 - Kimbie Light//    5 - Light Blue//    6 - Atom Dark//    7 - Forgotten Light./mdoc -html -showsource -css 1 examples/doc.my//Using external css filefor styling the generated html file.//The'-cssfile' option has higher priority than the'-css' option.//If the supplied css file does not exists,then the'-css' option will be used../mdoc -html -showsource -css 1 -cssfile ./examples/github-markdown.css examples/doc.my//processing all the'.my' filesin examples directory, generate html../mdoc -html examples

The generating of HTML document is base on github REST API,so you must have network connection to make it work.You may also need to set proxy if you behind a firewall(Environ variable:HTTP_PROXY).

Look at below to see how documentation generated by mdoc looks like.

Because github can not render html directly, you could use(http://htmlpreview.github.io/) to review the generated html.

Syntax Highlight

Currently there are below kinds of syntax highlight for editors:

  1. vim

    vim

  2. emeditor

    emeditor

  3. notepad++

    notepad++

  4. Visual Studio Code

    VSC

  5. Sublime Text 3

    Sublime Text 3

Future Plans

There are some other things i plan to do:

  • Improve the Standard Library with more functions.
  • Write more tests!
  • Improve this document with more explanation of the language.
  • Rewrite the demo program for better understanding of the language.
  • Rewrite the 'include' module logic.
  • Add support for if-elseif-else expression.

License

MIT


[8]ページ先頭

©2009-2025 Movatter.jp