Sabre is highly customisable, embeddable LISP engine for Go. :computer:

Overview

Sabre

GoDoc Go Report Card Build Status

DEPRECATED: This repository is deprecated in favour much better slurp project and will be archived/removed soon.

Sabre is highly customizable, embeddable LISP engine for Go.

Check out Slang for a tiny LISP written using Sabre.

Features

  • Highly Customizable reader/parser through a read table (Inspired by Clojure) (See Reader)
  • Built-in data types: nil, bool, string, number, character, keyword, symbol, list, vector, set, hash-map and module.
  • Multiple number formats supported: decimal, octal, hexadecimal, radix and scientific notations.
  • Full unicode support. Symbols can include unicode characters (Example: find-δ, π etc.) and 🧠, 🏃 etc. (yes, smileys too).
  • Character Literals with support for:
    1. simple literals (e.g., \a for a)
    2. special literals (e.g., \newline, \tab etc.)
    3. unicode literals (e.g., \u00A5 for ¥ etc.)
  • Clojure style built-in special forms: fn*, def, if, do, throw, let*
  • Simple interface sabre.Value and optional sabre.Invokable, sabre.Seq interfaces for adding custom data types. (See Evaluation)
  • A macro system.

Please note that Sabre is NOT an implementation of a particular LISP dialect. It provides pieces that can be used to build a LISP dialect or can be used as a scripting layer.

Usage

What can you use it for?

  1. Embedded script engine to provide dynamic behavior without requiring re-compilation of your application.
  2. Business rule engine by exposing very specific & composable rule functions.
  3. To build your own LISP dialect.

Sabre requires Go 1.13 or higher.

As Embedded Script Engine

Sabre has concept of Scope which is responsible for maintaining bindings. You can bind any Go value and access it using LISP code, which makes it possible to expose parts of your API and make it scriptable or build your own LISP dialect. Also, See Extending for more information on customizing the reader or eval.

package main

import "github.com/spy16/sabre"

func main() {
    scope := sabre.NewScope(nil)
    _ = scope.BindGo("inc", func(v int) int { return v+1 })

    result, _ := sabre.ReadEvalStr(scope, "(inc 10)")
    fmt.Printf("Result: %v\n", result) // should print "Result: 11"
}

Expose through a REPL

Sabre comes with a tiny repl package that is very flexible and easy to setup to expose your LISP through a read-eval-print-loop.

package main

import (
  "context"

  "github.com/spy16/sabre"
  "github.com/spy16/sabre/repl"
)

func main() {
  scope := sabre.NewScope(nil)
  scope.BindGo("inc", func(v int) int { return v+1 })

  repl.New(scope,
    repl.WithBanner("Welcome to my own LISP!"),
    repl.WithPrompts("=>", "|"),
    // many more options available
  ).Loop(context.Background())
}

Standalone

Sabre has a small reference LISP dialect named Slang (short for Sabre Lang) for which a standalone binary is available. Check out Slang for instructions on installing Slang.

Extending

Reader

