Test environment for crystal functions

Hello,
I found some time to write an interactive test environment for crystal functions.
It has a CLI which can be used to manage vars and start functions and procs stored in a hash table.

>ls functions
procs: typeof,print,puts,p,eval,ceval,after,def,return,+,-,*,/,inc,dec,<,>,if,eq,while,end,every,ls,vars,let,=,delete,clear,!,now,date,test,sleep,pass,cls,load,run,split_run,help,debug,singlestep,breakpoint,inject,system,exit,Array,append,size,list,log,clear_log,insert_line,write_line,delete_line,open_ipc,send_ipc,receive_ipc,send_receive_ipc,show_ipc_results,read_int_var


https://crystalshards.xyz/?filter=tree

While it is a sort of text interpreter its not that slow:

Example test script:
Welcome to tree

load counter2.txt
Loaded: counter2.txt Number of lines: 11
run
23:43:57.337687
0
1000000
23:44:00.273736
reached end of file

# ~ 5 sec with scripter
now
counter = 0
p counter
while counter < 1000000
 counter+= 1
 #p counter
end 
p counter
now

Best Regards,
Peter

1 Like

I’d checkout https://github.com/crystal-community/icr.

I am using icr about a year now and its really a good tool.
Here i wrote a small description what can be done with my small scripting environment called tree.

Its more about real time testing of already working functions and not about evaluating single crystal commands. I wanted to have an environment a bit like the Python shell where i can set vars and inspect functions at runtime.

