Clojure(ish) interpreter in Swift

Overview

Cormorant

Build Status

(CI is probably broken because Xcode 8 is not working properly on Travis. Sorry.)

An interpreter for a dialect of Clojure, implemented in Swift (and formerly known as Lambdatron). The interpreter endeavors to match Clojure's behavior as closely as possible. The eventual goal is a library that can be used independently of the REPL front-end.

Bug reports, feature requests, PRs, and other contributions are welcome!

Web site: http://cormorant-lang.org (coming soon); Twitter: @cormorantlang.

Software

Cormorant is a framework written in Swift 3. You will need Xcode 8 beta 1 or later to build.

Cormorant comes with a REPL demonstrating its capabilities. Due to current limitations with Swift, the REPL is a Cocoa OS X application. It can either be run directly (in GUI mode), or the underlying executable can be invoked from the command line with the -c flag to run the interactive REPL as a command line application. When in the REPL, type expressions at the command prompt and press 'Enter'.

Getting started: Clone the repository. Open up the Cormorant project in Xcode. Select the CormorantREPLRunner target and press the Run button to build and run the REPL. Alternately, select CormorantTests and run that to run the test suite. See the Development section below for more details on the development environment and configuration.

Running as command line application: Use the terminal command ./path/to/CorormantREPLRunner.app/Contents/MacOS/CorormantREPLRunner -c, and replace /path/to with whatever the absolute or relative path is to the Cocoa bundle.

Grimoire is a high-quality Clojure API reference, and can be used to reference the intended behavior of all included functions and special forms.

Need ideas? Try:

Basic arithmetic

  • (+ (* 2 4) (- 8 6) (+ (+ 1 3) 4))

