Skip to content

Repository files navigation

RustScript V2

V2 of https://github.com/mkhan45/RustScript

I originally wrote RustScript in Java because it was part of a school project, ignoring performance/code quality because I only had one night to do it.

This is an improved version of RustScript with improved performance and more features written to learn OCaml. It also served as a testbed for features, and a demonstration of the 80/20 rule; the language's design was largely based on ease of implementation.

Examples:

The most impressive examples are:

Language Tour

Basic types:

RustScript has 5 basic types

letx=5# integerletf=5.0# floatlets="Hello"# stringletb=T# booleanleta=:atom# atom

Compound types:

There are also a few compound types

lett=(1,"hello",:aaa)# tuplesletls=[1,2,3,4,5]# listsletm=%{one: 2, "three" => 3}# mapsletf1 = fn(x)=>x*2# closuresletf2(x)=x*2# functions

Patterns:

All bindings in RustScript are done through pattern matching. Aside from the primitives, there are:

let(a,(b,c),d) = (1,(2,3),4)# tuple patternsinspect((a,b,c,d))# (1, 2, 3, 4)let[a,b,c] = [1,2,3]# list patternsinspect((a,b,c))# (1, 2, 3)let[a,b|tl] = [1,2,3,4]# list head/tail patternsinspect((a,b,tl))# (1, 2, [3, 4])let%{one,"two"=>two} = %{one: 1, "two" => 2,unused: 0}# map patternsinspect((one,two))# (1, 2)
let _ =:something# wildcard pattern# no bindings are createdlet[x|xs] as ls =[1,2,3]# as patternsinspect((x,xs,ls))# (1, [2, 3], [1, 2, 3])

While pattern matching is most frequently used in let bindings, it is also used in if let expressions, match expressions, and function arguments.

if let expressions are used for refutable patterns:

letresult=(:ok,5)iflet(:ok,n)=resulttheninspect(n)elseprintln("Error")

match expressions:

letls=[1,2,3,4]matchls|[1|xs]->println("Starts with 1")|[_|xs]->println("Starts with something other than 1")

Closures:

leta=5letf = fn(x) => x * a# f captures ainspect(f(2))# 10
let g = fn(a,[x|xs]) = (a*x,xs)# pattern matching works in function argumentsinspect(g(1,[2,3,4]))# (2, [3, 4])

Named functions:

Named functions do not capture their environment. As a result, they run slightly faster and can be made mutually recursive

let f(x) = x * 2
inspect(f(2)) # 4

Maps:

# pairs with non-atom keys use "=>" arrowsletx=%{"one"=>1,"two"=>2,"three"=>3}# pairs with atom keys use colonslety=%{one: 1,two: 2,three: 3}# the following are equivalent:%{one: 1,two: 2}%{:one=>1,:two,2}# Maps are accessed via function call syntaxinspect(x("one"))# 1inspect(y(:one))# 1# However it's often more convenient to pattern match over them,# especially with atoms as keyslet%{"one"=>one,"two"=>two}=xinspect((one,two))# the three does not get boundlet%{one,two}=y# key punning, equivalent to the next linelet%{:one=>one,:two=>two}=y# Maps can be updated using update syntaxletm=%{one: 1,two: 2}letg=%{three: 3|m}inspect(g)# %{:one: 1, :three: 3, :two: 2}

Lists

# Lists are heterogenous linkedlists.letls=[1,2,5,7]# Generally, lists are accessed via pattern matchinglet[a,b|tl]=lsinspect((a,b,tl))# (1, 2, [5, 7])# They can also be accessed by index in O(n) time via the nth functioninspect(nth(ls,2))# 5# Range expressionsinspect([1..10])# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]inspect([1,5..25])# [1, 5, 9, 13, 17, 21]# List comprehensionsinspect([n*nfornin[1..100]ifnmod12==0])# [144, 576, 1296, 2304, 3600, 5184, 7056, 9216]

Captures and Pipes

# Currying is emulated via function capturesletpolynomial = fn(a,b,c,x) => a*x*x+b*x + cletf = polynomial(2,3,4,_)
let g = polynomial(_,_,_,10)inspect(f(10))# 234inspect(g(2,3,4))# 234# Captures are especially useful in combination with the pipe operator.# The following code takes advantage of the standard add, sub, mul, and div functions# as well as the fact that inspect returns its arguments unchanged after printing them
let f =polynomial(2,3,4)10|>f|>inspect# 234|>add(_,10)|>inspect# 244|>div(_,100)|>inspect# 2.44|>sub(1000,_)|>inspect# 997.56|>mul(_,10)|>inspect# -9975.599

Build

dune build

Run a file using:

dune exec ./bin/rustscript_cli.exe <file>

Start a REPL using:

dune exec ./bin/rustscript_cli.exe

Further examples

FizzBuzz

