Monkey Programming Language (v4.0 release)

The new features are as follows

  • Add “unsigned Int “base datatype (let u = 10u)
  • Added simple object oriented (class) support
    • Inheritance and polymorphism
    • Indexer (supports multiple indexers and indexers support multiple parameters)
    • Operator overloading
    • Properties (getters and setters)
    • Static methods/members/properties
  • Enhanced REPL(REPL supports real-time syntax highlighting)
  • If the function has no arguments, the parentheses can be omitted when the function is called
  • Add arbitrary precision floating point operation type (Decimal)
  • Fixed bug where return statements do not work in loops

The home page

monkey

An overview of the

Monkey is a parser written in the GO language. Syntax borrowed from C, Ruby, Python, Perl, and C#. Support common control flow, functional programming and object-oriented programming. It also includes a REPL with real-time syntax highlighting.

Here is an example program using the Monkey language:

// A pseudo-class using function with closure
fn Person(name, age) {
    self = {} // Create an empty hash, you can also use 'self = hash()'
    self.name = name
    self.age = age

    // You can also use 'self.getName = fn() {return self.name}'
    self.getName = () -> return self.name
    self.getAge  = () -> return self.age
    self.message = () -> return self.name + ", aged " + str(self.age)

    self.sets = fn(newName, newAge) {
        self.name = newName
        self.age = newAge
    }

    return self
}

p = Person("Mike".40)
printf("1 - info = %v\n", p.message())
printf("1 - name = %v\n", p.getName())
printf("1 - age = %v\n", p.getAge())


printf("\n=========================\n\n")

p.sets("HHF".42)
printf("2 - info = %v\n", p.message())
printf("2 - name = %v\n", p.getName())
printf("2 - age = %v\n", p.getAge())
Copy the code

The overview

This project is based on the Mayoms project Monkey, which fixes some of the bugs and adds many language features:

  • To change thestringModule (can handle UTF8 character encodings correctly)
  • To modify thefileModule (contains some new methods).
  • increasedmathThe module
  • increasedsql(db)Module (can handle correctlynullValue)
  • increasedtimeThe module
  • increasedsortThe module
  • increasedosThe module
  • increasedlogThe module
  • increasednetThe module
  • increasedhttpThe module
  • increasedfilepathThe module
  • increasedflagModule (for processing command-line arguments)
  • increasedjsonModules (JSON serialization and deserialization)
  • increasedfmtThe module
  • increasedsyncThe module
  • increasedlistThe module
  • increasedlinqModule (code fromlinqAnd changed accordingly)
  • increasedcsvThe module
  • increasedtemplateThe module
  • increaseddecimalModule (code fromdecimalAnd made minor changes accordingly)
  • Regular expression support (partially similar to Perl)
  • Channels (Channels based on the GO language)
  • More operator support (&&, | |, &, |, ^, + =, – =? , etc.)
  • Utf8 support (for example, you can use utF8 characters as variable names)
  • More flow control support (e.g., try/catch/finally, for-in, case-in, c-like for loops)
  • Defer to support
  • Spawn support (goroutine)
  • Enum support and
  • Pipe operator support
  • Functions that support variable and default arguments
  • Support for List Comprehension and Hash Comprehension
  • Simple object Oriented (OOP) support

The main objectives of this project are as follows:

  • Teach yourself go
  • Understand how the parser works

However, the speed of the parser is not a factor in this project

The installation

Download this project and run. /run.sh

Basic usage

You can use the REPL as follows:

~ » monkey
Monkey programming language REPL


>>
Copy the code

REPL

Or run a monkey file:

monkey path/to/file
Copy the code

Language tour

annotation

Monkey only supports single-line comments.

// an inline comment
# another inline comment
Copy the code

The data type

Monkey supports nine basic types: String, Int, UInt, Float, Bool, Array, Hash, Tuple, and Nil

s1 = "Hello, huang"       # strings are UTF-8 encoded
s2 = `hello, "world"`  # raw string
i = 10                 # int
u = 10u                # uint
f = 10.0               # float
b = true               # bool
a = [1."2"]           # array
h = { "a"= >1."b"= >2}  # hash
t = (1.2.3)            # tuple
n = nil
Copy the code

Constants (literals)

There are 11 main types of constants (literals) in Monkey.

  • Integer
  • UInteger
  • Float
  • String
  • Regular expression
  • Array
  • Hash
  • Tuple
  • Nil
  • Boolean
  • Function
// Integer literals
i1 = 10
i2 = 20 _000_000
i3 = 0x80           // hex
i4 = 0b10101        // binary
i5 = 0c127          // octal

// Unsigned Integer literals
ui1 = 10u
ui2 = 20_000_000u     //for more readable
ui3 = 0x80u           // hex
ui4 = 0b10101u        // binary
ui5 = 0c127u          // octal

// Float literals
f1 = 10.25
f2 = 1.02 e3
f3 = 123 _456. 789 _012

// String literals
s1 = "123"
s2 = "Hello world"

