pocketlang/docs/GettingStarted/LanguageManual.md
Thakee Nathees f5e2f15d23 negative index, range slice and more
... string concat with .. operator
    exponent operator with ** operator
2022-05-08 23:19:33 +05:30

9.3 KiB

Language Manual

You can consider pocketlang's syntax as python without indentation. For block statements it uses the ruby way, 'end' keyword to close a block, end a class, function etc. Most of the semantics are simillar to python and if you're know python it fairly easier to grasp pocketlang.

Hello World

print('Hello World')

Comments and White Spaces

Comments are marked with # and ends at the next new line character.

# Beautiful is better than ugly.
# Explicit is better than implicit.
# Simple is better than complex.
# Complex is better than complicated.

Except for new lines all the white spaces are ignored. New lines are used to end a statement, every statement should ends with a new line or semicollon.

Code blocks

Code blocks are following the same rule of ruby. All the blocks are closed with the end keyword, all blocks are stards with either a new line and an optional "block entering keyword". For a single line block these keywords are must. if blocks starts with the then, for while and for loops they starts with the do keyword.

## The `do` keyword is a must here.
while cond do something() end

## The `do` keyword is a optional here.
for i in 0..10
  print('$i')
end

## `then` is optional if new line is present.
if cond1 then
  foo()
else if cond2
  bar()
else
  baz()
end

Data Types

Pocketlang has 3 primitive types which are null, boolean and number all the number values are represented as IEEE 754 double precision floats. null, true, false are their literals and number support binary, hex and scientific literals 0b101001, 0xc0ffee, 3e8.

There are a handfull number of reference types like String, Range, List, Map, Closure (which is the first class functions), Fiber, etc. And you can define you own with the class keyword.

String

String sin pocketlang can be either double quoted or single quoted. At the moment pocket lang only supported a limited number of string escape characters and UTF8 is not supported. However it'll be implemented before the first release.

"Hello there!"; 'I\'m a string.'

"I'm a multi
line string."

Additionally pocketlang strings by default support interpolation with the $ symbol. For a single identifier it can be just written without any enclosing brackets, and for a long expressions they should be inside curly brackets. And they can be nested upto 8 depth.

n = 25
print('sqrt($n) = ${sqrt(n)}')

Range

A range value represent an interval, they can be created with the .. operator between two whole numbers.

for i in 0..10
  print(i)
end

!> Unlike ruby we don't have a ... operator and pocketlang Range objects are exclusive.

Exclusive ranges are more natural and more usefull when it comes to iterate over the length of something, thus pocketlang's ranges are always exclusive. And having two operator for the same but slightly different operation is a bit redundant in our humble openion.

List

Lists are a collection of ordered objects. Each element can be indexed starting at 0. Internally a list is a dynamically allocated variables buffer they'll grow or shrink as needed to store them.

[ 'apples', true, 0..5 ]

[
  [ 1, 2, 3],
  [ 4, 5, 6],
  [ 7, 8, 9],
]

Map

Maps are a collection of un ordered objects that stores the mapping of unique keys to the values. Internally it's implemented as a has table and it require a value to be hashable for it to be a key. In pocketlang only primitive types and immutable objects (String and Range) are hashable.

{
  12 : 34,
  0..3 : [0, 1, 2],
  'foo' : 'bar',
}

Closure

Closure are the first class citizen of functions that can be created, assigned to variables, passed as an argument, or returned from a function call. A closure can be created with the fn keyword, which will crate an anonymous function and wraps itself around it.

y = fn (x) return x * x end

Pocketlang support lexical scoping which allows the closure to capture an external local variable out of it's own scope. They're more like lambda expressions in other langauges.

def make_function(m, c)
  return fn(x)
    return m * x + c
  end
end

?> Note that a call of form f(fn ... end) is a syntax sugar for f fn ... end where f is a function or a method that takes a single literal function as argument, like Lua.

5.times fn(i)
  print(i)
end

Fiber

Pocketlang support coroutines via fibers (light weight threads with cooperative multitask). A fiber object is a wrapper around a closure, contains the execution state (simply the stack and the instruction pointer) of that closure, which can be run and once yielded resumed.

def foo()
  print("Hello from fiber!")
end

fb = Fiber(foo)
fb.run()

