From 9ee0523cadbc384b49ae500841c0899101bf280c Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Tue, 21 May 2013 14:03:16 -0700 Subject: [PATCH] Add wisp language support. --- lib/linguist/languages.yml | 7 + samples/wisp/intro.wisp | 367 +++++++++++++++++++++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 samples/wisp/intro.wisp diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 6b52b301..ba335939 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -1466,3 +1466,10 @@ reStructuredText: primary_extension: .rst extensions: - .rest + +wisp: + type: programming + lexer: Clojure + ace_mode: clojure + color: "#7582D1" + primary_extension: .wisp diff --git a/samples/wisp/intro.wisp b/samples/wisp/intro.wisp new file mode 100644 index 00000000..6b817dac --- /dev/null +++ b/samples/wisp/intro.wisp @@ -0,0 +1,367 @@ +;; # wisp + +; Wisp is homoiconic JS dialect with a clojure syntax, s-expressions and +; macros. Wisp code compiles to a human readable javascript, which is one +; of they key differences from clojurescript. + +;; ## wisp data structures + +;; 1. nil - is just like js undefined with a differenc that it's +;; not something can be defined. In fact it's just a shortcut for +;; void(0) in JS. +nil ;; => void(0) + +;; 2. Booleans - Wisp booleans true / false are JS booleans + +true ;; => true + +;; 3. Numbers - Wisp numbers are JS numbers +1 ;; => 1 + +;; 4. Strings - Wisp strings are JS Strings +"Hello world" +;; Wisp strings can be multiline +"Hello, +My name is wisp!" + +;; 5. Characters - Characters are sugar for JS single char strings +\a ;; => "a" + +;; 6. Keywords - Keywords are symbolic identifiers that evaluate to +;; themselves. +:keyword ;; => "keyword" +;; Since in JS string constats fulfill this purpose of symbolic +;; identifiers, keywords compile to equivalent JS strings. +(window.addEventListener :load handler false) +;; Keywords can be invoked as functions, that desugars to plain +;; associated value access in JS +(:bar foo) ;; => foo["bar"] + + +;; 7. Vectors - Wisp vectors are JS arrays. +[ 1 2 3 4 ] +;; Note: Commas are white space & can be used if desired +[ 1, 2, 3, 4] + + +;; 8. Maps - Maps are hash maps, plain JS objects. Note that unlike +;; in clojure keys can not be of arbitary types. +{ "foo" bar :beep-bop "bop" 1 2 } +;; Commas are optional but can come handy for separating key value +;; pairs. +{ a 1, b 2 } +;; In a future JSONs syntax may be made compatible with map syntax. + + +;; 9. Lists - You can't have a lisp without lists! Wisp has lists too. +;; Wisp is homoiconic and it's code is made up of lists representing +;; expressions. The first item in the expression is a function, being +;; invoked with rest items as arguments. +(foo bar baz) ; => foo(bar, baz); + +;; # Conventions +;; Wisp puts a lot of effort in making naming conventions transparent, +;; by encouraning lisp conventions and then translating them to equivalent +;; JS conventions: + +(dash-delimited) ;; => dashDelimited +(predicate?) ;; => isPredicate +(**privates**) ;; => __privates__ +(list->vector) ;; => listToVector + +;; As a side effect some names can be expressed in a few ways, although +;; it's considered to be an advantage. + +(parse-int x) +(parseInt x) + +(array? x) +(isArray x) + + +;; # Special forms + +;; There are some functions in wisp that are special, in a sence that +;; they compile to JS expressions & can not be passed around as regular +;; functions. JS operators are represteted in wisp as special forms + +;; Arithmetic forms - Wisp comes with special form for arithmetic +;; operations. + +(+ a b) ; => a + b +(+ a b c) ; => a + b + c +(- a b) ; => a - b +(* a b c) ; => a * b * c +(/ a b) ; => a / b +(mod a b) ; => a % 2 + +;; Comparison forms - Wisp comes with special forms for comparisons + +(identical? a b) ;; => a === b +(identical? a b c) ;; => a === b && b === c +(= a b) ;; => a == b +(= a b c) ;; => a == b && b == c +(> a b) ;; => a > b +(>= a b) ;; => a >= b +(< a b c) ;; => a < b && b < c +(<= a b c) ;; => a <= b && b <= c + +;; Logical forms - Wisp comes with special forms for logical operations + +(and a b) ;; => a && b +(and a b c) ;; => a && b && c +(or a b) ;; => a || b +(and (or a b) + (and c d)) ;; (a || b) && (c && d) + + +;; Definitions - Variable definitions also happen through special forms. +(def a) ; => var a = void(0); +(def b 2) ; => var b = 2; + +;; Assignments - In wisp new values can be set to a variables via `set!` +;; special form. Note that in functional programing binding changes are +;; a bad practice, avoiding those would make your programs only better! +;; Stil if you need it you have it. +(set! a 1) + +;; Conditionals - Conditional code branching in wisp is expressed via +;; if special form. First expression following `if` is a condition, +;; if it evaluates to `true` result of the `if` expression is second +;; expression otherwise it's third expression. +(if (< number 10) + "Digit" + "Number") +;; Else expression is optional, if missing and conditional evaluates to +;; `true` result will be `nil`. +(if (monday? today) "How was your weekend") + + + +;; Compbining expressions - In wisp is everything is an expression, but +;; sometimes one might want to compbine multiple expressions into one, +;; usually for the purpose of evaluating expressions that have +;; side-effects +(do + (console.log "Computing sum of a & b") + (+ a b)) + +;; Also number of expressions is `do` special form 0 to many. If `0` +;; result of evaluation will be nil. +(do) + + +;; Bindings - Let special form evaluates containing expressions in a +;; lexical context of in which simbols in the bindings-forms (first item) +;; are bound to their respective expression results. + +(let [a 1 + b (+ a c)] + (+ a b)) + + +;; Functions - Wisp functions are JS functions +(fn [x] (+ x 1)) + +;; Wisp functions can be named similar to JS +(fn increment [x] (+ x 1)) + +;; Wisp functions can also contain documentation and some metadata. +;; Note: Docstring and metadata is not presented in compiled JS yet, +;; but in a future it will compile to comments associated with function. +(fn incerement + "Returns a number one greater than given." + {:added "1.0"} + [x] (+ x 1)) + +;; Wisp makes capturing of rest arguments a lot easier than JS. argument +;; that follows special `&` simbol will capture all the rest args in array. + +(fn [x & rest] + (rest.reduce (fn [sum x] (+ sum x)) x)) + +;; Overloads - In wisp functions can be overloaded depending on number +;; of arguments they take, without introspection of rest arguments. +(fn sum + "Return the sum of all arguments" + {:version "1.0"} + ([] 0) + ([x] x) + ([x y] (+ x y)) + ([x & more] (more.reduce (fn [x y] (+ x y)) x))) + +;; If function does not has variadic overload and more arguments is +;; passed to it, it throws exception. +(fn + ([x] x) + ([x y] (- x y))) + + + +;; ## Other Special Forms + +;; Instantiation - In wisp type instantiation has a consice form, type +;; function just needs to be suffixed with `.` character +(Type. options) + +;; More verbose but JS like form is also there +(new Class options) + + +;; Method calls - In wisp method calls are no different from function +;; calls, it's just method functions are perfixed with `.` character +(.log console "hello wisp") + +;; Also more JS like forms are supported too! +(window.addEventListener "load" handler false) + + +;; Attribute access - In wisp attribute access is also just like function +;; call. Attribute name just needs to be prefixed with `.-` +(.-location window) + +;; Compound properties can be access via `get` special form +(get templates (.-id element)) + +;; Catching exceptions - In wisp exceptions can be handled via `try` +;; special form. As everything else try form is also expression. It +;; results to nil if no handling takes place. +(try (raise exception)) + +;; Although catch form can be used to handle exceptions +(try + (raise exception) + (catch error (.log console error))) + +;; Also finally clase can be used when necessary +(try + (raise exception) + (catch error (recover error)) + (finally (.log console "That was a close one!"))) + + +;; Throwing exceptions - Throw special form allows throwing exceptions, +;; although doing that is not idiomatic. +(fn raise [message] (throw (Error. message))) + + +;; ## Macros +;; Wisp has a programmatic macro system which allows the compiler to +;; be extended by user code. Many core constructs of Wisp are in fact +;; normal macros. + +;; quote + +;; Before diving into macros too much, we need to learn about few more +;; things. In lisp any expression can be marked to prevent it from being +;; evaluated. For instance, if you enter the symbol `foo` you will be +;; evaluating the reference to the value of the corresponding variable. +foo + +;; If you wish to refer to the literal symbol, rather then reference you +;; could use +(quote foo) +;; or more usually +'foo + +;; Any expression can be quoted, to prevent it's evaluation. Although your +;; resulting programs should not have these forms compiled to JS. +'foo +':bar +'(a b) + +;; Wisp doesn’t have `unless` special form or a macro, but it's trivial +;; to implement it via macro. Although let's try implemting it as a +;; function to understand a use case for macro! + +;; We want to execute body unless condition is `true`. +(defn unless-fn [condition body] + (if condition nil body)) + +;; Although following code will log "should not print" anyway, since +;; function arguments are exectued before function is called. +(unless-fn true (console.log "should not print")) + +;; Macros solve this problem, because they do not evaluate their arguments +;; immediately. Instead, you get to choose when (and if!) the arguments +;; to a macro are evaluated. Macros take items of the expression as +;; arguments and return new form that is compiled instead. +(defmacro unless + [condition form] + (list 'if condition nil form)) + +;; The body of unless macro executes at macro expansion time, producing an +;; if form for compilation. Which later is compiled as usual. This way +;; compiled JS is a conditional instead of function call. +(unless true (console.log "should not print")) + +;; Simple macros like above could be written via templating, expressed +;; as syntax-quoted forms. + +;; `syntax-quote` is almost the same as the plain `quote`, but it allows +;; sub expressions to be unquoted so that form acts a template. Symbols +;; inside form are resolved to help prevent inadvertent symbol capture. +;; Which can be done via `unquote` and `unquote-splicing` forms. + +(syntax-quote (foo (unquote bar))) +(syntax-quote (foo (unquote bar) (unquote-splicing bazs))) + +;; Also there is a special syntax sugar for both unquoting operators: + +;; Syntax quote: Quote form, but allow internal unquoting so that form +;; acts as template. Symbols inside form are resolved to help prevent +;; inadvertent symbol capture. +`(foo bar) + +;; Unquote: Use inside a syntax-quote to substitute an unquoted value. +`(foo ~bar) + +;; Splicing unquote: Use inside a syntax-quote to splice an unquoted +; list into a template. +`(foo ~bar ~@bazs) + + +;; For expmale build-in `defn` macro can be defined expressed with +;; simple template macro. That's more or less how build-in `defn` +;; macro is implemented. +(defmacro define-fn + [name & body] + `(def ~name (fn ~@body))) + + +;; Now if we use `define-fn` form above defined macro will be expanded +;; and compile time resulting into diff program output. +(define-fn print + [message] + (.log console message)) + + +;; Not all of the macros can be expressed via templating, but all of the +;; language is available at hand to assemble macro expanded form. +;; For instance let's define macro to ease functional chanining popular +;; in JS but usually expressed via method chaining. For example following +;; API is pioneered by jQuery is very common in JS: +;; +;; open(target, "keypress). +;; filter(isEnterKey). +;; map(getInputText). +;; reduce(render) +;; +;; Unfortunately though it usually requires all the functions need to be +;; methods of dsl object, which is very limited. Making third party +;; functions second class. Via macros we can achieve similar chaining +;; without such tradeoffs. +(defmacro -> + [& operations] + (reduce + (fn [form operation] + (cons (first operation) + (cons form (rest operation)))) + (first operations) + (rest operations))) + +(-> + (open tagret :keypress) + (filter enter-key?) + (map get-input-text) + (reduce render))