Working with collections

  • (cons 1 '(2 3 4))
  • (rest '(1 2 3 4 5))
  • (concat '(1 2 3) [4 5] {"six" 6 "seven" 7})
  • (seq {1 "one" 2 true "three" [nil]})

Defining and calling a function

  • (defn myfunc [a b] (+ a b 1)), then (myfunc 10 20)
  • Functions returning functions: (defn f1 [arg1] (fn [arg2] (+ arg1 arg2))), then (let [plusone (f1 1)] (plusone 3))

Recursion and iteration

  • Basic recursion: (defn r [a] (print a " ") (if (> a 0) (r (- a 1)))), then (r 10)
  • Tail-call recursion using recur: (defn recadd [mylist sofar] (if (= (first mylist) nil) sofar (recur (rest mylist) (+ (first mylist) sofar)))), then (recadd '(1 2 3 4 5) 0)
  • Iteration using loops: (loop [a 10 b 0] (if (= a 0) b (recur (- a 1) (+ b a))))

Creating and using a macro

  • (defmacro my-when [predicate then-do] `(if ~predicate ~then-do nil)), then (my-when (= 1 1) "good") or (my-when (= 1 2) (do (print "this shouldn't show up") "bad"))

Current Limitations

Cormorant has a couple of limitations, due mostly to its work-in-progress status:

  • The REPL can only take one form at a time.

These will disappear as the feature set is filled out.

Features

Cormorant has a number of possibly useful features. Cormorant's data structures should share identical semantics with Clojure's data structures (in that they are immutable and persistent), although some of them are implemented using Swift's native copy-on-write features rather than as persistent data structures.

Sequences, like in Clojure. Sequences are immutable, and come in several flavors. You can prepend items using cons, extract the first element using first, or get the sequence consisting of all but the first element using rest. Create a sequence or get a sequence view using seq.

  • Cons lists, the bread and butter of Lisp. Create the empty list using (). Or use the list function to create a list from zero or more arguments.
  • Lazy sequences, which store a thunk whose evaluation is deferred until absolutely necessary, after which they cache their value. Create a lazy sequence using the stdlib lazy-seq macro. Some of the stdlib and built-in functions may also return lazy sequences.
  • Lightweight views into strings, vectors, and hashmaps, which allow treating those collections like sequences without requiring expensive copying operations. These are transparent to the end user.
  • Contiguous lists backed by an Array. These are transparent to the end user.

Vectors, declared using square brackets: [1 2 true "Lisp"], or the vector function. Unlike lists, vectors can't be used to invoke functions. Vectors can be used in function position with an integer index argument.

Maps, declared using braces: {"one" 1 "two" nil "three" [1 2 3]}, or the hash-map function. Maps can be used in function position in order to get a value for a key.

Strings, declared using double quotes: "this is a string literal". Strings can be manipulated using first, rest, etc. Cormorant also supports a mutating string builder which can be used to efficiently construct strings through concatenation. Create one using sb, work with the buffer using sb-append and sb-reverse, and turn it back into a string using str.

Regular expressions, declared as such: #"[0-9]+", and backed by NSRegularExpression. Use re-pattern to create a regex at runtime. Use re-first to get the first match of a pattern in a string, and re-seq to get a list of all matches of a pattern in a string. Call re-iterate with a pattern, a string, and a function that takes two arguments; the function will be called once for each match with a vector of match tokens and a vector of ranges. The function can return true to end iteration, or any other value to allow iteration to continue.

Functions are first-class citizens which capture their environment (except for values defined using def). Create them using fn, followed by an optional name, a vector containing parameter bindings, and one or more forms comprising the function body. Or create a function bound to a global name using defn. Multiple arities can be defined by passing in one or more lists, each of which starts with a vector containing parameter bindings followed by the function body. Define varargs by passing in a parameter binding vector ending with & and the name of a vector to place the rest of the arguments (e.g. [a b & others]).

Macros are like functions, except that their arguments aren't evaluated before being passed in and the output is intended to be a form which can be further evaluated at runtime. Like functions, macros capture their (non parameter binding) context. Create them using defmacro. Macros can be defined with multiple arities and/or varargs.

Let-binding, using let, allows you to create a lexical context with new bindings available only within the scope of that context.

Vars are mutable memory cells that are interned within a namespace, and can be referred to by a qualified or unqualified symbol. Create them using def (e.g. def myVar 100). Vars are reified. Use the var special form to get the Var that a symbol corresponds to, and deref to extract the value contained within a Var.

Basic types include:

  • Booleans (true and false)
  • nil
  • integers
  • floating-point numbers (e.g. 1.234)
  • character literals (\a, \tab, \space, \newline, \return), which can be used in function position. Character literals can also be specified using the hexadecimal \uNNNN or octal \oNNN forms.
  • keywords (:else), which can be used in function position

Namespaces make organizing your code easier. Currently, all stdlib code is contained within the core namespace, and the user begins in the user namespace. Namespaces are reified and can be manipulated as normal objects using some of the built-in functions.

Syntax-quote makes defining macros slightly less tedious. Use ' to denote a normal quoted form. Use ` to denote a quote that should be syntax-quoted; within such a form ~ (unquote) can be used to force evaluation of the unquote form, while ~@ (unquote-splice) can be used to force evaluation of a form to a collection whose elements are then spliced in. Within a syntax-quoted expression, unqualified symbols are qualified to the current namespace (e.g. a might become user/a), while unqualified symbols suffixed by a # are converted into gensym'ed symbols.

Comments start with a semicolon and continue until the end of the current line: ; this is a comment

The following special forms, reader macros, and functions are built into the interpreter:

  • Special forms: quote, if, do, def, let, var, fn, defmacro, loop, recur, apply, attempt
  • Reader macros: ', `, ~, ~@, #', @
  • Namespace manipulation: ns-create, ns-set, ns-get, ns-name, ns-all, ns-find, ns-unmap, ns-alias, ns-aliases, ns-unalias, ns-refer, ns-map, ns-interns, ns-refers, ns-resolve, ns-remove
  • Collection manipulation: list, vector, hash-map, cons, first, rest, next, conj, concat, nth, seq, lazy-seq, get, assoc, dissoc, count, reduce
  • Primitive manipulation: symbol, keyword, int, double
  • String manipulation: str, subs, lower-case, upper-case, replace, replace-first
  • String building: sb, sb-append, sb-reverse
  • Regular expressions: re-pattern, re-first, re-seq, re-iterate, re-quote-replacement
  • I/O: read, print, println
  • Testing: nil?, number?, int?, float?, string?, char?, symbol?, keyword?, fn?, eval?, true?, false?, var?, seq?, vector?, map?, pos?, neg?, zero?, subnormal?, infinite?, nan?
  • Arithmetic: +, -, *, /, rem, quot
  • Comparison: =, ==, <, <= >, >=
  • Miscellaneous: deref, gensym, read-string, rand, eval, fail

Additional functions and macros are available as part of the standard library.

Development

Some notes on Cormorant development tools follow.

Development and OS X's SIP

When debugging Cormorant, you have at least three options:

  • Run Cormorant's REPL as a Cocoa app, and debug as usual.
  • Run Cormorant's REPL in the built-in Xcode console by setting the -c command-line argument in the scheme. Unfortunately, anything you type will be echoed twice due to an ancient and still-unfixed bug.
  • Add Cormorant as a library to your own Cocoa or command-line app, and debug from there.

If, in the future, it becomes possible to properly build command-line applications that depend upon Swift frameworks, you might be able to debug using Xcode by running the REPL in Terminal.app, but you will have to disable SIP to do so.

Code Organization

Cormorant is divided into three components:

  • The core framework (Cormorant)
  • The REPL framework (CormorantREPL)
  • A Cocoa/command-line app and associated build settings for running the REPL (CormorantREPLRunner)

Debugging

You can directly debug the Cormorant framework through the Cocoa REPL app.

If you want to profile using the command-line REPL, follow these instructions (you will need to disable SIP):

  1. Copy the app to a convenient location.
  2. Invoke the underlying binary from the command line using the -c flag to start the command-line REPL.
  3. In Xcode, go to "Debug" --> "Attach to Process" and choose the "CormorantREPLRunner" target. If you are fortunate it'll be at the top under "Likely Targets".
  4. Debug as usual. Note that modifying a breakpoint might cause libedit to redraw the prompt in Terminal. This is annoying, but merely cosmetic.

Profiling

You can directly profile the Cormorant framework through the Cocoa REPL app.

If you want to profile using the command-line REPL, follow these instructions (you will need to disable SIP):

  1. Copy the app to a convenient location.
  2. Invoke the underlying binary from the command line using the -c flag to start the command-line REPL.
  3. Open Instruments and choose the Instrument you want to profile with.
  4. Choose the target manually: it will be named "CormorantREPLRunner", under "System Processes". (Use Activity Monitor to get the PID if you can't find it.)
  5. Profile as usual.

Logging

Logging is in an embryonic state. In the command-line version of the REPL, type ?logging <DOMAIN> on or ?logging <DOMAIN> off to turn logging on or off for a given domain, or omit the <DOMAIN> argument to turn logging on or off globally.

The only currently supported domain is eval. This logging domain prints out messages detailing how macros, functions, etc are evaluated, and can be useful to see exactly what the interpreter is doing when it evaluates a form.

Benchmarking

The command-line version of the REPL includes a basic benchmarking tool. Invoke it using ?benchmark <SOME_FORM> <ITERATIONS>, where <SOME_FORM> is a valid code snippet and <ITERATIONS> is the number of times to repeat the execution. The benchmark tool will print the average, minimum, and maximum run times (in milliseconds).

The code snippet is lexed, parsed, and expanded before the benchmark, and the expanded data structure is cached between benchmark iterations, so the benchmarking tool only measures evaluation time. As well, the context is not cleared between executions, so side effects caused by one iteration are visible to all subsequent iterations.

Unit Tests

Cormorant has a comprehensive unit test suite that exercises the interpreter (not the standard library). Run the unit tests from within Xcode.

Cormorant relies on Travis CI for continuous integration. Click on the badge at the top of the README to see more information about CI testing.

Development Objectives

Development objectives can be divided into two categories.

Working On

These are objectives I am working on right now, or plan on doing in the near future.

  • Optimizing and refactoring code to take advantage of Swift 3's new features
  • Interpreter (translate source to bytecode and then execute)
  • Expanding standard library
  • Support for sets
  • Ability to type in multiple forms at the top level
  • Metacontext - allow consumer to define custom functions visible to the user
  • Performance optimization (once development stabilizes)
  • Full unit test suite (once development stabilizes)
  • Metadata

(Very) Long Term Goals

These are objectives that are either too big in scope to schedule, too technically challenging at this time, or of uncertain utility.

  • Persistent data structures
  • STM and support for multithreading
  • Destructuring via pattern matching
  • Custom types (e.g. deftype) and multimethods (may not be possible at the moment)
  • Rationals and bignums (may need to wait for a suitable Swift library handling these first)
  • Full Foundation/Cocoa bindings
  • Better Swift runtime interop (if proper reflection support ever comes to Swift)

Differences From Clojure

Aside from the (long) list of features not yet implemented (see the Working On and (Very) Long Term Goals sections above), there are a couple of intentional deviations from Clojure's API or conventions:

  • Hashmap iteration is not guaranteed to traverse the elements in the same order as in Clojure. No guarantees are made on hashmap iteration except that each key-value pair is visited exactly once. This has implications for any function that converts a map into an ordered sequence.
  • ifn? doesn't exist; use eval? instead. This is because Cormorant does not use protocols (i.e. interfaces) to define constructs that can be used in function position.
  • try doesn't exist. attempt is a (very basic) error handling facility. It takes one or more forms, executing each sequentially, and returns the first successful value (or the error from executing the final form).
  • The byte, short, long, and float functions are not implemented, as Cormorant only has an integer and a double-precision floating point numerical data type.
  • The subnormal?, infinite?, and nan? functions return false for integer arguments, and can be used to test whether floating point numbers are subnormal, infinite, or NaN (respectively).
  • keyword returns nil if given an empty string as an argument, not an invalid empty symbol.
  • read does not take an optional argument representing a reader object.
  • char-escape-string returns nil for the \formfeed and \backspace arguments, since Swift does not recognize the \f and \b escape sequences.
  • Regex support follows Cocoa conventions, since NSRegularExpression is very different from java.util.pattern.Regex and java.util.pattern.Match. re-iterate provides an idiomatic wrapper for enumerateMatchesInString:options:range:usingBlock:.
  • Once a namespace has been marked for deletion using ns-remove, all its aliases are automatically unregistered, and new aliases or refers can no longer be set for it.

License

Cormorant Β© 2015-2016 Austin Zheng, released as open-source software subject to the following terms.

The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in the file epl-v10.html at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.

Comments
  • Unit tests fail with dyld`dyld_fatal_error

    Unit tests fail with dyld`dyld_fatal_error

    Seems that no matter what test I try and run I always end up with this:

    dyld`dyld_fatal_error:
    0x7fff5fc0109c:  int3   
    0x7fff5fc0109d:  nop    
    

    running XCode 6.1.1 under OS X 10.9.5

    last commit is

    commit cb244f57f02db6ad19fc61d35dad5b0ae3779cba
    Author: Austin Zheng <[email protected]>
    Date:   Sun Mar 8 15:57:07 2015 -0700
    
        Adding unit tests
    
        Changes:
        - Added unit tests for the '.dissoc' function
        - Fixed .dissoc's arity error message
    
    bug testing 
    opened by masukomi 9
  • Support lazy sequences

    Support lazy sequences

    Lazy sequences are one of Clojure's most powerful features. Unlike (e.g.) STM, a decent and useful implementation of lazy sequences should be feasible for Lambdatron.

    new feature infra 
    opened by austinzheng 3
  • Fix param handling for functions vs macros

    Fix param handling for functions vs macros

    The function param system needs extensive work and correctness verification. Here are some things that should happen.

    For a fn, a param is resolved to its base value even before the function takes control. So arg1 --> 10.

    > (def a 10)
    > (def b a)
    > (defn foo [arg1] arg1)
    > (foo b)
    ; foo sees 10
    10
    

    For a macro, a param maps directly to the value that was passed in. So arg1 --> b.

    > (defmacro bar [arg1] arg1)
    > (macroexpand '(bar b))
    ; bar sees 'b'
    b
    

    Once inside the fn, params resolved to symbols are never further resolved.

    > (foo 'b)
    b
    > (macroexpand '(bar b))
    b
    

    If a function is passed a list, that list is eval'ed when the param values are being calculated:

    > (foo (+ 1 2))
    ; foo sees 3
    3
    

    If a macro is passed a list, that list is treated verbatim: arg1 -> (+ 1 2)

    > (macroexpand '(bar (+ 1 2)))
    ; bar sees (+ 1 2)
    (+ 1 2)
    

    Example A:

    > (defn ftest [a b c] (a b c))
    > (ftest + 1 2)
    3
    ;; This one is weird. Not sure what it's doing. It always returns the last element.
    ;; I would expect it to execute (+ 1 2) and return 3 as the macroexpansion.
    > (defmacro mtest [a b c] (a b c))
    > (mtest + 1 2)
    2
    > (defmacro mtest2 [a b] (+ a b))
    > (mtest2 1 2)
    3
    

    It looks like, for functions, a list (arg1 ...) [where arg1 is a function passed in as a param] is treated as a function call properly. But for macros, a list (arg1 ... last_item) just returns last_item. (This doesn't have anything to do with the code the macro is emitting. It's about how the macro runs computations internally.)

    However, normal function definitions (where the item in function position isn't a param) work fine in a macro, as seen in mtest2.

    Example B:

    > (defmacro mtest [a b] (list [(+ a 1) (+ b 2)] [a b]))
    > (macroexpand-1 '(mtest 10 20))
    ([11 22] [10 20])
    

    It's clear that the code inside the macro is resolving a = 10 and b = 20 and then performing arithmetic on those values to generate the output.

    However, if we try:

    > (macroexpand-1 '(mtest (+ 5 5) 20))
    

    we get an exception that says that a PersistentList can't be cast to a Number. Basically the macro body is attempting to eval (+ (+ 5 5) 1), but the (+ 5 5) cannot be further eval'ed.

    But...

    > (defmacro mtest [a b c] [(+ a (+ b c))])
    > (mtest 10 1 2)
    [13]
    

    This is fine, since it's completely evaluating (+ 10 (+ 1 2)).

    However,

    > (mtest (+ 5 5) 1 2)
    

    gives the same error. The body is (+ (+ 5 5) (+ 1 2)), but the (+ 5 5) cannot be evaluated (since it's an argument, an unevaluable list), while the (+ 1 2) can be since it's part of the macro code.

    A final example, one with vectors:

    > (defmacro mtest2 [a b c] (list a [(+ b 1) (+ c 1)]))
    > (macroexpand-1 '(mtest2 [(+ 12 1) (+ 13 1)] 12 13))
    ([(+ 12 1) (+ 13 1)] [13 14])
    

    Again we note that the vector argument a is treated as atomic; its constituents are not evaluated themselves.

    Therefore, a few things need to happen:

    • In macros, function calls with an arg at the beginning need to return instead the last element in the list (???).
    • When a macro is being evaluated, a distinction must be made between atomic items referred to by macro params, and 'normal' lists, vectors, etc defined as part of the macro body that should be evaluated.
    bug 
    opened by austinzheng 3
  • rough EditLine implementation for REPL

    rough EditLine implementation for REPL

    Currently echo double of each char typed, but otherwise works the same. I'd be grateful for any comments for improvement. If you know what's happening or have pointers for tracking it down let me know.

    opened by sventech 3
  • Unquote and unquote-splice don't properly handle complex internals

    Unquote and unquote-splice don't properly handle complex internals

    If unquote or unquote-splice is applied on a composite form (e.g. ~(a b) or ~@(a b)), the complex form isn't properly handled if it in turn has elements that are quoted or syntax-quoted. The unit tests I just pushed should highlight this deficiency.

    bug reader 
    opened by austinzheng 2
  • Rearchitect relationship between ConsValue and reader macros

    Rearchitect relationship between ConsValue and reader macros

    Right now, reader macros (currently quote, syntax-quote, unquote, and unquote-splice) are implemented as a certain case of ConsValue. The lexer and parser produce a ConsValue containing unexpanded reader macros, which is then fed into an expansion function.

    This has a couple of advantages. Since reader macros are treated as ordinary macros or functions in structure, the expander can take advantage of the tree structure of the ConsValue. For example, ``(a b)becomes(sq* (a b))`, which allows it to be processed as a normal list.

    However, it also has some glaring disadvantages. The first is that reader macro objects are exposed to the public through ConsValue.ReaderMacro. This exposes an implementation detail, since end users are never expected to be allowed to build code with the function-position forms of the reader macros.

    The second is that reader macros should not all be expanded at the same time. Instead, there should be different types of reader macros, and different functions which expand different macros. However, implementing this would require even more cases on ConsValue that should never have been exposed to the user in the first place.

    A complication is that Cons expects to hold a ConsValue. This means it's difficult to have the reader output (e.g.) an UnexpandedConsValue including list items. Cons could be subclassed, but that would mean unsealing the class (which I'd rather not do).

    enhancement infra 
    opened by austinzheng 2
  • Support namespaces

    Support namespaces

    Implement namespaces in Lambdatron per Clojure's pattern. Following is a rough sketch of work required to do so.

    • Implement pre-registration of symbols and keywords in Context. This allows the interpreter to load certain predefined symbols and keywords into the interning system at initialization, allowing built-in functions to work directly with predefined interned symbols. This isn't strictly necessary but will keep the design clean.
    • Reify namespaces. Namespaces should be an object that can be returned by things like all-ns and passed as arguments to other things. Note that Clojure doesn't seem to expose a way to refer literally to a namespace, and namespaces seem to only make sense in the global context.
    • The interpreter or base context should track the current namespace. One namespace can be active at a time. This is the namespace that unqualified symbols implicitly resolve to.
    • There are also base namespaces (e.g. clojure.core) that are checked if symbols don't resolve to the current user namespace.
    • Symbols and keywords need an additional piece of data in their data structure, a symbol ID that indicates which namespace they belong to.
    • Symbol and keyword resolution must take into account the namespace of the item and/or the current namespace (for simply qualified symbols or keywords).
    • Implement APIs: see here: http://clojure.org/namespaces
    • The user should not be able to switch to the base namespace(s). Doing so in the Clojure REPL allows you to do strange things.
    • Support needs to be added to define fully-qualified symbols (e.g. user/a) and keywords (::a).
    • Syntax-quote needs to expand symbols (and maybe keywords?) to their fully-qualified names. For example, ``(a)expands touser/ain the user namespace, butexample/a` in the example namespace.
    new feature infra 
    opened by austinzheng 2
  • Implement 'map'

    Implement 'map'

    Implement 'map' using the stdlib and/or as a built-in fn. (Lazy support not necessary for now.)

    http://conj.io/store/org.clojure/clojure/1.7.0-alpha4/clojure.core/map/

    new feature stdlib 
    opened by austinzheng 2
  • Change how slots in Context are allocated

    Change how slots in Context are allocated

    Right now slots in a Context are simulated as a dictionary. This is wasteful, especially since:

    • In most use cases the number of items is fixed
    • In most use cases the number of items is small

    Need to determine a better way to store bindings. Unfortunately Swift doesn't yet support fixed-length arrays.

    performance 
    opened by austinzheng 2
  • Fix behavior of 'recur' with varargs

    Fix behavior of 'recur' with varargs

    An interesting phenomenon:

    Say that a function takes args [x y & more]. Later on there is a recur statement of the form (recur y (first more) (rest more)).

    Clojure binds the sequence formed by (rest more) directly to the vararg 'more', rather than making a list out of it (as might be expected).

    This came up when trying to implement == as a stdlib function: the current 'recur' doesn't rebind directly, causing the list to become more and more nested. This should be fixed.

    bug 
    opened by austinzheng 2
  • Support regexes

    Support regexes

    Support regexes (probably using NSRegularExpression).

    • #"regex" should be parsed as a regex
    • Implement the library functions: re-find, re-seq, re-matches, re-pattern, re-matcher, re-groups, replace, replace-first, re-quote-replacement
    new feature stdlib reader builtin 
    opened by austinzheng 1
  • Basic support for `ref`

    Basic support for `ref`

    I don't think I can implement a proper STM system. However, a decent approximation that might serve for low-traffic purposes follows:

    A ref is represented by a reference type (type with identity) encapsulating:

    • A mutable value type cell
    • A GCD queue (I think refs can share queues, not sure about this though)
    • A counter

    A read-only transaction is simple: the value type is simply copied and returned to the owner. Since value types in Swift are COW, mutations to the value type shouldn't touch the mutable cell. I believe that COW is thread-safe (as opposed to, e.g. mutating a var containing a collection across multiple threads).

    A dosync transaction is modeled roughly in the following way: a transaction closure is run on the local queue, which takes in the counter and a copy of the cell value. When finished, a nested closure is dispatched to the ref's queue with the new value and the counter.

    If the counter has not changed, the ref increments the counter and updates the value, and dispatches a completion closure to the local queue.

    If the counter has changed, the ref dispatches the transaction closure to the local thread again, but with an updated copy of the cell value and an updated counter value. The transaction is then re-run, as described previously.

    This basically corresponds to a lock to start the transaction and a lock to end it (ensuring that transactions are processed in a well-defined order). However, the concurrency mechanism is work queues, not locks or mutexes.

    The main technical challenge is naming the queues to dispatch between. It's possible that a namespace (or whatever mechanism is used to model a thread of execution) can hold on to the name of its queue, allowing it to be retrieved from GCD by the ref data structure.

    new feature exploratory 
    opened by austinzheng 0
  • Rework Xcode dependencies

    Rework Xcode dependencies

    The way Xcode handles dependencies between projects is a shambling dumpster fire. Take some time to figure out exactly what needs to be done so people who want to build the project actually can do so without having to fix anything.

    infra 
    opened by austinzheng 0
  • SwiftPM compatibility

    SwiftPM compatibility

    Compatibility with SwiftPM should be explored in tandem with efforts to make the codebase portable. It's not really clear right now what Swift 2.2's story for dylibs in conjunction with command line applications is, so this task may or may not be tractable.

    new feature 
    opened by austinzheng 0
  • Cross-platform compatibility

    Cross-platform compatibility

    Project's not dead; I've just been dealing with many personal things.

    Before any serious rearchitecting can be done, the project needs to refactored so that it:

    • Can be built on non-OS X platforms without Xcode
    • Dependency on Foundation can be swapped out
    enhancement 
    opened by austinzheng 0
  • Support try-catch-finally

    Support try-catch-finally

    The attempt special form should be replaced by a proper try-catch-finally, with reified exceptions holding EvalErrors and the ability to catch specific error types. This is necessary for building binding or any of the concurrent var-related features.

    new feature infra 
    opened by austinzheng 0
  • Improve error system

    Improve error system

    The error system is tolerable right now, but could be significantly better. In particular, decent stacktraces should be possible via the interpreter's Context stack system. Not sure if line info metadata can be added, but that would be nice as well.

    enhancement builtin exploratory 
    opened by austinzheng 0
Owner
Austin Zheng
I do things with computers.
Austin Zheng
A Swift package for encoding and decoding Swift Symbol Graph files.

SymbolKit The specification and reference model for the Symbol Graph File Format. A Symbol Graph models a module, also known in various programming la

Apple 141 Dec 9, 2022
Delightful code generation for OpenAPI specs for Swift written in Swift

Create API Delightful code generation for OpenAPI specs for Swift written in Swi

Alexander Grebenyuk 286 Dec 23, 2022
WebDomHandling - A Swift Package for handling JavaScript code between WebKit and Swift implemented by WebKit

WebDomHandling A Swift Package for handling JavaScript code between WebKit and S

null 0 Jan 23, 2022
A very simplistic state machine system for Swift while mainly used with Raylib on Swift

A very simplistic state machine system for Swift while mainly used with Raylib on Swift

Conifer Coniferoslav 2 Dec 12, 2022
A Swift SPM framework for running and managing Lua code from Swift

LuaKit A Swift Package for running and managing Lua code from Swift. Documentation For documentation, add this package as Swift Package Dependency, an

GGorAA 5 Nov 24, 2022
This is a Swift Package bundling different Train APIs into one simple Swift interface.

This is a Swift Package bundling different Train APIs into one simple Swift interface.

ICE Buddy 8 Jul 5, 2022
Beak 🐦 Peck into your Swift files from the command line

Beak ?? Peck into your Swift files from the command line Beak can take a standard Swift file and then list and run any public global functions in it v

Yonas Kolb 566 Dec 6, 2022
A Collection of PropertyWrappers to make custom Serialization of Swift Codable Types easy

CodableWrappers Simplified Serialization with Property Wrappers Move your Codable and (En/De)coder customization to annotations! struct YourType: Coda

null 393 Jan 5, 2023
Turn your Swift data model into a working CRUD app.

Model2App is a simple library that lets you quickly generate a CRUD iOS app based on just a data model defined in Swift. (CRUD - Create Read Update De

Q Mobile 132 Dec 22, 2022
OpenAPI/Swagger 3.0 Parser and Swift code generator

SwagGen SwagGen is a library and command line tool for parsing and generating code for OpenAPI/Swagger 3.0 specs, completely written in Swift. Swagger

Yonas Kolb 568 Jan 5, 2023
Homebrew for Swift packages

Swiftbrew A package manager that installs prebuilt Swift command line tool packages, or Homebrew for Swift packages. Installation Homebrew brew instal

Swiftbrew 142 Nov 8, 2022
The Swift code generator for your assets, storyboards, Localizable.strings, … β€” Get rid of all String-based APIs!

SwiftGen SwiftGen is a tool to automatically generate Swift code for resources of your projects (like images, localised strings, etc), to make them ty

null 8.3k Dec 31, 2022
Start your next Open-Source Swift Framework πŸ“¦

SwiftKit enables you to easily generate a cross platform Swift Framework from your command line. It is the best way to start your next Open-Source Swi

Sven Tiigi 821 Dec 28, 2022
Easily generate cross platform Swift framework projects from the command line

SwiftPlate Easily generate cross platform Swift framework projects from the command line. SwiftPlate will generate Xcode projects for you in seconds,

John Sundell 1.8k Dec 27, 2022
A Swift command line tool for generating your Xcode project

XcodeGen XcodeGen is a command line tool written in Swift that generates your Xcode project using your folder structure and a project spec. The projec

Yonas Kolb 5.9k Jan 9, 2023
A Swift playground that comes pre-loaded with Plot, that can be used to explore the new component API.

PlotPlayground A Swift playground that comes pre-loaded with Plot, so that you can quickly try out the library and its new, SwiftUI-like API for build

John Sundell 48 Jan 5, 2023
[Accepted] WWDC21 Swift Student Challenge Submission

Hear See Tell us about the features and technologies you used in your Swift playground. Swift Playground Author Template: providing the general struct

Zhiyu Zhu/ζœ±ζ™Ίθ―­ 12 Apr 7, 2022
🌳 Environment – a nicer, type-safe way of working with environment variables in Swift.

?? Environment Welcome to Environment – a nicer, type-safe way of working with environment variables in Swift. Usage Access Environment Variables The

Will Lisac 31 Dec 17, 2022
A Swift wrapper around the CoreSymbolication private framework on macOS.

CoreSymbolication provides a very powerful system for looking up and extracting symbolic information from mach-o executables, dyld shared caches, and dSYMs.

Stacksift 7 Nov 21, 2022