// Regular expression literals
r = /\d+/.match("12")
if (r) { prinln("regex matched!")}// Array literals
a = [1+2.3.4."5".3]

// Hash literals
h = { "a"= >1."b"= >2."c"= >2}

//Tuple literals
t = (1.2+3."Hello".5)

// Nil literal
n = nil

// Boolean literals
t = true
f = false

// Function literals
let f1 = add(x, y) { return x + y }
println(f1(1.2))

//short-arrow function literals
let f2 = (x, y) -> x + y
println(f2(1.2))
Copy the code

variable

You can declare a variable using let, or you can declare and assign a variable directly using assignment :variable=value.

let a, b, c = 1."hello world"[1.2.3]
d = 4
e = 5Name ="Yellow"
Copy the code

If you do not use let to assign to variables, you will not be able to use multivariable assignment. The following statement is incorrect:

// Error, multivariable assignment must use let keyword
a, b, c = 1."hello world"[1.2.3]
Copy the code

Reserved words

Here are the reserved words for the Monkey language:

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

Type conversion

You can convert between different types using the built-in methods: int(), uint(), float(), STR (), array(), tuple, hash, decimal.

let i = 0xa
let u = uint(i)                 // result: 10
let s = str(i)                  // result: "10"
let f = float(i)                // result: 10
let a = array(i)                // result: [10]
let t = tuple(i)                // result: (10,)
let h = hash(("key"."value"))  // result: {"key"=>"value}
let d = decimal("123.45634567") / / result: 123.45634567
Copy the code

You can create a tuple from an array:

let t = tuple([10.20])   / / the result: (10, 20)
Copy the code

Similarly, you can create an array from a tuple:

let arr = array((10.20))  / / the result: [10, 20]
Copy the code

You can only create a hash from arrays or tuples:

// Create an empty hash
let h1 = hash()  //same as h1 = {}

// Create hash from array
let h1 = hash([10.20])     //result: {10 => 20}
let h2 = hash([10.20.30])   //result: {10 => 20, 30 => nil}

// Create hash from tuple
let h3 = hash((10.20))     //result: {10 => 20}
let h4 = hash((10.20.30))   //result: {10 => 20, 30 => nil}
Copy the code

qw(Quote word) keyword

The qW keyword is similar to the Perl QW keyword. Qw is a great help when you want to use a lot of double-quoted strings.

for str in qw<abc, def, ghi, jkl, mno> { // Pair operators allowed: '{}', '<>', '()'
  println('str={str}')
}

newArr = qw(1.2.3.5) // newArr is a string array, not an integer array.
fmt.printf("newArr=%v\n", newArr)
Copy the code

enumThe 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.LstdFlags
println(opt)

// Get all names of 'enum'
for s in LogOption.getNames() { // Non-ordered
    println(s)
}

// Get all values of 'enum'
for s in LogOption.getValues() { // Non-ordered
    println(s)
}

// Get a specific name for 'enum'
println(LogOption.getName(LogOption.Lshortfile))
Copy the code

Control process

  • if/if-else/if-elif-else/if-elsif-else/if-elseif-else
  • unless/unless-else
  • for/for-in
  • while
  • do
  • try-catch-finally
  • case-in/case-is
// if-else
let a, b = 10.5
if (a > b) { // '()' is optional, but '{}' is required
    println("a > b")
}
elseif a == b { // 'elsif', 'elseif' and 'elif' can also be used
    println("a = b")}else {
    println("a < b")}//unless-else
unless b > a {
    println("a >= b")}else {
    println("b > a")}// for
i = 9
for { // Infinite loop
    i = i + 2
    if (i > 20) { break }
    println('i = {i}')
}


i = 0
for (i = 0; i < 5; i++) {  // Similar to the C for loop, '()' must have
    if (i > 4) { break }
    if (i == 2) { continue }
    println('i is {i}')
}


for i in range(10) {
    println('i = {i}')
}

a = [1.2.3.4]
for i in a where i % 2! =0 {
    println(i)
}


hs = {"a"= >1."b"= >2."c"= >3."d"= >4."e"= >5."f"= >6."g"= >7}
for k, v in hs where v % 2= =0 {
    println('{k} : {v}')
}


for i in 1.5 {
    println('i={i}')
}

