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.
The most impressive examples are:
- The tic-tac-toe AI: https://github.com/mkhan45/RustScript2/blob/main/examples/tictactoe_minimax.rsc
- The TOML parser library: https://github.com/mkhan45/rustscript_toml
- A simple static site generator/HTML template engine: https://github.com/mkhan45/ssg_rustscript
- My personal website served using the previous two libraries: https://github.com/mkhan45/rustscript_site, hosted at https://rustscript.mikail-khan.com
- More examples below
RustScript has 5 basic types
letx=5# integerletf=5.0# floatlets="Hello"# stringletb=T# booleanleta=:atom# atomThere 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# functionsAll 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")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 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
# 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 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]# 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.599dune buildRun a file using:
dune exec ./bin/rustscript_cli.exe <file>Start a REPL using:
dune exec ./bin/rustscript_cli.exe# 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)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]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))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]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))# ()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"euler1=sum([xforxin[1..1000]ifx%3==0 || x%5==0])inspect(euler1)# 233168leteuler2 = {letaux = fn((a,b),acc) =>
ifb < 4000000thenaux((b,a+4*b),acc+b)elseaccaux((0,2),0)}inspect(euler2)# 4613732letgcd = 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)# 6857More project euler problems can be found in the examples folder.
