Merge pull request #35 from ThakeeNathees/readme-update

readme updated with performance benchmarks
This commit is contained in:
Thakee Nathees 2021-05-31 05:33:35 +05:30 committed by GitHub
commit 39c3bb41a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 210 additions and 55 deletions

View File

@ -1,14 +1,13 @@
# PocketLang
<p align="center" >
<img src="https://user-images.githubusercontent.com/41085900/117528974-88fa8d00-aff2-11eb-8001-183c14786362.png" width="500" >
</p>
**PocketLang** is a small (~3000 semicollons) and fast programming language written in C. It's syntactically similar to Ruby and it can be learned in less than an hour. Including the compiler, bytecode VM and runtime, it's a standalone executable with zero external dependecies just as it's self descriptive name. The pocketlang VM can be embedded in another hosting program very easily, as static/shared library or a generated single header version of the source, which makes it even effortless.
**Pocketlang** is a small (~3000 semicollons) and fast functional programming language written in C. It's syntactically similar to Ruby and it can be learned in less than an hour. Including the compiler, bytecode VM and runtime, it's a standalone executable with zero external dependecies just as it's self descriptive name. The pocketlang VM can be embedded in another hosting program very easily.
The language is written using [Wren Language](https://wren.io/) and their wonderful book [craftinginterpreters](http://www.craftinginterpreters.com/) as a reference.
### What PocketLang looks like
### What pocketlang looks like
```ruby
# Python like import statement.
@ -26,3 +25,31 @@ for i in 0..10
end
```
## Performance!
The source files used to run benchmarks could be found at `test/benchmarks/` directory. They were ran using a small python script in the test directory.
- PC: ASUS N552VX, Intel Core i7-6700HQ 2.6GHz, 12GB SODIMM Ram
- Lnaguages: pocketlang (pre-alpha), wren v0.3.0, python v3.7.4, ruby v2.7.2,
![preformance2](https://user-images.githubusercontent.com/41085900/120123257-6f043280-c1cb-11eb-8c20-a42153268a0f.png)
When it comes to loops pocketlang much faster (even faster than [wren](https://wren.io/), the language I'm using as a reference) because we have dedicated opcode instructions for iterations and calling builtin functions. And it's is a functional language, which makes is possible to avoid object orianted/ class instance creation overhead.
## Building From Source
It can be build from source easily without any depencency, requirenments or even a build system, just a c99 compatible compiler is enough. It can be compiled with the following command.
#### gcc/mingw
```
gcc -o pocket cli/*.c src/*.c -Isrc/include -lm -Wno-int-to-pointer-cast
```
#### msvc
```
cl /Fepocket cli/*.c src/*.c /Isrc/include && rm *.obj
```
For other compilers it's the same (If you weren't able to compile it, please report by [opening an issue](https://github.com/ThakeeNathees/pocketlang/issues/new)). In addition you can use some of our build scripts (Makefile, batch script for MSVC, SCons) in the `build/` directory. For more see [build from source docs](https://thakeenathees.github.io/pocketlang/Getting%20Started/build%20from%20source.html).

View File

@ -557,6 +557,37 @@ void stdMathCeil(PKVM* vm) {
RET(VAR_NUM(ceil(num)));
}
void stdMathPow(PKVM* vm) {
double num, ex;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (!validateNumeric(vm, ARG2, &ex, "Parameter 2")) return;
RET(VAR_NUM(pow(num, ex)));
}
void stdMathSqrt(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
RET(VAR_NUM(sqrt(num)));
}
void stdMathAbs(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (num < 0) num = -num;
RET(VAR_NUM(num));
}
void stdMathSign(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (num < 0) num = -1;
else if (num > 0) num = +1;
else num = 0;
RET(VAR_NUM(num));
}
PK_DOC(stdMathHash,
"hash(value:var) -> num\n"
"Return the hash value of the variable, if it's not hashable it'll "
@ -623,6 +654,10 @@ void initializeCore(PKVM* vm) {
Script* math = newModuleInternal(vm, "math");
moduleAddFunctionInternal(vm, math, "floor", stdMathFloor, 1);
moduleAddFunctionInternal(vm, math, "ceil", stdMathCeil, 1);
moduleAddFunctionInternal(vm, math, "pow", stdMathPow, 2);
moduleAddFunctionInternal(vm, math, "sqrt", stdMathSqrt, 1);
moduleAddFunctionInternal(vm, math, "abs", stdMathAbs, 1);
moduleAddFunctionInternal(vm, math, "sign", stdMathSign, 1);
moduleAddFunctionInternal(vm, math, "hash", stdMathHash, 1);
}
@ -656,7 +691,12 @@ Var varAdd(PKVM* vm, Var v1, Var v2) {
} break;
case OBJ_LIST:
TODO;
{
if (o2->type == OBJ_LIST) {
TODO;
}
}
TODO;
case OBJ_MAP:
case OBJ_RANGE:

View File

@ -759,6 +759,24 @@ bool isValuesEqual(Var v1, Var v2) {
memcmp(s1->data, s2->data, s1->length) == 0;
}
case OBJ_LIST: {
/*
* l1 = []; list_append(l1, l1) # [[...]]
* l2 = []; list_append(l2, l2) # [[...]]
* l1 == l2 ## This will cause a stack overflow but not handling that
* ## (in python too).
*/
List *l1 = (List*)o1, *l2 = (List*)o2;
if (l1->elements.count != l2->elements.count) return false;
Var* v1 = l1->elements.data;
Var* v2 = l2->elements.data;
for (uint32_t i = 0; i < l1->elements.count; i++) {
if (!isValuesEqual(*v1, *v2)) return false;
v1++, v2++;
}
return true;
}
default:
return false;
}

View File

@ -1,12 +1,10 @@
from lang import clock
start = clock()
N = 50000000; factors = []
for i in 1..N+1
if N % i == 0
list_append(factors, i)
end
end
print("elapsed:", clock() - start, 's')

View File

@ -8,8 +8,7 @@ end
start = clock()
for i in 0..10
print(fib(32))
print(fib(30))
end
print('elapsed:', clock() - start, 's')

View File

@ -6,5 +6,5 @@ def fib(n):
start = clock()
for i in range(0, 10):
print(fib(32))
print(fib(30))
print("elapsed: ", clock() - start, 's')

View File

@ -9,6 +9,6 @@ end
start = Time.now
for i in 0...10
puts fib(32)
puts fib(30)
end
puts "elapsed: " + (Time.now - start).to_s + ' s'

View File

@ -1,5 +1,4 @@
class Fib {
static get(n) {
if (n < 2) return n
@ -9,6 +8,6 @@ class Fib {
var start = System.clock
for (i in 0...10) {
System.print(Fib.get(32))
System.print(Fib.get(30))
}
System.print("elapsed: %(System.clock - start)")
System.print("elapsed: %(System.clock - start) s")

View File

@ -13,10 +13,8 @@ def reverse_list(list)
end
start = clock()
N = 20000000
l = (0..N).as_list
reverse_list(l)
print(clock() - start, 's')
print('elapsed: ', clock() - start, 's')

View File

@ -12,4 +12,4 @@ start = clock()
N = 20000000
l = list(range(N))
reverse_list(l)
print(clock() - start, 's')
print('elapsed: ', clock() - start, 's')

View File

@ -1,15 +1,10 @@
from lang import clock
start = clock()
list = []
for i in 0..10000000
list_append(list, i)
end
l = []
for i in 0..10000000 do list_append(l, i) end
sum = 0
for i in list
sum += i
end
for i in l do sum += i end
print(sum)
print("elapsed:", clock() - start)
print("elapsed:", clock() - start, 's')

View File

@ -7,4 +7,4 @@ var sum = 0
for (i in list) sum = sum + i
System.print(sum)
System.print("elapsed: %(System.clock - start)")
System.print("elapsed: %(System.clock - start) s")

View File

@ -9,8 +9,7 @@ def is_prime(n)
end
start = clock()
N = 60000; primes = []
N = 30000; primes = []
for i in 0..N
if is_prime(i)
list_append(primes, i)

View File

@ -8,7 +8,7 @@ def is_prime(n):
start = clock()
N = 60000; primes = []
N = 30000; primes = []
for i in range(N):
if is_prime(i):
primes.append(i)

View File

@ -8,7 +8,7 @@ def is_prime(n)
end
start = Time.now
N = 60000; primes = []
N = 30000; primes = []
for i in 0...N
if is_prime(i)
primes.append(i)

View File

@ -9,12 +9,12 @@ var is_prime = Fn.new {|n|
var start = System.clock
var N = 60000
var N = 30000
var primes = []
for (i in 0...N) {
if (is_prime.call(i)) {
primes.add(i)
}
}
System.print("elapsed: %(System.clock - start)")
System.print("elapsed: %(System.clock - start) s")

View File

@ -1,18 +1,16 @@
## Fib test.
res = ''
## Iterative fibonacci function.
def fib(n)
res = []
a = 0; b = 1
for _ in 0..n
res += to_string(a) + " "
temp = a;
a = b;
b += temp;
list_append(res, a)
temp = a; a = b; b += temp;
end
return res
end
fib(10)
print(res)
assert(res == '0 1 1 2 3 5 8 13 21 34 ')
fibs = fib(10)
print('fibs =', fibs)
assert(fibs == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34])

View File

@ -0,0 +1,9 @@
for i in 1..100
if i%3 == 0 and i%5 == 0 then print('fizzbuzz')
elif i%3 == 0 then print('fizz')
elif i%5 == 0 then print('buzz')
else print(i)
end
end

View File

@ -0,0 +1 @@
print('Hello world!')

19
test/examples/palette.pk Normal file
View File

@ -0,0 +1,19 @@
## Run pocket palette.pk > palette.ppm
## Open palatte.ppm with any image viwer which support ppm image
## (ex: photoshop, gimp, libre office draw, ...)
blue = 200
print("P3")
print(255, 255)
print(255)
from lang import write
for r in 0..255
for g in 0..255
write(r, ' ', g, ' ', blue, ' ')
end
write('\n')
end

17
test/examples/pi.pk Normal file
View File

@ -0,0 +1,17 @@
## PI approximation using Leibniz formula.
##
## PI/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - ...
from math import abs
pi_by_4 = 0; sign = -1
for i in 1..10000
sign *= -1
pi_by_4 += sign * 1/(2*i - 1)
end
PI = 4 * pi_by_4
assert(abs(PI - 3.141593) < 0.0002)
print('PI =', PI)

View File

@ -1,8 +1,6 @@
## Prime numbers.
res = ''
def is_prime(n)
if n < 2 then return false end
for i in 2..n
@ -12,12 +10,16 @@ def is_prime(n)
end
def get_all_primes(n)
res = []
for i in 0..n
if is_prime(i)
res += to_string(i) + ' '
list_append(res, i)
end
end
return res
end
get_all_primes(20)
assert(res == '2 3 5 7 11 13 17 19 ')
primes = get_all_primes(20)
assert(primes == [2, 3, 5, 7, 11, 13, 17, 19])
print('primes =', primes)

View File

@ -1,7 +1,11 @@
import subprocess
import json, os
import subprocess, os, sys
import json, re
from os.path import join
files = [
FMT_PATH = "%-25s"
INDENTATION = ' | '
test_files = [
"lang/basics.pk",
"lang/functions.pk",
"lang/if.pk",
@ -9,12 +13,44 @@ files = [
"examples/prime.pk",
]
FMT_PATH = "%-25s"
INDENTATION = ' | '
benchmarks = {
"factors" : ['.pk', '.py', '.rb', '.wren'],
"fib" : ['.pk', '.py', '.rb', '.wren'],
"list_reverse" : ['.pk', '.py', '.rb'],
"loop" : ['.pk', '.py', '.rb', ".wren"],
"primes" : ['.pk', '.py', '.rb', ".wren"],
}
def run_all_benchmarks():
print("--------------------------------")
print(" BENCHMARKS ")
print("--------------------------------")
def get_interpreter(file):
if file.endswith('.pk' ) : return 'pocket'
if file.endswith('.py' ) : return 'python'
if file.endswith('.rb' ) : return 'ruby'
if file.endswith('.wren') : return 'wren'
assert False
for bm_name in benchmarks:
print(bm_name + ":")
for ext in benchmarks[bm_name]:
file = join('benchmark', bm_name, bm_name + ext)
interpreter = get_interpreter(file)
print(' %10s: '%interpreter, end=''); sys.stdout.flush()
result = run_command([interpreter, file])
time = re.findall(r'elapsed:\s*([0-9\.]+)\s*s',
result.stdout.decode('utf8'),
re.MULTILINE)
assert len(time) == 1, r'elapsed:\s*([0-9\.]+)\s*s --> no mach found.'
print('%10ss'%time[0])
def run_all_tests():
for path in files:
print("--------------------------------")
print(" TESTS ")
print("--------------------------------")
for path in test_files:
run_file(path)
def run_file(path):
@ -37,4 +73,4 @@ def run_command(command):
run_all_tests()
run_all_benchmarks()