When a function is yielded, it's state will be stored in the fiber it's belongs to and will return from the function, to parent fiber it's running from (not the caller of the function). You can pass values between fibers when they yield.

def foo()
  print('running')
  yield()
  print('resumed')
end

fb = Fiber(foo)
fb.run()    # Prints 'running'.
print('before resumed')
fb.resume() # Prints 'resumed'.

Yield from the fiber with a value.

def foo()
  print('running')
  yield(42) # Return 42.
  print('resumed')
end

fb = Fiber(foo)
val = fb.run() # Prints 'running'.
print(val)     # Prints 42.
fb.resume()    # Prints 'resumed'.

Resume the fiber with a value.

def foo()
  print('running')
  val = yield() # Resumed value.
  print(val)    # Prints 42.
  print('resumed')
end

fb = Fiber(foo)
val = fb.run()  # Prints 'running'.
fb.resume(42)   # Resume with 42, Prints 'resumed'.

Once a fiber is done execution, trying to resume it will cause a runtime error. To check if the fiber is finished check its attribute is_done and use the attribute function to get it's function, which could be used to create a new fiber to "re-start" the fiber.

fb = Fiber fn()
  for i in 0..5
    yield(i)
  end
end

fb.run()

while not fb.is_done
  fb.resume()
end

# Get the function from the fiber.
f = fb.get_func()

Classes

Classes are the blueprint of objects, contains method definitions and behaviors for its instances. The instance of a class method can be accessed with the self keyword.

class Foo end
foo = Foo() ## Create a foo instance of Foo class.

To initialize an instance when it's constructed use _init method. Pocketlang instance attributes are dynamic (means you can add a new field to an instance on the fly).

class Foo
  def _init(bar, baz)
    self.bar = bar
  end
end

foo = Foo('bar', 'baz')

To override an operator just use the operator symbol as the method name.

class Vec2
  def _init(x, y)
    self.x = x; self.y = y
  end
  def _str
    return "<${self.x}, ${self.y}>"
  end
  def + (other)
    return Vec2(self.x + other.x,
                self.y + other.y)
  end
  def += (other)
    self.x += other.x
    self.y += other.y
    return self
  end
  def == (other)
    return self.x == other.x and self.y == other.y
  end
end

To distinguish unary operator with binary operator the self keyword should be used.

class N
  def _init(n)
    self.n = n
  end

  def - (other) ## N(1) - N(2)
    return N(self.n - other.n)
  end

  def -self () ## -N(1)
    return N(-self.n)
  end
end

All classes are ultimately inherit an abstract class named Object to inherit from any other class use is keyword at the class definition. However you cannot inherit from the builtin class like Number, Boolean, Null, String, List, ...

class Shape # Implicitly inherit Object class
end

class Circle is Shape # Inherits the Shape class
end

To override a method just redefine the method in a subclass.

class Shape
  def area()
    assert(false)
  end
end

class Circle is Shape
  def _init(r)
    self.r = r
  end
  def area()
    return math.PI * r ** 2
  end
end

To call the a method on the super class use super keyword. If the method name is same as the current method super() will do otherwise method name should be specified super.method_name().

class Rectangle is Shape
  def _init(w, h)
    self.w = w; self.h = h
  end
  def scale(fx, fy)
    self.w *= fx
    self.h *= fy
  end
end

class Square is Rectangle
  def _init(s)
    super(s, s) ## Calls super._init(s, s)
  end
  
  def scale(x)
    super(x, x)
  end

  def scale2(x, y)
    super.scale(x, y)
  end
end

Importing Modules

A module is a collection of functions classes and global variables. Usually a single script file will be compiled to a module. To import a module use import statement.

import math # Import math module.
import lang, math # Import multiple modules.
from math import sin, tan # Import symbols from a module.

# Using alias to bind with a different name.
import math as foo
from lang import clock as now

# Import inside a directory
import foo.bar # Import module bar from foo directory
import baz # If baz is a directory it'll import baz/_init.pk

# I'll only search for foo relatievly.
import .foo # ./foo.pk or ./foo/_init.pk or ./foo.dll, ...

# ^ meaning parent directory relative to this script.
import ^^foo.bar.baz # ../../foo/bar/baz.pk

?> Note that star import from foo import * is not supported but may be in the future.