Linux | Windows |
---|---|
Skeem is a Scheme language interpreter written in Ruby.
Add this line to your application's Gemfile:
gem 'skeem'
And then execute:
$ bundle
Or install it yourself as:
$ gem install skeem
The Skeem project is WIP and currently the gem supports a subset of the Scheme language.
If you're not familiar to Scheme, the section About Scheme contains a number of interesting pointers.
Once the gem is installed, the skeem
executable can be used.
It allows to run the interpreter from a REPL console or from the command line.
Another way to interact with the Skeem interpreter is to embed it in your Ruby code.
To start a REPL (Read-Eval-Print-Loop) session, just type:
skeem
Skeem displays a greeting, a prompt and then waits for your input:
Welcome to Skeem 0.2.16.
>
Now that we know that Skeem
is well alive, let's kick it...
We begin with the ritual 'Hello world' example, by typing after the > prompt:
(display "Hello, world")
Skeem then replies:
Hello, world
Skeem::SkmUndefined
This works as expected except, maybe, for the last line. It can be easily explained if one knows
that the return value of the display
procedure is undefined in standard Scheme.
Internally Skeem, implements such undefined result as a Skeem::Undefined
instance.
To give some taste of things, here is an excerpt from a REPL session:
> (+ 4 (* 5 6))
34
> (define x 6)
6
> (+ (* 5 x x) (* 4 x) 3)
207
> (/ 21 5)
21/5
> (/ 21.0 5)
21/5
To exit a REPL session, call the exit
procedure as follows:
(exit)
To run a Scheme file:
skeem path/to/some-file.skm
By the way, the /bin
folder of the skeem
gem contains a couple of Skeem sample files.
This is the third way for Rubyists to interact with Skeem by integrating it directly in their Ruby code.
require 'skeem'
schemer = Skeem::Interpreter.new
scheme_code =<<-SKEEM
; This heredoc consists of Scheme code...
; Let's define a Scheme variable
(define foobar (* 2 3 7))
; Now test its value against a lower value
(if (> foobar 40) #true #false)
SKEEM
# Ask Ruby to execute Scheme code
result = schemer.run(scheme_code)
puts result.value # => true
# The interpreter object keeps the bindings of variable
# Let's test that...
scheme_code = '(* foobar foobar)'
result = schemer.run(scheme_code)
puts result.value # => 1764
require 'skeem'
schemer = Skeem::Interpreter.new
scheme_code =<<-SKEEM
; Let's implement the 'min' function
(define min (lambda (x y) (if (< x y) x y)))
; What is the minimum of 2 and 3?
(min 2 3)
SKEEM
# Ask Ruby to execute Scheme code
result = schemer.run(scheme_code)
puts result.value # => 2
# Let's retry with other values
scheme_code = '(min 42 3)'
result = schemer.run(scheme_code)
puts result.value # => 3
require 'skeem'
schemer = Skeem::Interpreter.new
scheme_code = <<-SKEEM
; Compute the factorial of 100
(define fact (lambda (n)
(if (<= n 1) 1 (* n (fact (- n 1))))))
(fact 100)
SKEEM
result = schemer.run(scheme_code)
puts result.value # => 9332621544394415268169923885626670049071596826438162146859296389521759999322991560894146397615651828625369792082722375825118521091686400000000000000000000000
require 'skeem'
schemer = Skeem::Interpreter.new
scheme_code = <<-SKEEM
(define make-counter
;; create a procedure that will bind count and
;; return a new procedure that will iself increment the
;; variable and return its newest value
(lambda ()
(let ((count 0))
(lambda ()
(set! count (+ count 1))
count))))
(define c1 (make-counter))
(define c2 (make-counter))
(define c3 (make-counter))
;; Notice how each procedure keeps track of its "own" counter.
(c1) ; => 1
(c2) ; => 1
(c1) ; => 2
(c3) ; => 1
(c1) ; => 3
SKEEM
result = schemer.run(scheme_code)
puts result.last.value # => 3
require 'skeem'
schemer = Skeem::Interpreter.new
scheme_code =<<-SKEEM
; Let's implement the 'signum' function
(define signum (lambda (x)
(cond
((positive? x) 1)
((zero? x) 0)
(else -1))))
(signum -3)
SKEEM
result = schemer.run(scheme_code)
puts result.value # => -1
- Semi-colon delimited comments:
; This comment stops at the end of line
- Block
#| ... |#
comments
- Booleans:
#t
,#true
,#f
,#false
- Characters:
#\a
,#\newline
,#\x3BB
- Of the number hierarchy:
real
(e.g. 2.718, 6.671e-11),
rational
(e.g. 22/7, 1/137, -13/41)
integer
(42, -3 also in hexadecimal notation: #x3af) - Lists (quoted) : '(1 two "three")
- Strings:
"Hello, world."
- Identifiers (symbols):
really-cool-procedure
- Vectors:
#(1 2 "three")
- Constant literals
- Quotations
- Quasiquotation (without unquote-splicing)
- Variable references
- Procedure calls
- Lambda expressions
- Conditionals (
if
,cond
) - Definitions
- Assignments
- Iteration (
do
) - Control procedures
This section lists the procedures following closely the official Revised7 Report on the Algorithmic Language standard.
eqv?
,equal?
boolean?
,boolean=?
,and
,or
,not
char?
char->integer
,char=?
,char<?
,char>?
,char<=?
,char>=?
- Number-level:
number?
,complex?
,real?
,rational?
,integer?
,zero?
,
exact?
,inexact?
,exact-integer?
,+
,-
,*
,/
,=
,square
,number->string
- Real-level:
positive?
,negative?
,<
,>
,<=
,>=
,abs
,max
,min
,
floor/
,floor-quotient
,floor-remainder
,truncate/
,truncate-quotient
,
truncate-remainder
,quotient
,remainder
,modulo
,gcd
,lcm
,numerator
,
denominator
,floor
,ceiling
,truncate
,round
- Integer-level:
even?
,odd?
,integer->char
list?
,null?
,pair?
,append
,car
,cdr
,caar
,cadr
,cdar
,cddr
,
cons
,make-list
,length
,list
,list-copy
,list->vector
,reverse
,
set-car!
,set-cdr!
,assq
,assv
string?
,string=?
,string
,make-string
,string-append
,string-length
,string->symbol
symbol?
,symbol=?
,symbol->string
vector?
,make-vector
,vector
,vector-length
,vector-set!
,vector->list
procedure?
,apply
,map
display
,newline
assert
Purpose: Create a new variable and bind an expression/value to it.
Syntax:
- (define <identifier> <expression>)
- (define (<variable> <formals>) <body>)
Purpose: Conditional evaluation based on a test expression.
Syntax:
- (if <test> <consequent>)
- (if <test> <consequent> <alternate>)
Purpose: Definition of a procedure.
Syntax:
- (lambda <formals> <body>)
Purpose: Quoting an expression (leave it unevaluated).
Syntax:
- (quote <datum>)
- '<datum>
Purpose: Assign to an existing variable an expression/value to it.
Syntax:
- (set! <identifier> <expression>)
Purpose: Define one or more branchings.
Syntax:
- (cond (<test> <consequent>)+)
- (cond (<test><consequent>)* (else <alternate>))
Purpose: Sequential iteration
Example
(do (
(vec (make-vector 5))
(i 0 (+ i 1)))
((= i 5) vec)
(vector-set! vec i i)) ; => #(0 1 2 3 4)
Purpose: Define one or more variable local to the block.
Syntax:
- (let (<binding_spec*>) <body>)
Purpose: Define one or more variable local to the block.
Syntax:
- (let* (<binding_spec*>) <body>)
- Implement an equivalent of lis.py
- Implement an equivalent of lispy
- Make it pass the test suite
- Extend the language in order to support Minikanren
- Make it pass all examples from the Reasoned Schemer book.
The Scheme programming language is a Lisp dialect that supports multiple paradigms, including functional programming and imperative programming.
Here are a few pointers for the Scheme programming language:
- The Scheme Programming Language, 4th Edition by Kent Dybvig. A complete, introductory textbook on Scheme based on the older R5RS standard.
- Teach Yourself Scheme in Fixnum Days by Dorai Sitaram
- Yet Another Scheme Tutorial by Shido Takafumi
- An Introduction to Scheme and its Implementation by Paul R. Wilson
Skeem isn't the sole implementation of the Scheme language in Ruby.
Here are a few other ones:
-
Heist gem -- Probably one of best Scheme implementation in Ruby. Really worth a try. Alas, the project seems to be dormant for several years.
-
Schemerald gem. The last commit for the project is October 2017.
-
RLisp ...Simple scheme interpreter written in ruby
Bug reports and pull requests are welcome on GitHub at https://github.com/famished-tiger/Skeem. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
Copyright (c) 2018-2021, Dimitri Geshef.
Skeem is released under the MIT License see LICENSE.txt for details.
Everyone interacting in the Skeem project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.