Oppo
If javascript is lisp in c's clothing, then oppo is lisp in lisp's clothing, with c's pajamas.
Install / Use
/learn @benekastah/OppoREADME
Note to anyone who may care: I haven't used or substantially updated this project in a long time and do not plan on putting any further effort into it. The npm package has been deprecated to reflect this. If you want this to work for you, feel free to fork it.
Oppo
Oppo is a sweet little lisp for javascript. This is experimental, and therefore any suggestions are welcome.
Usage
To install, run the following in your terminal. Make sure you have node.js and npm installed first.
npm install -g oppo@0.1.2 # The repl doesn't work properly in 0.1.3
Now you have access to the oppo command. To enter the repl:
oppo
or
oppo -r
To compile a file:
oppo -c /path/to/file.oppo
You can also specify an output file:
oppo -o /path/to/file.js -c /path/to/file.oppo
In the previous command, if you have uglify-js2 installed globally,
it will prettify the resulting javascript. You could pass the -C switch to
have it compress your code instead.
To run a file:
oppo /path/to/file.oppo
Language Overview
The basics
Since oppo is a lisp, it takes on the basic semantics of a lisp. Oppo isn't dissimilar to scheme or clojure, so if you want to get a feel for the basic semantics of lisp, there's plenty of code out there to look at.
Here are some specifics about the way oppo is parsed:
Numbers
- Integer:
2,-5 - Float:
-2.5,.34 - Scientific notation:
10e-2.5 - Explicit base:
2#001101,8#1427,16#fff
Strings
"whatever". If you need to escape a double-quote, you can use \".
Symbols
Oppo accepts most characters as valid symbols. Symbols can't start
with a number or any of the following sequences: #, ", ', \``, ,, ,@, .`. Symbols can't contain any kind of whitespace. Anything
else on the US keyboard is fair game.
Lists
Lists are code. Oppo code is primarily a tree of lists. All lists are executable. The basic way to make a list is to put space-separated items in parenthesis:
(puts "I" "am" "a" "list")
Since lists are executable, this will call the puts macro, which
will log the rest of the items in the list to the console. If you
don't want your list to be executed you can quote it or use a literal
array:
'(1 2 3) ;; Won't try to call this as a function
#[1 2 3] ;; Same result
Objects
Oppo only thinks of objects as data and never tries to execute an object. Objects are made like this:
#{'a 1 'b 2}
You can do some interesting things with oppo objects that you can't do in javascript (at least not as easily). For example, keys can be variables in the same way their values can:
(def key 'asdf)
(def value 5)
#{key value} ;; -> #{asdf 5}
Functions
Oppo uses the lambda macro to generate functions. There is also a
reader macro #(...) which acts as a shorthand for the lambda
macro. You can also define a function with the def macro:
(lambda (a b) (+ a b))
#(+ #1 #2) ;; #1 and #2 access the 0th and 1st arguments respectively
(def (add-a-b a b)
(+ a b))
Language Documentation
Following is a list of macros, functions and variables available to
the oppo programmer. Entries take the format of
<module>::<item-name>. The core module is automatically available,
so you don't have to use the module prefix in that case. As an
example, you can call core::str by simply invoking str (unless you
have replaced str in your current module or in an active local
scope).
core module
-
macro
core::defmacro:(defmacro optional-metadata (macro-name ...args) ...body)Defines a macro.Example:
(defmacro (log ...x) `(.log console ,@x)) -
macro
core::def:(def optional-metadata name value)|(def optional-metadata (fn-name ...args) ...body)Defines a variable on the current module.Example:
;; Define module variable (def one 1) ;; Define function (def (identity x) x) -
macro
core::set!:(set! name value)Resets the value of a variable.Example:
(def n 1) (set! n (+ n 1)) -
macro
core::lambda:(lambda (...args) ...body)Creates a function. You can also use the#(...)reader macro for this. Arguments that aren't named can still be accessed from theargumentsobject. A reader macro is provided for this as well,#1where the1can be any number greater than 0.#1will access the 0th argument,#2the first argument and so on.Example:
(map (lambda (a) (+ a 1)) #[1 2 3 4 5]) ;; -> #[2 3 4 5 6] (map #(+ #1 1) #[1 2 3 4 5]) ;; -> #[2 3 4 5 6] -
macro
core::call:(call callable-item ...args)This is used internally for all function/macro calling. This is not generally the way you will need to call things.Example:
(call puts 1 2 3) ;; The normal way to call things is without `call`. ;; Running it this way will do the same thing as the line above. (puts 1 2 3) -
macro
core::object-get-value:(object-get-value prop base)Gets the value with the correspondingpropin a collection. You shouldn't often need to invoke this directly. See example below.Example:
(object-get-value 'alert window) ;; Here's a better way to do this: ('alert window) ;; or ("alert" window) -
macro
core::.:(. fname base ...args)Gets a callable value frombaseand immediately calls it withargs. Because of a handy reader macro, there doesn't need to be a space separating the.from thefname.Example:
(.alert window 5) (."static" express) -
macro
core::quote:(quote x)Quotesx. Instead of using the explicit call, most of the time you will want to use the reader macro'.Example:
(quote x) ;; -> x 'x ;; -> x -
macro
core::quasiquote:(quasiquote x)Quasiquotesx. You can also use the reader macro ```.Example:
(def a 5) `(1 2 3 4 a ,a) ;; -> '(1 2 3 4 a 5) -
macro
core::unquote:(unquote x)Unquotesx. You can also use the reader macro,.Example:
(def a 5) `(1 2 3 4 a ,a) ;; -> '(1 2 3 4 a 5) -
macro
core::unquote-splicing:(unquote-splicing x)Unquotes each item inxin sequence into another structure. You can also use the reader macro,@.Example:
(def a #[1 2 3 4 5]) `(0 ,@a 6) ;; -> #[0 1 2 3 4 5 6] -
macro
core::let:(let (...bindings) ...body)Introduces a local scope.bindingsare the local variables. It will run each item in body in this new local scope and return the result of the last item.Example:
(let [a 1 b 2 c 3] (+ a b c)) ;; -> 6 -
macro
core::if:(if condition run-if-true optional-run-if-false)ifexpression.Example:
(if (nil? x) (do-something) (do-something-else)) -
macro
core::for:(for [defs ls] ...body)Theforexpression is very similar to map, but can be much faster.Example:
;; `n` represents an item in the array. (for [n #[1 2 3 4 5]] (* n 2)) ;; -> #[2 4 6 8 10] ;; `n` represents an item in the array and `i` represents the index. (for [(n i) #[1 2 3 4 5]] (+ n i)) ;; -> #[1 3 5 7 9] -
macro
core::do:(do ...body)dowill run each item inbodyand give you the result of the last statement.Example:
(if (< n 5) (do (puts n) (set! n (+ n 1)))) -
macro
core::include:(include ...module-names)This will find and compile each module. If the module has already been compiled, then it is already available and will not attempt to compile it again.Modules are simple directory paths that are resolved in the same base directory as the main file. Generally, you should only send one single file to the oppo compiler, and this file will include all the modules it needs, and those modules will include the modules they need, and so forth. In this way, the compiler will get all the source files used and compile them all into a single javascript file.
Example:
(include routes/main routes/users) (.get app "/" routes/main::home) (.get app "/user/:id" routes/users::show) -
macro
core::apply:(apply fn args)This is the same as using the javascript .apply function, except you can't specify scope. -
macro
core::require:(require module-name)|(require varname module)Makes using the node.js require function (or any implementation similar enough) simpler to use.Example:
(require express) ;; var express = require('express'); (require routes "./routes") ;; var routes = require('./routes'); -
macro
core::new:(new Class ...args)Allows you to instantiate a class. -
macro
core::puts:(puts ...args)Logs output to the console. -
macro
core::puts-warning:(puts-warning ...args)Logs warning message to the console. -
macro
core::puts-error:(puts-error ..args)Logs error message to the console. -
macro
core::cond:(cond cond1 body1 cond2 body2 cond-n body-n)Evaluates each condition until it finds one that is true. When it finds a true condition, it executes the corresponding body.Example:
(def n 3) (cond (eq n 1) (puts "one") (eq n 2) (puts "two") (eq n 3) (puts "three") 'else (puts "idk")) ;; will print "three" to the console. -
macro