for item in 10.20 where $_ % 2= =0 { // $_ is the index
    printf("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 index
    printf("idx=%d, item=%s\n", $_, v)
}


// The for loop is an expression, not a statement, so it can be assigned to a variable
let plus_one = for i in [1.2.3.4] { i + 1 }
fmt.println(plus_one)

// while
i = 10
while (i>3) {
    i--
    println('i={i}')
}

// do
i = 10
do {
    i--
    if (i==3) { break}}// try-catch-finally(throw a string variable only)
let exceptStr = "SUMERROR"
try {
    let th = 1 + 2
    if (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-is
let testStr = "123"
case testStr in { // in(complete or partial match), is(complete 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")}}Copy the code

Integer (Integer)

In Monkey, an integer is also an object. Therefore, you can call methods on this object. Look at the following example:

x = (-1).next()
println(x) / / 0

x = -1.next() //equals 'x = -(1.next())
println(x) / / - 2

x = (-1).prev()
println(x) / / - 2

x = -1.prev() //equals 'x = -(1.prev())
println(x) / / 0

x = [i for i in 10.upto(15)]
println(x) //[10, 11, 12, 13, 14, 15]

for i in 10.downto(5) {
    print(i, "") // 5

}
println(a)if 10.isEven() {
    println("10 is even")}if 9.isOdd() {
    println("9 is odd")}Copy the code

Floating point (Float)

In Monkey, floating point is also an object. Therefore, you can call methods on this object. Look at the following example:

f0 = 15.20
println(f0)

f1 = 15.20.ceil()
println(f1)

f2 = 15.20.floor()
println(f2)
Copy the code

A Decimal type

In the Monkey, the Decimal type represents a fixed-point Decimal number with Arbitrary precision. This type of code is primarily based on Decimal.

Look at the following example:

d1 = decimal.fromString("123.45678901234567")  // Creates a Decimal type from a string
d2 = decimal.fromFloat(3)  // Create a Decimal type from a floating-point type

// Set the division precision.
// Note: This operation affects all subsequent operations on Decimal type
decimal.setDivisionPrecision(50)

fmt.println("123.45678901234567/3 =", d1.div(d2))  / / print d1 / d2
fmt.println(d1.div(d2)) // same as above

fmt.println(decimal.fromString("123.456").trunc(2))

// Converts the string to decimal
d3=decimal("123.45678901234567")
fmt.println(d3)
fmt.println("123.45678901234567/3 =", d3.div(d2))
Copy the code

Array (Array)

In Monkey, you can initialize an empty array with [] :

emptyArr = []
emptyArr[3] = 3 // The capacity will be automatically expanded
println(emptyArr)
Copy the code

Arrays can contain elements of any data type.

mixedArr = [1.2.5."Hello"["Another"."Array"] and {"Name"= >"HHF"."SEX"= >"Male"}]
Copy the code

Note: The comma (‘, ‘) before closing the square bracket (‘]’) can be omitted.

You can use indexes to access array elements.

println('mixedArr[2]={mixedArr[2]} ')println(["a"."b"."c"."d"] [2])
Copy the code

Because an array is an object, you can manipulate it using object methods.

if ([].empty()) {
    println("array is empty")
}

emptyArr.push("Hello")
println(emptyArr)

// You can add an element to the array using 'add (+=)' :
emptyArr += 2
println(emptyArr)
Copy the code

You can use a for loop to iterate over an array.

numArr = [1.3.5.2.4.6.7.8.9]
for item in numArr where item % 2= =0 {
    println(item)
}

let strArr = ["1"."a5"."5"."5b"."4"."cc"."Seven"."dd"."9"]
for item in strArr where /^\d+/.match(item) {
    println(item)
}

for item in ["a"."b"."c"."d"] where $_ % 2= =0 {  / / $_ is indexed
    printf("idx=%d, v=%s\n", $_, item)
}
Copy the code

You can use the built-in function reverse to reverse array elements:

let arr = [1.3.5.2.4.6.7.8.9]
println("Source Array =", arr)

revArr = reverse(arr)
println("Reverse Array =", revArr)
Copy the code

String (String)

There are three types of strings in monkey:

  • Native string (can contain\n)
  • Double quoted string (not included\n)
  • Single quoted strings (parsed strings)

A native string is a sequence of characters. Use backquotes (‘ ‘). In native strings, you can use any character except backquotes.

Look at the following example:

normalStr = "Hello " + "world!"
println(normalStr)

println("123456"[2])

rawStr = `Welcometo visit us! `println(rawStr)

// You can use single quotes when you want a variable to be parsed in a string.
// The string to be parsed is placed in curly braces ('{}') :
str = "Hello world"
println('str={str}') // Output: "Hello world"
str[6] ="W"
println('str={str}') // Output: "Hello World"

Copy the code

In monkey, strings are encoded at UTF8, which means you can use utF8-encoded characters as variable names:

3 =35 =5
println(三 + 五) // Output: 8
Copy the code

Strings are also objects, and you can manipulate strings using methods in the Strings module:

upperStr = "hello world".upper()
println(upperStr) // Output: HELLO WORLD
Copy the code

Strings can also be traversed:

for idx, v in "abcd" {
    printf("idx=%d, v=%s\n", idx, v)
}

for v in "Hello World" {
    printf("idx=%d, v=%s\n", $_, v) / / $_ is indexed
}
Copy the code

You can concatenate an object to a string:

joinedStr = "Hello " + "World"
joinedStr += "!"
println(joinedStr)
Copy the code

You can also use the built-in function reverse to reverse a string:

let str = "Hello world!"
println("Source Str =", str)
revStr = reverse(str)
println("Reverse str =", revStr)
Copy the code

Hash (Hash)

In monkey, use {} to create an empty hash:

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

A hash key can be a string, an int, or a Boolean:

hashObj = {
    12= >"twelve".true= >1."Name"= >"HHF"
}
println(hashObj)
Copy the code

Note: The comma (‘, ‘) before closing the curly bracket (‘}’) can be omitted.

You can also use ‘+’ or ‘-‘ to add or remove an element from a hash:

hashObj += {"key1"= >"value1"}
hashObj += {"key2"= >"value2"}
hashObj += {5= >"five"}
hashObj -= "key2"
hashObj -= 5
println(hash)
Copy the code

A hash is also an object, and you can manipulate it using methods in the Hash module:


hashObj.push(15."fifteen") // The first argument is the key, and the second argument is the value
hashObj.pop(15)

keys = hashObj.keys()
println(keys)

values = hashObj.values()
println(values)
Copy the code

You can also use the built-in reverse function to reverse the hash key and value:

let hs = {"key1"= >12."key2"= >"HHF"."key3"= >false}
println("Source Hash =", hs)
revHash = reverse(hs)
println("Reverse Hash =", revHash)
Copy the code

Tuples (a Tuple)

In Monkey, tuples are very similar to arrays, but once you create a meta-ancestor, you can’t change it.

Tuples are created using parentheses:

// Create an empty ancestor
let t1 = tuple()

// same as above
let t2 = ()

// Create a progenitor with only one element.
// Note: the trailing "," is required, otherwise it will be resolved to (1) instead of the ancestor
let t3 = (1.)// Create a meta-ancestor with two elements
let t4 = (2.3)
Copy the code

You can use the built-in function tuple to render any type of object as a primitive.

let t = tuple("hello")
println(t)  // Result: ("hello")
Copy the code

Like arrays, primitives can be indexed, or sliced. The index expression tuple[I] returns the primitive element at the ith index position, and the slice expression tuple[I :j] returns a child primitive.

let t = (1.2.3) [2]
print(t) // result:3
Copy the code

Primitives can also be iterated over (like arrays), so primitives can be used in for loops and list derivations.

/ / a for loop
for i in (1.2.3) {
    println(i)
}

// Comprehension
let t1 =  [x+1 for x in (2.4.6)]
println(t1) //result: [3, 5, 7]
Copy the code

Unlike arrays, primitives cannot be modified. But the mutable elements inside a meta-ancestor can 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)
Copy the code

The ancestor can also be used as a key in a hash.

key1=(1.2.3)
key2=(2.3.4)
let ht = {key1 => 10, key2 =>20}
println(ht[key1]) // result: 10
println(ht[key2]) // result: 20
Copy the code

A meta-ancestor can be connected using +, which creates a new meta-ancestor.

let t = (1.2) + (3.4)
println(t) // Result: 1, 2, 3, 4
Copy the code

If a primitive is used in a Boolean environment, then true is returned if the number of elements in the primitive is greater than zero.

let t = (1.)if t {
    println("t is not empty!")}else {
    println("t is empty!")}// Result: "t is not empty!"
Copy the code

Json serialization (deserialization) of primitives results in arrays, not primitives

let tupleJson = ("key1"."key2")
let tupleStr = json.marshal(tupleJson)
// Result: [
/ / "key1",
/ / "key2",
/ /]
println(json.indent(tupleStr, ""))

