Skip to content

A Ruby implementation of the Scheme language (a subset of)

License

Notifications You must be signed in to change notification settings

famished-tiger/Skeem

Repository files navigation

Skeem

Linux Windows
Linux Build Status Windows Build Status

Gem Version License

Skeem is a Scheme language interpreter written in Ruby.

Installation

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.

Usage

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.

Launching a REPL session

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::Undefinedinstance.

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

Terminating a REPL session

To exit a REPL session, call the exit procedure as follows:

(exit)

Running a Skeem file

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.

Embed Skeem in your Ruby app

This is the third way for Rubyists to interact with Skeem by integrating it directly in their Ruby code.

Example 1 (Variable definition)

  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

Example 2 (Defining a function)

  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

Example 3 (Defining a recursive function)

  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

Example 4 (Defining a procedure that holds its own environment)

  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

Example 5 (Conditional branching)

  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

Currently implemented R7RS features

Comments

  • Semi-colon delimited comments: ; This comment stops at the end of line
  • Block #| ... |# comments

Data type literals

  • 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")

Scheme Expressions

  • Constant literals
  • Quotations
  • Quasiquotation (without unquote-splicing)
  • Variable references
  • Procedure calls
  • Lambda expressions
  • Conditionals (if, cond)
  • Definitions
  • Assignments
  • Iteration (do)
  • Control procedures

Standard library

This section lists the procedures following closely the official Revised7 Report on the Algorithmic Language standard.

Equivalence predicates

  • eqv?, equal?

Boolean procedures

  • boolean?, boolean=?, and, or, not

Character procedures

  • char? char->integer, char=?, char<?, char>?,char<=?, char>=?

Numerical operations

  • 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 procedures

  • 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 procedures

  • string?, string=?, string, make-string, string-append, string-length, string->symbol

Symbol procedures

  • symbol?, symbol=?, symbol->string

Vector procedures

  • vector?, make-vector, vector, vector-length, vector-set!, vector->list

Control procedures

  • procedure?, apply, map

Input/output procedures

  • display, newline

Non-standard procedures

  • assert

Standard syntactic forms

define

Purpose: Create a new variable and bind an expression/value to it.
Syntax:

  • (define <identifier> <expression>)
  • (define (<variable> <formals>) <body>)

if

Purpose: Conditional evaluation based on a test expression.
Syntax:

  • (if <test> <consequent>)
  • (if <test> <consequent> <alternate>)

lambda

Purpose: Definition of a procedure.
Syntax:

  • (lambda <formals> <body>)

quote

Purpose: Quoting an expression (leave it unevaluated).
Syntax:

  • (quote <datum>)
  • '<datum>

set!

Purpose: Assign to an existing variable an expression/value to it.
Syntax:

  • (set! <identifier> <expression>)

Derived expressions

cond

Purpose: Define one or more branchings.
Syntax:

  • (cond (<test> <consequent>)+)
  • (cond (<test><consequent>)* (else <alternate>))

do

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)

let

Purpose: Define one or more variable local to the block.
Syntax:

  • (let (<binding_spec*>) <body>)

let*

Purpose: Define one or more variable local to the block.
Syntax:

  • (let* (<binding_spec*>) <body>)

Roadmap

  • 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.

About Scheme

The Scheme programming language is a Lisp dialect that supports multiple paradigms, including functional programming and imperative programming.

Resources on Scheme

Here are a few pointers for the Scheme programming language:

  • Wikipedia article on Scheme
  • Latest official Scheme standard: R7RS

Online tutorials and books:

Other Scheme implementations in Ruby

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.

  • rubic gem. The last commit for the project is June 2015.

  • RLisp ...Simple scheme interpreter written in ruby

Contributing

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.

License

Copyright

Copyright (c) 2018-2021, Dimitri Geshef.
Skeem is released under the MIT License see LICENSE.txt for details.

Code of Conduct

Everyone interacting in the Skeem project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

A Ruby implementation of the Scheme language (a subset of)

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published