Sabre reader is inspired by Clojure reader and uses a read table. Reader supports following forms:

  • Numbers:
    • Integers use int64 Go representation and can be specified using decimal, binary hexadecimal or radix notations. (e.g., 123, -123, 0b101011, 0xAF, 2r10100, 8r126 etc.)
    • Floating point numbers use float64 Go representation and can be specified using decimal notation or scientific notation. (e.g.: 3.1412, -1.234, 1e-5, 2e3, 1.5e3 etc.)
  • Characters: Characters use rune or uint8 Go representation and can be written in 3 ways:
    • Simple: \a, , etc.
    • Special: \newline, \tab etc.
    • Unicode: \u1267
  • Boolean: true or false are converted to Bool type.
  • Nil: nil is represented as a zero-allocation empty struct in Go.
  • Keywords: Keywords are like symbols but start with : and evaluate to themselves.
  • Symbols: Symbols can be used to name a value and can contain any Unicode symbol.
  • Lists: Lists are zero or more forms contained within parenthesis. (e.g., (1 2 3), (1 [])). Evaluating a list leads to an invocation.
  • Vectors: Vectors are zero or more forms contained within brackets. (e.g., [], [1 2 3])
  • Sets: Set is a container for zero or more unique forms. (e.g. #{1 2 3})
  • HashMaps: HashMap is a container for key-value pairs (e.g., {:name "Bob" :age 10})

Reader can be extended to add new syntactical features by adding reader macros to the read table. Reader Macros are implementations of sabre.ReaderMacro function type. Except numbers and symbols, everything else supported by the reader is implemented using reader macros.

Evaluation

  • Keyword, String, Int, Float, Character, Bool, nil, MultiFn, Fn, Type and Any evaluate to themselves.
  • Symbol is resolved as follows:
    • If symbol has no ., symbol is directly used to lookup in current Scope to find the value.
    • If symbol is qualified (i.e., contains .), symbol is split using . as delimiter and first field is resolved as per previous rule and rest of the fields are recursively resolved as members. (For example, foo.Bar.Baz: foo is resolved from scope, Bar should be member of value of foo. And Baz should be member of value resolved for foo.Bar)
  • Evaluating HashMap, Vector & Set simply yields new hashmap, vector and set whose values are evaluated values contained in the original hashmaap, vector and set.
  • Evaluating Module evaluates all the forms in the module and returns the result of last evaluation. Any error stops the evaluation process.
  • Empty List is returned as is.
  • Non empty List is an invocation and evaluated using following rules:
    • If the first argument resolves to a special-form (SpecialForm Go type), it is invoked and return value is cached in the list. This return value is used for evaluating the list.
    • If the first argument resolves to a Macro, macro is invoked with the rest of the list as arguments and return value replaces the list with (do retval) form.
    • If first value resolves to an Invokable value, Invoke() is called. Functions are implemented using MultiFn which implements Invokable. Vector also implements Invokable and provides index access.
    • It is an error.
Comments
  • Re-organize single sabre package into core, reader and sabre

    Re-organize single sabre package into core, reader and sabre

    Still work-in-progress. Highlights of the refactor here:

    1. Bulk of the core LISP related functionality now lives in core package.
    2. Reader is in a reader package.
    3. sabre root package provides the same interface for using sabre as before. (i.e., New(), ReadEvalStr, ReadEval, ValueOf etc.)

    I was experimenting with the idea discussed in #16 .. But most likely i will go back to the old Value interface itself.

    Feel free to provide feedback and design inputs if any.

    Upcoming changes:

    1. Move Set, HashMap and Module to sabre package instead of core ? (just a thought.. since these are not really very well implemented at the moment and are not really necessary for pure LISP like usage)
    2. Re-define special-forms system. Expose Analyse as public function to allow adding custom special forms? (I don't want to expose since either the special form is very generic in which case users can raise a PR and we can merge it into Sabre. If not, it probably can be implemented as a macro/function)..
    opened by spy16 17
  • Member access for Go types from Sabre

    Member access for Go types from Sabre

    Sabre should be able to access member fields/methods of values bound in the scope.

    Clojure uses (.MethodName target) or (.-FieldName target) for method and field accesses repsectively.

    I am thinking this differentiation is not required in Go since Go types cannot have method+field of same name. ~But (.MemberName target) seems like a reasonable choice.~ In favor of keeping special handling to minimum, i am leaning towards exposing a special . function (. member-name target) which finds the member of the target with the given name and returns it,

    Any thoughts on this ? @lthibault

    opened by spy16 12
  • Sabre Experience Report

    Sabre Experience Report

    Hi @spy16, I hope you're well and that things are returning to normal on your end.

    As mentioned over in https://github.com/spy16/sabre/pull/26 and https://github.com/spy16/sabre/pull/27, I've been making heavy use of Sabre over the past few weeks in the context of Wetware, so I thought I'd share my thoughts on what works and what can be improved.

    I'm well aware that the current runtime design has shown some limitations, and comfortable with the fact that parts of Wetware will have to be rewritten once we iron out the creases. My goal in publishing this is to:

    • fill you in on the design requirements for Wetware (as per your comment over at https://github.com/spy16/sabre/pull/26#issuecomment-675853479).
    • follow up on https://github.com/spy16/sabre/pull/26 and hopefully start working on the Clojure-style runtime. I've made room in my schedule to lend a hand, but am looking to you for general direction. 😃

    This report is structured as follows:

    1. Context: A detailed description of Wetware, and how Sabre fits into the picture. It's a bit lenghty because I wanted to err on the side of completeness. Please forgive me as I slip into pitch-mode from time to time :sweat_smile:!
    2. The Good Parts of working with Sabre. This section highlights where Sabre provides a net gain in productivity, and was generally a delight to use.
    3. Pain Points, Papercuts and Suggestions. This section highlights where Sabre could be improved. I've tried to be as specific as possible, and to link to Wetware source code where appropriate. Possible solutions to these issues are also discussed.
    4. Miscellanea that are generally on my mind. These are basically issues that I expect to encounter in the mid-to-near term. They are important, but not urgent.

    Context

    Wetware is a distributed programming language for the cloud. Think Mesos + Lisp. Or Kubernetes + Lisp, if you prefer.

    Wetware abstracts your datacenters and cloud environments into a single virtual cloud, and provides you with a simple yet powerful API for building fault-tolerant and elastic systems at scale.

    It achieves its goals by layering three core technologies:

    1. A Cloud Management Protocol

    At its core, Wetware is powered by a simple peer-to-peer protocol that allows hosts to discover each other over the network, and assembles them into a fully-featured virtual cloud.

    This virtual cloud is self-healing (i.e. antifragile), truly distributed (with no single point of failure), and comes with out-of-the box support for essential cloud services, including:

    • Elastic process management using Virtual Machines (like Amazon EC2), Containers (like Amazon ECS), bare-metal UNIX processes, and even ultra-lightweight threads (goroutines).
    • Persistent storage with support for binary blobs (like Amazon S3) and even structured data (maps, lists, strings, sets, etc.).
    • Interprocess communication and orchestration via distributed PubSub (like Amazon SQS) and Channels (i.e. network-aware queues).

    Wetware's Cloud Management Protocol works out-of-the-box, requires zero configuration, and features first-class support for hybrid and multicloud architectures.

    2. A Distributed Data Plane

    Unifying data across applications is a major challenge for current cloud architectures. Developers have to deal with dozens (sometimes even hundreds) of independent applications, each producing, encoding and serializing data in its own way. In traditional clouds, ETL and other data operations are time-consuming, error-prone and often require specialized stacks.

    Wetware solves this problem by providing

    1. High-performance, immutable datastructures for representing data across applications,
    2. High-throughput protocols for efficiently sharing large datastructures across the network, and
    3. An intuitive API for working concurrently with data in a cluster-wide, shared-memory space.

    With Wetware's dataplane, you can coordinate millions of concurrent processes to work on terabyte-sized maps, sets, lists, etc. These immutable and wire-native datastructures protect you from concurrency bugs while avoiding overhead due to (de)serialization.

    Lastly, Wetware's location-aware caching means you're always fetching data from the nearest source, avoiding egress costs in hybrid and multicloud environments.

    3. A Dynamic Programming Language

    The Wetware REPL is the primary means through which users interact with their virtual cloud, and the applications running on top of it. Unsurprisingly, this REPL is a Lisp dialect built with Sabre.

    Let's walk through a few examples.

    We can simulate a datacenter from the comfort of our laptop by starting any number of Wetware host processes:

    # Start a host daemon.
    #
    # In production, you would run this command once on each
    # datacenter host or cloud instance.
    #
    # In development, you can simulate a datacenter/cloud of
    # n hosts by running this command n times.
    $ ww start
    

    Next, we start the Wetware REPL and instruct it to dial into the cluster we created above.

    # The -dial flag auto-discovers and connects to an
    # arbitrary host process.  By default, the shell runs
    # locally, without connecting to a virtual cloud.
    $ ww shell -dial
    

    We're greeted with an interactive shell that looks like this:

    Wetware v0.0.0
    Copyright 2020 The Wetware Project
    Compiled with go1.15 for darwin
    
    ww »
    

    From here, we can list the hosts in the cloud. If new hosts appear, or if existing hosts fail, these changes to the cluster will be reflected in subsequent calls to ls.

    
    ww » (ls /) ;; list all hosts in the cluster
    [
        /SV4e8BwRMPmShMPRcfTmpfTZQN7JQFaqzwt9g2wrF5bj
        /cie5uM1dAuQcbTEpHi4GKsghNVBk6H4orjVs6fmd16vV
    ]
    

    The ls command returns a core.Vector, which contains a special, Wetware-specific data type: core.Path. These paths point to special locations called ww.Anchor. Anchors are cluster-wide, shared-memory locations. Any Wetware process can read or write to an Anchor, and the Wetware language provides synchronization primitives to deal with the hazards of concurrency and shared memory.

    Anchors are organized hierarchically. The root anchor / represents the whole cluster, and its children represent physical hosts. Children of hosts are created dynamically upon access, and can contain any Wetware datatype.

    ;; Anchors are created transparently on access.
    ;; You can retrieve the value stored in an Anchor
    ;; by invoking its Path without arguments.
    ;; Anchors are empty by default.
    ww » (/SV4e8BwRMPmShMPRcfTmpfTZQN7JQFaqzwt9g2wrF5bj/foo)
    nil
    
    ;; Invoking a Path with a single argument stores
    ;; the value in the corresponding Anchor.
    ww » (/SV4e8BwRMPmShMPRcfTmpfTZQN7JQFaqzwt9g2wrF5bj/foo
       ›   {:foo "foo value"
       ›    :bar 42
       ›    :baz ["hello" "world"] })
    nil
    
    ;; The stored value is accessible by _any_ Wetware
    ;; process in the cluster.
    ;;
    ;; Let's fetch it from a goroutine running in the
    ;; remote host `cie5uM1...`
    ww » (go /cie5uM1dAuQcbTEpHi4GKsghNVBk6H4orjVs6fmd16vV)
       ›   (print (/SV4e8BwRMPmShMPRcfTmpfTZQN7JQFaqzwt9g2wrF5bj/foo)))
    nil
    

    Why did this print nil? Because the form (print (/SV4e8.../foo)) was executed on the remote host cie5uM...! That is, the following things happened:

    1. A network connection to cie5uM... was opened.
    2. The list corresponding to the print function call was sent over the wire.
    3. On the other side, cie5uM... received the list and evaluated it.
    4. During evaluation, cie5uM... fetched the value from the Sv4e8.../foo Anchor and printed it.

    If we were to check cie5uM...'s logs, we would see the corresponding output.

    Important Technical Note: Wetware's datastructures are implemented using the Cap'n Proto schema language, meaning their in-memory representations do not need to be serialized in order to be sent across the network.

    Our heavy reliance on capnp has implications for the design of varous Sabre interfaces, as discussed in part 2.

    This concludes general introduction to Wetware.

    While Wetware is very much in a pre-alpha stage, the foundational code for features 1 - 3 are in place, and the overall design has been validated. Now that we are leaving the proof-of-concept stage, developing the language (and its standard library) will be the focus of the next few months. For this reason, Sabre will continue play a central role in near-term development and I expect to split my development time roughly equally between Wetware and Sabre. As such, I'm hoping the following feedback can serve as a synchronization point between us, and motivate the next few PRs.

    The Good Parts

    (N.B.: I am exclusively developing on the reader branch, which is itself a branch of runtime.)

    Overall, Sabre succeeds in its mission to be an "80% Lisp". The pieces fit together quite well, and most things are easily configurable. This last bit is particularly true of the runtime branch where I was able to write custom implementations for each atom/collection, as well as create some new, specialized datatypes. I have not encountered any fundamental design flaws, which is great!!

    The REPL is a breeze to use, requring little effort to set up and configure. This is in large part thanks to your decision to make REPL (and Reader for that matter) concrete structs that hold interfaces internally, as opposed to declaring them as interface types. Doing so allows us to inject dependencies via functional arguments rather than re-writing a whole new implementation just to make minor changes to behavior. The result is a REPL that took me less time to set up than to write this paragraph, so this is a pattern we should definitely continue to exploit.

    Relatedly, I think these few lines of code really showcase the ergonomics of functional options. They compose well, are discoverable & extensible, and visually cue the reader to the fact that the repl.New constructor is holding everything in the package together. I'm disproportionately pleased with the outcome.

    Lastly, the built-in datatypes are very useful when developing one's own language because they serve as simple stubs until custom datastructures have been developed. In practice, this means I was able to develop other parts of the language in spite of the fact that e.g. Vectors had not yet been implemented in Wetware. It's hard to overstate not only how incredibly useful this is, and how much of that usefulness stems from the fact that Sabre is using native Go datastructures under the hood. Designing one's own language is quite hard, so every ounce of simplicity and familiarity is a godsend. I am strongly in favor of maintaining the existing implementations and not adding persistent datatypes for this reason. An exception might be made for LinkedList since the current implementation is dead-simple and shoe-horning a linked-list into a []runtime.Value is a bit ... backwards. In any case, Sabre really came through for me, here.

    Pain Points, Papercuts & Suggestions

    I want to stress that this section is longer than its predecessor not because there are more downsides than upsides in Sabre, but because there's always more to say about problems than non-problems! With that said, I've sorted the pain-points I've encountered into a few broad buckets:

    1. Error handling
    2. Design of container types
    3. Reader design

    Error Handling

    By far the biggest issue I encountered was the handling of errors inside datastructure methods. Throughout our design discussion in #25, our thinking was (understandably) anchored to the existing implementations for Map, Vector, etc. Specifically, we assumed that certain operations (e.g. Count() int) could not result in errors. This turns out to have been an incorrect assumption.

    As mentioned in the Context section above, Wetware's core datastructures are generated from a Cap'n Proto schema. As such, simple things such as calling an accessor function often return errors, including for methods like core.Vector.Count(). The result is that my code is quite panicky: Count, Conj, First and Next all panic.

    While there are (quite convoluted) ways of avoiding these panics, I think there's a strong argument for changing the method signatures to return errors. Sabre is intended as a general-purpose build-your-own-lisp toolkit, and predicting what users will do with it is nigh impossible. For example, they may write datastructures implemented by SQL tables, which make RPC calls, or which interact with all manner of exotic code. As such, I think we should take the most general approach, which means returning errors almost everywhere.

    Design of Container Types

    This issue is pretty straightforward. I'd like to implement an analog to Clojure's conj that works on arbitrary containers. Currently, runtime.Vector.Conj returns a Vector, so I'm wondering how this might work. Do you think it's best to resort to reflection in such cases? Might it not be better to return runtime.Value from all Conj methods?

    Reader Design

    Despite being generally well-designed, there is room for improvement in reader.Reader.

    Firstly, https://github.com/spy16/sabre/pull/27 adds the ability to modify the table of predefined symbols, which was essential in my case as I have custom implementations for Nil and Bool.

    Secondly, relying on Reader.Container to build containers is not appropriate for all situations. The Container method reads a stream of values into a []runtime.Value, and returns it for further processing. In the case of Wetware's core.Vector, this is quite inefficient since:

    1. I need to allocate a []runtime.Value.
    2. I might need to grow the []runtime.Value, causing additional allocs, but I can't predict the size of the container ahead of time.
    3. Once the []runtime.Value is instantiated, I have to loop through it and call core.VectorBuilder.Conj, which also allocates.

    In order to avoid the penalty of double-allocation, I wrote readContainerStream, which applies a function to each value as it is decoded by the reader. The performance improvement is significant for large vectors, so I think we should add it as a public method to reader.Reader.

    Thirdly, Wetware's reliance on Cap'n Proto means that I must implement custom numeric types. To make matters more complicated, I would like to add additional numeric types analogous to Go's big.Int, big.Float, and big.Rat. As such, I will need the ability to configure the reader's parsing logic for numerical values.

    Currently, numerical parsing is hard-coded into the Reader. I suggest adding a reader option called WithNumReader (or perhaps WithNumMacro?) that allows users to configure this bit of logic. I expect this will also have repercussions on sabre.ValueOf, but it should be noted that this function is already outdated with respect to the new runtime datastructure interfaces.

    Miscellanea

    Lastly, a few notes/questions that are on my mind, but not particularly urgent:

    1. The Position type seems very useful, but I'm not sure how it's meant to be used. Who is responsible for keeping it up-to-date, exactly? Any "use it this way" notes you might have would be helpful.
    2. I don't quite understand the distinction between GoFunc, Fn and MultiFn. Best I can figure, GoFunc is used to call a native Go function from Sabre, while (Multi)Fn is meant to be dynamically instantiated by defn? From there, I assume MultiFn is used for multi-arity defn forms? (I think I might have answered my own question :smile:)
    3. I expect to start thinking about Macros in 6-8 weeks or so. Are there any major changes planned for the macro system, or can I rely on what already exists?
    4. I'm going to tackle goroutine invokation within the next 2-3 weeks and will keep you appraised of my progress in #15. If you have any thoughts on the subject, I'm very much interested.

    Conclusion

    I hope you find it as useful to read this experience report as I have found it useful to write. I'm eager to discuss all of this at your earliest convenience, and standing by to help with implementation! :slightly_smiling_face:

    opened by lthibault 11
  • Cannot build with GO111MODULE=1

    Cannot build with GO111MODULE=1

    Hi @spy16 ,

    I just tried building a project that imports sabre using Go's module system:

    $go run cmd/ww/main.go
    build command-line-arguments: cannot load github.com/spy16/sabre/core: module github.com/spy16/[email protected] found (v0.1.1), but does not contain package github.com/spy16/sabre/core
    

    Do you know what the problem might be? I'm happy to push a fix.

    opened by lthibault 8
  • Dynamic namespace similar to Clojure

    Dynamic namespace similar to Clojure

    Hi @spy16 ,

    I've been making strides on my lisp project, and I'd like to implement something similar to Clojure's ns special form (minus the Java interop stuff, obviously).

    My intuition is that this kind of functionality falls outside of Sabre's scope (similar to https://github.com/spy16/sabre/issues/3).

    If that's the case, I'm not sure how to go about implementing this. Any suggestions?

    opened by lthibault 5
  • Support for macros & special forms

    Support for macros & special forms

    • A MacroFn invokable value type (i.e., implements both sabre.Value and sabre.Invokable) which expands the macro definition and evaluates when invoked.
    • A way to define the macro. defmacro
    • A way to see the macro expansion without evaluating - macroexpand
    enhancement 
    opened by spy16 4
  • Add basic support for recur

    Add basic support for recur

    Hi guys, I've added a basic support for recur which will optimize function to create tail-call optimization.

    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/spy16/sabre"
    )
    
    const rangeTco = `
    (def range (fn* range [min max coll]
                     (if (= min max)
                       coll
                       (recur (inc min) max (coll.Conj min)))))
    
    (print (range 0 10 '()))
    (range 0 1000 '())
    `
    
    const rangeNotTco = `
    (def range (fn* range [min max coll]
                     (if (= min max)
                       coll
                       (range (inc min) max (coll.Conj min)))))
    
    (print (range 0 10 '()))
    (range 0 1000 '())
    `
    
    func main() {
    	scope := sabre.New()
    	scope.BindGo("inc", inc)
    	scope.BindGo("print", fmt.Println)
    	scope.BindGo("=", sabre.Compare)
    
    	initial := time.Now()
    	_, err := sabre.ReadEvalStr(scope, rangeNotTco)
    	if err != nil {
    		panic(err)
    	}
    	final := time.Since(initial)
    	fmt.Printf("no recur: %s\n", final)
    
    	initial = time.Now()
    	_, err = sabre.ReadEvalStr(scope, rangeTco)
    	if err != nil {
    		panic(err)
    	}
    	final = time.Since(initial)
    	fmt.Printf("recur: %s\n", final)
    }
    
    func inc(val sabre.Int64) sabre.Int64 {
    	return val + 1
    }
    

    result:

    $ go run main.go
    (0 1 2 3 4 5 6 7 8 9)
    no recur: 319.056393ms
    (0 1 2 3 4 5 6 7 8 9)
    recur: 14.510449ms
    
    opened by issadarkthing 3
  • Migrate parens standard library to sabre

    Migrate parens standard library to sabre

    Are there any plans to migrate parens' standard library to sabre? I see that some of the basic symbols are bound in the core package, but it's still missing a number of basics e.g. +.

    Is this something sabre should provide?

    opened by lthibault 3
  • Moving Slang to separate repository

    Moving Slang to separate repository

    Now that the repository has become somewhat large, i am thinking, May be it would be better to move slang into its own repository.

    @lthibault Thoughts?

    opened by spy16 2
  • Remove all type specific functions in favor of generic core

    Remove all type specific functions in favor of generic core

    • Removes all type check functions from Go code (vector?, string? etc.)
    • Adds generic functions like to-type, type and type invocation for initialization
    • Adds core.lisp file that defines vector?, string? etc.
    opened by spy16 2
  • [WIP] Re-organize single sabre package into core, reader and sabre

    [WIP] Re-organize single sabre package into core, reader and sabre

    Still work-in-progress. Highlights of the refactor here:

    1. Bulk of the core LISP related functionality now lives in core package.
    2. Reader is in a reader package.
    3. sabre root package provides the same interface for using sabre as before. (i.e., New(), ReadEvalStr, ReadEval, ValueOf etc.)

    I was experimenting with the idea discussed in #16 .. But most likely i will go back to the old Value interface itself.

    Feel free to provide feedback and design inputs if any.

    opened by spy16 1
  • Invoke on List type

    Invoke on List type

    Hi, how do I invoke an Invokable with List as its argument without the List is being evaluated? Here's a simple implementation to create a reduce function which requires the callback function to accept List and Int64 type.

    package main
    
    import (
    	"fmt"
    
    	"github.com/spy16/sabre"
    )
    
    const program = `
    (print (reduce (fn [acc x] (acc.Cons x)) '() '(1 2 3)))
    `
    
    func main() {
    	scope := sabre.New()
    	scope.BindGo("sum", sum)
    	scope.BindGo("print", fmt.Println)
    	scope.BindGo("fn", sabre.Lambda)
    	scope.BindGo("reduce", reduce)
    
    	_, err := sabre.ReadEvalStr(scope, program)
    	if err != nil {
    		panic(err)
    	}
    }
    
    func reduce(fn sabre.MultiFn, acc sabre.Value, list *sabre.List) (sabre.Value, error) {
    
    	for _, v := range list.Values {
    		applied, err := fn.Invoke(nil, acc, v)
    		if err != nil {
    			return nil, err
    		}
    		acc = applied
    	}
    
    	return acc, nil
    }
    

    This will produce this error.

    $ go run main.go
    panic: eval-error in '' (at line 0:0): cannot invoke value of type 'sabre.Int64'
    
    goroutine 1 [running]:
    main.main()
    	/home/terra/Documents/system33/go/sabre/examples/simple/main.go:22 +0x194
    exit status 2
    
    opened by issadarkthing 0
  • Add Reader options

    Add Reader options

    As part of Wetware, the need has arisen reimplement virtually every atom. Accordingly, I also need to adjust the map of predefined symbols to reflect my own implementation of Nil, and Bool.

    This PR implements the Option type for reader.Reader. Currently, options exist to configure:

    • the macro table
    • the dispatch table
    • predefined symbols

    ⏱️ Estimated review time: < 10 minutes ✅ Merge when ready

    opened by lthibault 0
  • Runtime & Better Contracts

    Runtime & Better Contracts

    This PR has the new structure and types as per the discussions on #25 . New contracts are nested within a sub-package sabre for the time being (Just to make it easy to move.)..

    Highlights:

    • runtime contains all the core interfaces/types that Sabre deals with - Runtime, Value, Invokable, Seq, Seqable, Vector, Map, Set & a common Error type.
    • reader package contains the Reader. reader.New returns a reader instance that can read the primitive types defined in runtime. For map, vectors, sets,reader provides MapReader(mapFactory), VectorReader(vectorFactory), SetReader(setFactory) reader-macro implementation.. With the factory argument, makes it easy to use any map implementation.
    • repl package remains the same.
    • core package is in the works and will contain the function value, macro system, special form type and built-in special forms.

    Opening this incomplete PR to get feedbacks early on. (Expect lot of random commits here. I will be rewriting commit history to make sense before merge)

    opened by spy16 8
  • golang package and test best practice

    golang package and test best practice

    I have been working on an implementation of the 'case' construct and the package layout including how tests are maintained is causing a bit of pain. I suggest moving the project to a standard golang layout (placing packages in 'pkg') with tests in the same package as the code. As it is now I have to modify every file to create a PR that adds a single function.

    opened by leifj 8
  • FYI:  Clojure implemented in Go

    FYI: Clojure implemented in Go

    I've just stumbled upon the Joker language, which is an implementation of Clojure in Go. I figured it might be of interest as inspiration for design.

    opened by lthibault 0
Releases(v0.3.0)
Owner
Shivaprasad Bhat
Neuroscience Enthusiast. Polyglot Programmer. Gopher.
Shivaprasad Bhat
A customisable virtual machine written in Go

== About GoLightly == GoLightly is a lightweight virtual machine library implemented in Go, designed for flexibility and reuse. Traditionally popular

Eleanor McHugh 217 Nov 16, 2022
A dialect of Lisp extended to support concurrent programming, written in Go.

LispEx A dialect of Lisp extended to support concurrent programming. Overview LispEx is another Lisp Interpreter implemented with Go. The syntax, sema

null 181 Nov 22, 2022
Lisp Interpreter

golisp Lisp Interpreter Usage $ golisp < foo.lisp Installation $ go get github.com/mattn/golisp/cmd/golisp Features Call Go functions. Print random in

mattn 123 Dec 15, 2022
Mini lisp interpreter written in Go.

Mini Go Lisp Mini lisp interpreter written in Go. It is implemented with reference to the d-tsuji/SDLisp repository written in Java. Support System Fu

Tsuji Daishiro 17 Nov 25, 2022
Toy Lisp 1.5 interpreter

Lisp 1.5 To install: go get robpike.io/lisp. This is an implementation of the language defined, with sublime concision, in the first few pages of the

Rob Pike 895 Jan 1, 2023
Interactive Go interpreter and debugger with REPL, Eval, generics and Lisp-like macros

gomacro - interactive Go interpreter and debugger with generics and macros gomacro is an almost complete Go interpreter, implemented in pure Go. It of

Massimiliano Ghilardi 2k Dec 30, 2022
A Lisp-dialect written in Go

Lispy ✏️ Intro Lispy is a programming language that is inspired by Scheme and Clojure. It's a simple Lisp-dialect I built to better understand Lisp an

Amir Bolous 22 Dec 8, 2022
a dynamically typed, garbage collected, embeddable programming language built with Go

The agora programming language Agora is a dynamically typed, garbage collected, embeddable programming language. It is built with the Go programming l

Martin Angers 324 Dec 30, 2022
Go bindings to QuickJS: a fast, small, and embeddable ES2020 JavaScript interpreter.

quickjs Go bindings to QuickJS: a fast, small, and embeddable ES2020 JavaScript interpreter. These bindings are a WIP and do not match full parity wit

Kenta Iwasaki 140 Dec 28, 2022
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing

Expr Expr package provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not lim

Anton Medvedev 3.3k Dec 30, 2022
ECMAScript/JavaScript engine in pure Go

goja ECMAScript 5.1(+) implementation in Go. Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and performan

Dmitry Panov 3.5k Dec 29, 2022
v8 javascript engine binding for golang

Go-V8 V8 JavaScript engine bindings for Go. Features Thread safe Thorough and careful testing Boolean, Number, String, Object, Array, Regexp, Function

Hoping White 208 Nov 21, 2022
A Go API for the V8 javascript engine.

V8 Bindings for Go The v8 bindings allow a user to execute javascript from within a go executable. The bindings are tested to work with several recent

Augusto Roman 383 Dec 15, 2022
A languge based on LISP syntax, NOT strictly lisp

Lisp-Interpreter A languge based on LISP syntax, NOT strictly lisp, I changed some things that in my opinion make the language more useable, this was

Leo De Silva 1 Jan 2, 2023
Simple-lisp - A Simple Lisp Interpreter written in Go

Simple Lisp A simple Lisp interpreter written in Go. The fixed-precision-numbers

Oxygen 5 Jun 21, 2022
A customisable virtual machine written in Go

== About GoLightly == GoLightly is a lightweight virtual machine library implemented in Go, designed for flexibility and reuse. Traditionally popular

Eleanor McHugh 217 Nov 16, 2022
Customisable and automated HTTP header injection

headi Customisable and automated HTTP header injection. Example run from the HTB machine Control: InsecureSkipVerify is not currently configured, if y

mlcsec 153 Dec 31, 2022
🍫 A customisable, universally compatible terminal status bar

Shox: Terminal Status Bar A customisable terminal status bar with universal shell/terminal compatibility. Currently works on Mac/Linux. Installation N

Liam Galvin 679 Dec 27, 2022
A simple yet customisable program written in go to make hackerman-like terminal effects.

stuntman a simple program written in go to make you look like a hackerman Demo stuntman -binar -width 90 -color cyan stuntman -text -width 90 -vertgap

Solaris 10 Aug 4, 2022
A dead simple, highly performant, highly customizable sessions middleware for go http servers.

If you're interested in jwt's, see my jwt library! Sessions A dead simple, highly performant, highly customizable sessions service for go http servers

Adam Hanna 70 Dec 19, 2022