let tupleJson1 = json.unmarshal(tupleStr)
println(tupleJson1) // Result: ["key1", "key2"]
Copy the code

Adding a primitive to an array returns an array, not a primitive.

t2 = (1.2.3) + [4.5.6]
println(t2) // Result: [(1, 2, 3), 4, 5, 6]
Copy the code

You can also use the built-in function reverse to reverse elements in a primitive:

let tp = (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)
Copy the code

class

Monkey supports simple object-oriented programming, and the features Mokey supports are listed below:

  • Inheritance and polymorphism
  • Operator overloading
  • Properties (getters and setters)
  • Static variables/methods/properties
  • The indexer

The Monkey parser correctly handles the keywords public, private, and protected, but the interpreter (evaluator) ignores these. That said, monkey does not support access restrictions for now.

You can declare a class using the class keyword and create an instance of the class using new Class(XXX).

class Animal {
    let name = ""
    fn init(naem) {    //'init' is the constructor
        //do somthing}}Copy the code

In the Monkey, all classes inherit from the object root class. The Object root class contains several methods common to all classes. Examples include toString(), instanceOf(), is_a(), classOf(), hashCode. InstanceOf () is equivalent to is_a()

The following code is equivalent to the above:

class Animal : object {
    let name = ""
    fn init(naem) {    //'init' is the constructor
        //do somthing}}Copy the code

Inheritance and polymorphism

You use: to indicate inheritance:

class Dog : Animal { //Dog is descended from Animal
}
Copy the code

In subclasses, you can use parent to access the methods and fields of the base class.

Look at the following example:

class Animal {
    let Name;

    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]

for animal in animals
{
    println("Animal name: " + animal.Name)
    animal.MakeNoise(a)println(animal.ToString())
    if is_a(animal, "Dog") {
        animal.OnlyDogMethod()}}Copy the code

The running results are as follows:

Animal name: pearl
Meow
pearl cat
Animal name: cole
Woof!
cole dog
secret dog only method
Animal name: nil
generic noise
oooooooo
Copy the code

Operator overloading

class Vector {
    let x = 0;
    let y = 0;

    // constructor
    fn init (a, b, c) {
        if(! a) { a =0; }if(! b) {b =0; } x = a; y = b } fn +(v) {/ / reloading the '+'
        if (type(v) == "INTEGER" {
            return new Vector(x + v, y + v);
        } elseif v.is_a(Vector) {
            return new 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);
    
    // The following code calls the '+' method on the Vector
    v3 = v1 + v2 V3 = v1.+(v2)
    / / return "(5), (7)"
    println(v3.String());
    
    v4 = v1 + 10 // equivalent to v4 = v1.+(10);
    / / return "(11), (12)"
    println(v4.String());
}

Vectormain(a)Copy the code

Properties (similar to C#)

class Date {
    let month = 7;  // Backing store
    property Month
    {
        get { return month }
        set {
            if ((value > 0) && (value < 13))
            {
                month = value
            } else {
               println("BAD, month is invalid")
            }
        }
    }

    property Year { get; set; } propertyDay { 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 = 10
printf("dateObj.Month=%d\n", dateObj.Month)

dateObj.Year = 2018
println()
dateObj.getDateInfo()

// The following code will report an error because OtherInfo1 is read-only
//dateObj.OtherInfo1 = "Other Date Info"
//println(dateObj.OtherInfo1)

// The following code will report an error because OtherInfo2 is a write-only attribute
//dateObj.OtherInfo2 = "Other Date Info2"
//println(dateObj.OtherInfo2)

// The following code will report an error because the Day attribute is read-only
//dateObj.Day = 18
Copy the code

Monkey also supports an Indexer like C#. Indexers allow you to access objects as if they were arrays.

Indexers are declared as follows:

Declare an indexer using the property this[parameter] method.

property this[index] {
    get { xxx }
    set { xxx }
}
Copy the code

Take a look at the following code:

class IndexedNames
{
    let namelist = []
    let size = 10
    fn init()
    {
        let i = 0
        for (i = 0; i < size; i++)
        {
            namelist[i] = "N. A."
        }
    }

    fn getNameList() {
        println(namelist)
    }

    property this[index]
    {
        get
        {
            let tmp;
            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(a)// The following code calls setter methods of the indexer
    namesObj[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]) // Call the indexer's getter}}Main(a)Copy the code

Static variables/methods/properties

class Test
{
   static let x = 0;
   static let y = 5;

   static fn Main()
   {
      println(Test.x);
      println(Test.y);

      Test.x = 99;
      println(Test.x); }}Test.Main(a)Copy the code

Standard input/output/error

The following three objects are predefined in Monkey: stdin, stdout, and stderr. They represent standard input, standard output, and standard error

stdout.writeLine("Hello world")
// Same effect as above
fmt.fprintf(stdout, "Hello world\n")

print("Please type your name:")
name = stdin.read(1024)  // Read up to 1024 bytes from standard input
println("Your name is " + name)
Copy the code

Error handling in the standard library

When library functions return nil or false, you can use their message() method class to get an error message:

file = newFile(filename, "r")
if (file == nil) {
    println("opening ", filename, "for reading failed, error:", file.message())
}

// Manipulate files
/ /...

// Close the file
file.close()


let ret = http.listenAndServe("127.0.0.1:9090")
if (ret == false) {
    println("listenAndServe failed, error:", ret.message())
}

Copy the code

You might wonder, why does nil or false have a message() method? Because in the monkey, nil and false are both objects, so they both have methods.

aboutdeferThe keyword

The defer statement delays the execution of a function until it returns.

let add  =  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))
Copy the code

The results are as follows:

I'm in add
I'm defer2
I'm defer1
4
Copy the code

Different types of joins

In Monkey, you can join different types. Look at the following example:

// Number plus assignment
num = 10
num += 10 + 15.6
num += 20
println(num)

// String plus assignment
str = "Hello "
str += "world! "
str += [1.2.3]
println(str)

// Array plus assignment
arr = []
arr += 1
arr += 10.5
arr += [1.2.3]
arr += {"key"= >"value"}
println(arr)

// Array compare
arr1 = [1.10.5[1.2.3] and {"key"= >"value"}]
println(arr1)
if arr == arr1 { //support ARRAY compare
    println("arr1 = arr")}else {
    println("arr1 ! = arr")}// Hash assignment("+=", "-=")
hash = {}
hash += {"key1"= >"value1"}
hash += {"key2"= >"value2"}
hash += {5= >"five"}
println(hash)
hash -= "key2"
hash -= 5
println(hash)
Copy the code

Comprehensions are Comprehensions.

Monkey supports list inference (lists can be arrays, strings, ranges, tuples, hashes). List derivations return arrays. Look at the following example:

/ / array
x = [[word.upper(), word.lower(), word.title()] for word in ["hello"."world"."good"."morning"]]
println(x) / / the result: [["HELLO", "hello", "Hello"], ["WORLD", "world", "World"], ["GOOD", "good", "Good"], ["MORNING", "morning", "Morning"]]


/ / string
y = [ c.upper() for c in "huanghaifeng" where $_ % 2! =0] //$_ is the index
println(y) // Result: ["U", "N", "H", "I", "E", "G"]

/ / range
w = [i + 1 for i in 1.10]
println(w) // Result: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

//tuple
v = [x+1 for x in (12.34.56)]
println(v) // Results: [13, 35, 57]

/ / hash
z = [v * 10 for k,v in {"key1"= >10."key2"= >20."key3"= >30}]
println(z) // Result: [100, 200, 300]
Copy the code

Monkey also supports hash derivation. The return value of the hash derivation is hash. Look at the following example:

// From hash
z1 = { v:k for k,v in {"key1"= >10."key2"= >20."key3"= >30}} //reverse key-value pair
println(z1) / / results: {10 = > "key1", 20 = > "key2", 30 = > "key3"}, order may be different

// hash from array
z2 = {x:x**2 for x in [1.2.3]}
println(z2) {1 => 1, 2 => 4, 3 => 9

// hash derivation (from.. range)
z3 = {x:x**2 for x in 5.7}
println(z3) // Result: {5 => 25, 6 => 36, 7 => 49}

// Hash derivation (from string)
z4 = {x:x.upper() for x in "hi"}
println(z4) // result: {"h" => "h", "I" => "I"}

// from tuple
z5 = {x+1:x+2 for x in (1.2.3)}
println(z5) // Result: {4 => 5, 2 => 3, 3 => 4}
Copy the code

The Grep and map

Grep and map are similar to perl grep and map.

let sourceArr = [2.4.6.8.10.12]

//$_ represents the value obtained for each loop
let m = grep  $_ > 5, sourceArr // For each element in sourceArr, only elements ">5" are returned
println('m is {m}')

let cp = map $_ * 2 , sourceArr // Multiply each element by 2
println('cp is {cp}')

// A more complicated example
let fields = {
                "animal"= >"dog"."building"= >"house"."colour"= >"red"."fruit"= >"apple"
             }
let pattern = `animal|fruit`
// =~(match),! ~(mismatch)
let values = map { fields[$_] } grep { $_ =~ pattern } fields.keys()
println(values)
Copy the code

function

In the Monkey, functions, like any other basic type, can be used as arguments to functions and return values to functions

Functions can also have default and variable arguments.

//define a function
let add = fn() { [5.6]}let n = [1.2] + [3.4] + add()
println(n)


let complex = {
   "add" => fn(x, y) { return fn(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])


let warr = [1+1.3, fn(x) { x + 1} (2),"abc"."def"]
println(warr)


println("\nfor i in 5.. 1 where i > 2 :")
for i in fn(x){ x+1} (4).. fn(x){ x+1} (0) where i > 2 {
  if (i == 3) { continue }
  println('i={i}')
}


// Default and variable parameters
add = fn (x, y=5, z=7, args...) {
    w = x + y + z
    for i in args {
        w += i
    }
    return w
}

w = add(2.3.4.5.6.7)
println(w)
Copy the code

You can also create a named function like this:

fn sub(x,y=2) {
    return x - y
}
println(sub(10)) // Result: 8
Copy the code

You can also create an anonymous function using the short arraw syntax:

let x = () -> 5 + 5
println(x())  // Result: 10

let y = (x) -> x * 5
println(y(2)) // Result: 10

let z = (x,y) -> x * y + 5
println(z(3.4)) / / results: 17


let add = fn (x, factor) {
  x + factor(x)
}
result = add(5, (x) -> x * 2)
println(result)  // Result: 15
Copy the code

If the function has no arguments, you can omit (). For example,

println("hhf".upper)  // Result: "HHF"
// Same result as above
println("hhf".upper())
Copy the code

The Monkey does not support multiple return values, but there are many ways to do it.

Here’s one way to do it:

fn div(x, y) {
    if y == 0 {
        return [nil."y could not be zero"]}return [x/y, ""]
}

ret = div(10.5)
if ret[1] != "" {
    println(ret[1])}else {
    println(ret[0])}Copy the code

The Pipe operator

The PIPE operator comes from Elixir.

# Test pipe operator(|>)
x = ["hello"."world"] |> strings.join("") |> strings.upper() |> strings.lower() |> strings.title()
printf("x=<%s>\n", x)

let add = fn(x,y) { return x + y }
let pow = fn(x) { return x ** 2}
let subtract = fn(x) { return x - 1}

let mm = add(1.2) |> pow() |> subtract()
printf("mm=%d\n", mm)

"Hello %s! \n" |> fmt.printf("world")
Copy the code

Spawn and channel

You can use spawn to create a new thread and chan to interact with the thread.

let aChan = chan()
spawn fn() {
    let message = aChan.recv()
    println('channel received message=<{message}>')
}()

// Send information to the thread
aChan.send("Hello Channel!")
Copy the code

Introduction to standard Modules

Monkey has predefined standard modules such as JSON, SQL, sort, FMT, OS, Logger, Time, Flag, NET, HTTP, etc.

Below is a brief description of the monkey’s standard modules.

FMT module

let i, f, b, s, aArr, aHash = 108.25.383.true."Hello, world"[1.2.3.4."a"."b"] and {"key1"= >1."key2"= >2."key3"= >"abc"}

// Use '%v (value)' to print the variable value and '%_' to print the variable type
fmt.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")
Copy the code

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))
Copy the code

The logger module

Log = 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())

// Output to a file
file = newFile("./logger.log"."a+")
log.setOutput(file)
for i in 1.5 {
    log.printf("This is <%d>\n", i)
}
file.close() // Don't forget to close the file
Copy the code

Flag module (handles command line options)

let verV = flag.bool("version".false."0.1")
let ageV = flag.int("age".40."an int")
let heightV = flag.float("height".120.5."a float")
let nameV = flag.string("name"."HuangHaiFeng"."a string")
let hobbiesV = 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")}Copy the code

Json modules (JSON serialization (Marshal) and deserialization (unmarshal))

let hsJson = {"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)}let hashStr = json.marshal(hsJson) // 'json.tojson (hsJson)' can also be used
println(json.indent(hashStr, ""))

let hsJson1 = json.unmarshal(hashStr)
println(hsJson1)


let arrJson = [1.2.3."HHF", [] and {"key"= >10."key1"= >11}]
let arrStr = json.marshal(arrJson)
println(json.indent(arrStr))
let arr1Json = json.unmarshal(arrStr)  // 'json.fromjson (arrStr)' can also be used
println(arr1Json)
Copy the code

Net module

// Simple TCP client
let conn = dialTCP("tcp"."127.0.0.1:9090")
if (conn == nil) {
    println("dailTCP failed, error:", conn.message())
    os.exit(1)}let n = conn.write("Hello server, I'm client")
if (n == nil) {
    println("conn write failed, error:", n.message())
    os.exit(1)}let ret = conn.close()
if (ret == false) {
    println("Server close failed, error:", ret.message())
}

// A simple TCP server
let ln = listenTCP("tcp".": 9090")
for {
    let conn = 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 connection
        println(conn.read())
    }(conn)

} //end for

let ret = ln.close()
if (ret == false) {
    println("Server close failed, error:", ret.message())
}
Copy the code

Linq module

In the Monkey, the LINQ module supports the following types of objects:

  • File object (using built-in functionsnewFileCreate)
  • Csv Reader objects (using built-in functionsnewCsvReaderCreate)
  • String object
  • The Array object
  • The Tuple objects
  • The Hash object
  • Channel object (using built-in functionschanCreate)
let mm = [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}')

let sortArr = [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) {
    return x["Owner"]
}).thenByDescending(fn(x){
    return x["Name"]
}).toOrderedSlice()    //Note: You need to use toOrderedSlice

//use json.indent() for formatting the output
let thenByDescendingArrStr = json.marshal(result)
println(json.indent(thenByDescendingArrStr, ""))

/ / test 'selectManyByIndexed'
println(a)let selectManyByIndexedArr1 = [[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}')

let selectManyByIndexedArr2 = ["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}')
Copy the code

Linq for file support

Monkey now has support for LINq for file. This functionality is similar to AWK. Take a look at the following code:

//test: linq for "file"
file = newFile("./examples/linqSample.csv"."r") // Open linqsample.csv in read mode
result = linq.from(file,",",fn(line){ // The second argument is the field delimiter, and the third argument is the comment function.
    if line.trim().hasPrefix("#") { // If the line begins with '#'
        return true // Return 'true' to ignore this line
    } else {
        return false}}).where(fn(fields) {
    //'fields' is a hash array:
    // 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 // Select only the value of the first field > 300000
}).sort(fn(field1,field2){
    return int(field1[1]) > int(field2[1]) // The first field is sorted in descending order
}).select(fn(fields) {
    fields[5]  // Output only the fifth field
})
println(result)
file.close() // Don't forget to close the file


//another test: linq for "file"
file = newFile("./examples/linqSample.csv"."r") // Open linqsample.csv in read mode
result = linq.from(file,",",fn(line){ // The second argument is the field delimiter, and the third argument is the comment function.
    if line.trim().hasPrefix("#") { // If the line begins with '#'
        return true // Return 'true' to ignore this line
    } else {
        return false}}).where(fn(fields) {
    int(fields[1) >300000 // Select only the value of the first field > 300000
}).sort(fn(field1,field2){
    return int(field1[1]) > int(field2[1]) // The first field is sorted in descending order
}).selectMany(fn(fields) {
    row = [[fields[0]]] // Fields [0] is a full line of data. Note: We need to use both [], otherwise selectMany() will flatten the output
    linq.from(row)  // Output the entire line of data
})
println(result)
file.close() // Don't forget to close the file


//test: linq for "csv"
r = newCsvReader("./examples/test.csv") // Open test.csv in read mode
r.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"// Select only the second field = "Pike"
}).sort(fn(x,y){
    return len(x[1]) > len(y[1]) // Sort by the length of the first field
})
println(result)
r.close() // Don't forget to close Reader
Copy the code

The CSV module

// Test CSV reader
let r = 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())
}

for line in ra {
    println(line)
    for record in line {
        println("", record)
    }
}
r.close() //do not to forget to close the reader

// Test the CSV writer
let ofile = newFile("./examples/demo.csv"."a+")
let w = newCsvWriter(ofile)
w.setOptions({"Comma"= >""})
w.write(["1"."2"."3"])
w.writeAll([["4"."5"."6"], ["Seven"."8"."9"], ["10"."11"."12"]])
w.flush()
ofile.close() //do not to forget to close the file
Copy the code

The template module

The template module contains ‘text’ and ‘HTML’ template processing.

Using newText (…). Or parseTextFiles (…). Create a new ‘text’ template.

Using newHtml (…). Or parseHtmlFiles (…). To create a new ‘HTML’ template.

arr = [
    { "key"= >"key1"."value"= >"value1" },
    { "key"= >"key2"."value"= >"value2" },
    { "key"= >"key3"."value"= >"value3"}]// Use parseTextFiles() to write a string
template.parseTextFiles("./examples/looping.tmpl").execute(resultValue, arr)
println('{resultValue}')

// Use parseTextFiles() to write a file
file = newFile("./examples/outTemplate.log"."a+")
template.parseTextFiles("./examples/looping.tmpl").execute(file, arr)
file.close() //do not to forget to close the file

/ / use the parse ()
// We need to use "{{-" and "-}}" to remove newline from output.
template.newText("array").parse(`Looping
{{- range . }}
        key={{ .key }}, value={{ .value -}}
{{- end }}
`).execute(resultValue, arr)
println('{resultValue}')
Copy the code

SQL module

SQL modules provide a low-level wrapper to manipulate the database.

It handles null values in the database correctly, although it has not been fully tested.

To test the SQL module, you need to do the following steps:

  1. Download the SQL Driver code.

  2. Include the drive package in the ‘sql.go’ file:

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

Here is a complete example of using a database (examples/db.my):

let dbOp = fn() {
    os.remove("./foo.db") //delete `foo.db` file
    let db = dbOpen("sqlite3"."./foo.db")
    if (db == nil) {
        println("DB open failed, error:", db.message())
        return false
    }
    defer db.close()
    letsqlStmt = `create table foo (id integer not null primary key, name text); delete from foo; `let exec_ret = db.exec(sqlStmt)
    if (exec_ret == nil) {
        println("DB exec failed! error:", exec_ret.message())
        return false
    }

    let tx = db.begin()
    if (tx == nil) {
        println("db.Begin failed! , error:", tx.message())
        return false
    }

    letstmt = tx.prepare(`insert into foo(id, name) values(? ,?) `)if (stmt == nil) {
        println("tx.Prepare failed! , error:", stmt.message())
        return false
    }

    defer stmt.close()
    let i = 0
    for (i = 0; i < 105; i++) {
        let name = "Hello" + i
        if (i>100) {
            // Insert 'null' value. There are seven predefined NULL constants: INT_NULL,UINT_NULL,FLOAT_NULL,STRING_NULL,BOOL_NULL,TIME_NULL, DECIMAL_NULL.
            let rs = stmt.exec(i, sql.STRING_NULL)}else {
            let rs = stmt.exec(i, name)
        }

        if (rs == nil) {
            println("statement exec failed, error:", rs.message())
            return false}}//end for

    tx.commit()

    let id, name = 0.""
    let rows = db.query("select id, name from foo")
    if (rows == nil) {
        println("db queue failed, error:", rows.message())
        return false
    }
    defer rows.close()
    while (rows.next()) {
        rows.scan(id, name)
        if (name.valid()) { // Check for 'null'
            println(id, "|", name)
        } else {
            println(id, "|"."null")}}return true
}

let ret = dbOp()
if (ret == nil) {
    os.exit(1)
}

os.exit()
Copy the code

utility

The project also includes some tools to use: Formatter and highlighter.

The Formatter tool formats the Monkey language. The highlighter tool syntactically highlights the Monkey language (providing two outputs: command line and HTML).

You can also use them together:

. / FMT xx. My |. / highlight / / output to the screen (command line highlight not only the Windows)Copy the code

Syntax highlighting

Currently, Monkey supports syntax highlighting for three editors:

  1. vim

    vim

  2. emeditor

    emeditor

  3. notepad++

    notepad++

  4. Visual Studio Code

    VSC

  5. Sublime Text 3

    Sublime Text 3

Future plans

The following is a description of future plans for the project:

  • Improve the standard library and add more functions.
  • Write more test code!

license

MIT

note

If you like this project, please click the link below, Duoduo Star, fork. Thank you very much! monkey