ls
builtin vars: {“started” => false, “debug” => false, “filename” => “”, “lines” => 0}
user vars:
vars_int32: {“num” => 1, “size” => 99}
vars_string: {“weather” => “cold”, “day” => “monday”}
functions:
{“run”, #<Proc(String, Int32, Int32):0x56148908bbd0>}
{“list”, #<Proc(String, Int32, Int32):0x56148908ccb0>}
{“print”, #<Proc(String, Int32, Int32):0x561489070540>}
{“load”, #<Proc(String, Int32, Int32):0x5614890705e0>}
{“eval”, #<Proc(String, Int32, Int32):0x5614890716e0>}
{“after”, #<Proc(String, Int32, Int32):0x5614890751a0>}
{“+”, #<Proc(String, Int32, Int32):0x561489075900>}
{“<”, #<Proc(String, Int32, Int32):0x561489076c60>}
{“while”, #<Proc(String, Int32, Int32):0x561489076d60>}
{“every”, #<Proc(String, Int32, Int32):0x561489077100>}
{“split”, #<Proc(String, Int32, Int32):0x5614890778f0>}
{“ls”, #<Proc(String, Int32, Int32):0x56148908b560>}
{“let”, #<Proc(String, Int32, Int32):0x561489078580>}
{“p”, #<Proc(String, Int32, Int32):0x561489079410>}
{“!”, #<Proc(String, Int32, Int32):0x561489079cb0>}
{“now”, #<Proc(String, Int32, Int32):0x56148907e8b0>}
{“help”, #<Proc(String, Int32, Int32):0x56148908b310>}
{“debug”, #<Proc(String, Int32, Int32):0x56148908b810>}
{“test”, #<Proc(String, Int32, Int32):0x56148908ba00>}
{“pass”, #<Proc(String, Int32, Int32):0x56148908bb50>}
{“end”, #<Proc(String, Int32, Int32):0x56148908bb60>}
{“exit”, #<Proc(String, Int32, Int32):0x56148908bbc0>}

The first idea was to use procs to build a fast as possible text interpreter environment and see how it performs.

def procloop
  puts(Time.local.to_s("%H:%M:%S.%6N"))
  10.times {
    procs = {->foo, ->pass, ->bar}
    procs.each do |p|
      p.call
    end
  }
  puts(Time.local.to_s("%H:%M:%S.%6N"))
end

ICR is the best tool i now for inspecting single crystal commands.
tree and some other small scripts can be checked out here:
git clone GitHub - pebauer68/crystal: Small programs written in Crystal language

Ah gotcha. Are you always aware of crystal play command? I think it does something like what you’re wanting.

I guess I’m just curious where something like this would fit in both with icr and/or just having some file and doing crystal test.cr on it?

Crystal play is also a good tool, but i prefer a CLI interface.
From the tree prompt you can always start icr with:

! icr # exit and back to tree with ^D

Adding functions to tree is easy:
example: -add a clear console(aka cls) command
{“cls”, ->(x : String, y : Int32) { print “\33c\e[3J”; return 0 }},

I have added a ceval function which evalutes expressions via
the crystal binary - a bit like icr.

example:
ceval puts 1+2
The result is stored in a user(public) var as string:
{“ceval.result” => “3”}

I have extend my test environment with a sort of
lexer/token function which splits operators from vars e.g. “a+=1” -> “a + = 1”
but keeps quoted string in codelines as they are e.g. print “123±=7”
The quoted check is in quoted.cr, hope it is useful.

useful function:
def inside_quotes?(char,line,pos)
returns true if a char inside line is inside single or double quotes.
I have updated my first post, you can find tree on crstalshards now.

I have added a small demo video

1 Like

I have added interpreted functions to tree. It needed to have a local namespace for each function. For this i am using the hash datatype. Each functions does a name lookup in his namespace before reading/writing to a var.

functions = { } of String => Hash(String,(String|Int32)) 
p! functions

functions["hello"] = {  "line" => 1,
                    "args" => "test",
                 }

functions["goodbye"] =  {  "line" => 27,
                       "args" => "aha",
                    }                    

p! functions         
p! functions["hello"]["line"]
p! functions["hello"]["args"]
p! functions["goodbye"]["line"]
p! functions["goodbye"]["args"]

I added code injection to this test environment.
-this allows to inject a line of code from a file called “line.txt” in the current working dir into a running interpreter script when the inject flag is set to true.
-the file is automatically deleted after running this line

Example - How to use:
start tree with a while loop
code:

inject  #toggle code injection flag to true
counter=0
while true
  sleep 1
  inc counter
end

inject a print statement for counter from the command line:
$echo p counter > file.txt

I have added a ready to use logging component to the tree test environment.

# log to a file
# example output line:
# 2021-04-24 09:00:53.648818 INFO log cleared

require "log"
class Mylog
  Log = ::Log.for("tree", :debug)    
  #p! Log
  # => #<Log:0x7fe6e190f500 @source="tree",
  # @backend=#<Log::IOBackend:0x7fe6e190f600 
  # @dispatcher=#<Log::AsyncDispatcher:0x7fe6e190cd40 
  # @channel=#<Channel(Tuple(Log::Entry, Log::Backend)):0x7fe6e190f5c0>, 
  # @done=#<Channel(Nil):0x7fe6e190f580>>, 
  # @io=#<IO::FileDescriptor: fd=3>, 
  # @formatter=Log::ShortFormat>, 
  # @level=Debug, 
  # @initial_level=Info>  

def initialize
  @file_name = "./tree.log"  
  @backend = ::Log::IOBackend.new(fhandle=File.new(@file_name, "a"))
  @backend.formatter = ::Log::Formatter.new do |entry, io|
    io << entry.timestamp.to_s("%Y-%m-%d %H:%M:%S.%6N  ") 
    io << entry.severity.label << "  "
    io << entry.message
  end
  Log.backend = @backend
  print "Log init,logging to file: ",@file_name,"\n"
end

def clearlog
  @backend.close if @backend
  File.delete(@file_name) if File.exists?(@file_name)
  initialize
  self.info("log cleared")
end

def info(msg)
  Log.info { msg  }  
end  

def error(msg)
  Log.error { msg }
end

end

Tlog = Mylog.new # use constant for global visibility
Tlog.info “tree started”