# ideally, for ... in will become a macro over foreachletfizzbuzz(n) = foreach([1..101],fn(n) => match(n%3,n%5)
| (0,0) -> println("FizzBuzz")
| (0,_)->println("Fizz")|(_,0)->println("Buzz")|_->println(to_string(n)))fizzbuzz(100)

Quicksort

letsort = fn(ls)=>matchls|[] -> []
| [pivot|tail] ->{let(higher,lower) = partition(tail,fn(x)=>x>=pivot)sort(lower)+[pivot]+sort(higher)}inspect(sort([5,3,7,9,10,4,6]))# [3, 4, 5, 6, 7, 9, 10]

Run Length Encode

letrun_len_encode=fn(ls)=>matchls|[]->[]|[x|xs]->{letnext=run_len_encode(xs)matchnext|[(next_x,cnt)|tl]whenx==next_x->[(x,cnt+1)|tl]|_->[(x,1)|next]}lettest_ls=[1,1,2,3,4,4,4,5,6,1,2,2]# [(1., 2.), (2., 1.), (3., 1.), (4., 3.), (5., 1.), (6., 1.), (1., 1.), (2., 2.)]inspect(run_len_encode(test_ls))

Binary Search Tree

letinsert = fn(root,key) => matchroot|()->%{val: key}|%{right,val}whenval<key->%{right: insert(right,key)|root}|%{left}->%{left: insert(left,key)|root}lettree_to_ls_inorder={letloop=fn(root,acc) => matchroot|()->acc|%{val,left,right}->{letacc=loop(left,acc)letacc=[val|acc]loop(right,acc)}fn(bst) => reverse(loop(bst,[]))}
let construct_from_list = fn(ls)=>fold((),fn(t,v)=>insert(t,v),ls)
let ls =[50,30,20,65,42,20,40,70,60,80]
let bst =construct_from_list(ls)inspect(tree_to_ls_inorder(bst))# [20, 20, 30, 40, 42, 50, 60, 65, 70, 80]

Two Sum

lettwo_sum = fn(nums,target) => {lethelper=fn(m,ls,target) => matchls|[]->()|[(i,x)|xs]->{letcomplement=target-xmatchm|%{complement=>()}->helper(%{x: i|m},xs,target)|%{complement=>y}->(y,i)}helper(%{},enumerate(nums),target)}inspect(two_sum([1,9,13,20,47],10))# (0, 1)inspect(two_sum([3,2,4,1,9],10))# (0, 4)inspect(two_sum([],10))# ()
Caesar Cipher
let(to_number,to_letter) = {letenumerated = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"|>to_charlist |> enumerateletto_number = fold(%{},fn(m,(i,l))=>%{l=>i|m},enumerated)letto_letter = fold(%{},fn(m,(i,l))=>%{i=>l|m},enumerated)(to_number,to_letter)}letencode = fn(text,n) => {letshift = shift%26letloop = fn(char_ls,acc) => matchchar_ls|[] -> concat(reverse(acc))|[c|xs]whento_number(c)==()->loop(xs,[c|acc])
| [c|xs] ->{letnew_letter = c|>to_number|>add(shift,_)
|> fn(c) => ifc < 0then26 + celsec
|> fn(c) => to_letter(c%26)loop(xs,[new_letter|acc])}loop(to_charlist(text),[])}letdecode = fn(text,n) => encode(text,-n)inspect(encode("HELLO WORLD",5))# "MJQQT BTWQI"inspect(decode(encode("HELLO WORLD",5),5))# "HELLO WORLD"
Project Euler #1
euler1=sum([xforxin[1..1000]ifx%3==0 || x%5==0])inspect(euler1)# 233168
Project Euler #2
leteuler2 = {letaux = fn((a,b),acc) =>
ifb < 4000000thenaux((b,a+4*b),acc+b)elseaccaux((0,2),0)}inspect(euler2)# 4613732

Euler 3

letgcd = fn(a,b) => match (a,b)
| (0,x)|(x,0) -> x
| (a,b)whena>b -> gcd(b,a)
| (a,b) -> {letremainder = b%aifremainder!=0then(gcd(a,remainder))else a
}
let abs = fn(x) => ifx < 0 then -xelsexletpollard = fn(n)=>matchn|1 -> ()|nwhenn%2==0->2
| n->{letg = fn(x,n) => (x*x+1)%nletiter = fn(x,y,d) => match (x,y,d)
| (x,y,1) -> {letx=g(x,n)
let y =g(g(y,n),n)
let d =gcd(abs(x-y),n)iter(x,y,d)}
| (_,_,d) -> ifd==nthen()elsediter(2,2,1)}letfactor = fn(n)=>{letd=pollard(n)
if d ==()then()else n /d}
let euler3 ={# repeatedly factors until largest is foundletaux=fn(n)=>matchfactor(n)|()->n|fwhenn==f->f|f->aux(f)letn=600851475143aux(n)}inspect(euler3)# 6857

More project euler problems can be found in the examples folder.

About

RustScript is a functional scripting language with as much relation to Rust as Javascript has to Java.

Topics

Resources

Stars

43 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages