Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
Checking mergeability… Don’t worry, you can still create the pull request.
  • 14 commits
  • 4 files changed
  • 0 commit comments
  • 1 contributor
Showing with 436 additions and 96 deletions.
  1. +16 −9 index.html
  2. +131 −69 lib/ls.js
  3. +243 −0 src/ls.ls
  4. +46 −18 src/macros.ls
View
25 index.html
@@ -44,8 +44,7 @@
</style>
</head>
<body>
-<textarea id="input" name="input">
-;; Hello World! in LispyScript.
+<script type="text/expamles" id="examples">;; Hello World! in LispyScript.
(console.log "Hello LispyScript!")
;; A more intricate Hello World!
@@ -142,25 +141,33 @@
(function (e)
(console.log (+ "Cannot write file " outfile)
(process.exit 1))))
-
-</textarea>
-<textarea id="output" name="output"></textarea>
+</script>
+<div id="input" name="input"></div>
+<div id="output" name="output"></div>
<script>
function updatePreview(editor) {
- output.setValue(require('../lib/ls')._compile(editor.getValue()))
+ var code = editor.getValue()
+ localStorage.buffer = code
+ output.setValue(require('../lib/ls')._compile(code))
}
- var input = CodeMirror.fromTextArea(document.getElementById("input"), {
+
+ var input = CodeMirror(document.getElementById("input"), {
lineNumbers: true,
mode: "scheme",
theme: "ambiance",
autofocus: true,
+ fixedGutter: true,
+ matchBrackets: true,
+ value: localStorage.buffer ||
+ document.getElementById("examples").innerHTML,
onChange: updatePreview
});
- var output = CodeMirror.fromTextArea(document.getElementById("output"), {
+ var output = CodeMirror(document.getElementById("output"), {
lineNumbers: true,
+ fixedGutter: true,
mode: "javascript",
theme: "ambiance",
- readOnly: "nocursor"
+ readOnly: true
});
setTimeout(updatePreview, 1000, input)
View
200 lib/ls.js
@@ -18,21 +18,19 @@ var _LS = {},
banner = "// Generated by LispyScript v" + this.version + "\n",
isWhitespace = /\s/,
isFunction = /^function/,
- validName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/,
+ validSymbol = /^[a-zA-Z_$]([\-\.\[]{0,1}[0-9a-zA-Z_$]+\]{0,1})*\?{0,1}$/,
+ validName = /[a-zA-Z_$]/,
noReturn = /^var|set|throw\b/,
indent = -4,
keywords = {},
macros = {},
templates = {};
-templates["var"] = _.template("var <%= rest %>");
-templates["new"] = _.template("new <%= rest %>");
-templates["set"] = _.template("<%= name %> = <%= value %>");
templates["function"] = _.template("function(<%= params %>) {\n<%= expressions %><%= indent %>}");
templates["try"] = _.template("(function () {\n<%= indent %><%= indent %>try {\n<%= indent %><%= trypart %>\n<%= indent %><%= indent %>} catch (e) {\n<%= indent %><%= indent %><%= indent %>return (<%= catchpart %>)(e);\n<%= indent %><%= indent %>}\n<%= indent %>})()");
templates["if"] = _.template("<%= condition %> ?\n<%= indent %><%= trueexpr %> :\n<%= indent %><%= falseexpr %>");
-templates["get"] = _.template("<%= list %>[<%= key %>]");
templates["operator"] = _.template("(<%= loperand %> <%= operator %> <%= roperand %>)");
templates["str"] = _.template("[<%= elems %>].join('')");
+templates[".invoke"] = _.template("(<%= target %>).<%= method %>");
var parse = function(code, filename) {
code = "(" + code + ")";
@@ -43,6 +41,7 @@ var parse = function(code, filename) {
var parser = function() {
var tree = [],
token = "",
+ special = "",
isString = false,
isSingleString = false,
isJSArray = 0,
@@ -54,7 +53,12 @@ var parse = function(code, filename) {
tree._filename = filename;
var handleToken = function() {
if (token) {
- tree.push(token);
+ if (special) {
+ tree.push([ special, token ]);
+ special = "";
+ }
+ else
+ tree.push(token);
token = "";
}
};
@@ -66,6 +70,10 @@ var parse = function(code, filename) {
if (isComment) isComment = false;
}
if (isComment) continue;
+ if (isRegex && c !== "/") {
+ token += c;
+ continue;
+ }
if (c == '"') {
if (isString && token[token.length - 1] === "\\") {
token += c;
@@ -82,11 +90,6 @@ var parse = function(code, filename) {
token += c;
continue;
}
- if (c == "'") {
- isSingleString = !isSingleString;
- token += c;
- continue;
- }
if (isSingleString) {
token += c;
continue;
@@ -125,21 +128,26 @@ var parse = function(code, filename) {
isComment = true;
continue;
}
- if (c == "/") {
- isRegex = true;
- token += c;
- continue;
- }
- if (isRegex) {
- if (isWhitespace.test(c)) {
- isRegex = false;
- } else {
+
+ if (c == '/') {
+ if (isRegex && token[token.length - 1] === "\\") {
token += c;
continue;
}
+ isRegex = !isRegex;
+ token += c;
+ continue;
}
+
+
if (c == "(") {
- tree.push(parser());
+ var compound = parser();
+ if (special) {
+ tree.push([ special, compound ]);
+ special = "";
+ }
+ else
+ tree.push(compound);
continue;
}
if (c == ")") {
@@ -151,6 +159,18 @@ var parse = function(code, filename) {
handleToken();
continue;
}
+ if (c === "`") {
+ special = "quasiquote";
+ continue;
+ }
+ if (c === "'") {
+ special = "quote";
+ continue;
+ }
+ if (c === "~") {
+ special = "unquote";
+ continue;
+ }
token += c;
}
if (isString) throw handleError(3, tree._line, tree._filename);
@@ -190,6 +210,19 @@ var handleExpressions = function(exprs) {
return ret;
};
+var handleSymbol = function(symbol, expr) {
+ if (!validName.test(symbol.charAt(0)))
+ return symbol;
+ if (!validSymbol.test(symbol))
+ throw handleError(12, expr._line, expr._filename);
+ var isPredicate = symbol.substr(-1) === "?";
+ var name = isPredicate ? symbol.substr(0, symbol.length - 1) : symbol;
+
+ return name.split('-').reduce(function(result, $) {
+ return result + (result ? $[0].toUpperCase() + $.substr(1) : $);
+ }, isPredicate ? 'is' : '');
+};
+
var handleExpression = function(expr) {
var command = expr[0];
if (macros[command]) {
@@ -199,9 +232,8 @@ var handleExpression = function(expr) {
if (_.isString(command)) {
if (keywords[command])
return keywords[command](expr);
- if (command.charAt(0) === ".") {
- return "(" + (_.isArray(expr[1]) ? handleExpression(expr[1]) : expr[1]) + ")" + command;
- }
+ if (!validName.test(command.charAt(0)))
+ return readers[command.charAt(0)](expr);
}
handleSubExpressions(expr);
var fName = expr[0];
@@ -212,47 +244,67 @@ var handleExpression = function(expr) {
var handleSubExpressions = function(expr) {
_.each(expr, function(value, i, t) {
- if (_.isArray(value)) t[i] = handleExpression(value);
+ t[i] = _.isArray(value) ? handleExpression(value)
+ : handleSymbol(value);
});
+ return expr;
};
var macroExpand = function(tree) {
var command = tree[0],
- template = macros[command]["template"],
- code = macros[command]["code"],
+ template = macros[command].template,
+ code = macros[command].code,
replacements = {};
+
for (var i = 0; i < template.length; i++) {
- if (template[i] == "rest...") {
- replacements["~rest..."] = tree.slice(i + 1);
+ if (template[i].substr(-3) === "...") {
+ replacements[template[i]] = tree.slice(i + 1);
} else {
- replacements["~" + template[i]] = tree[i + 1];
+ replacements[template[i]] = tree[i + 1];
}
}
+
var replaceCode = function(source) {
var ret = [];
ret._line = tree._line;
+ ret._filename = tree._filename;
+
for (var i = 0; i < source.length; i++) {
- if (typeof source[i] == "object") {
- ret.push(replaceCode(source[i]));
- } else {
- var token = source[i];
- var isATSign = false;
- if (token.indexOf("@") >= 0) {
- isATSign = true;
- token = token.replace("@", "") ;
- }
- if (replacements[token]) {
+ var token = source[i];
+ if (typeof token == "object") {
+ if (token[0] === "unquote" ||
+ token[0] === "quote" ||
+ token[0] === "quasiquote")
+ ret = ret.concat(replaceCode(token));
+ else
+ ret.push(replaceCode(token));
+ }
+ else if (token === "unquote") {
+ token = source[++i];
+ if (typeof token === "object") {
+ ret.push(replaceCode(token));
+ } else {
+ var splicing = token[0] === "@";
+ var spreading = token.substr(-3) === "...";
+ token = splicing ? token.substr(1) : token;
var repl = replacements[token];
- if (isATSign || token == "~rest...") {
- for (var j = 0; j < repl.length; j++)
- ret.push(repl[j]);
- } else {
+ if (splicing || spreading)
+ ret = ret.concat(repl);
+ else
ret.push(repl);
- }
- } else {
- ret.push(token);
}
}
+ else if (token === "quote") {
+ token = source[++i];
+ ret.push(token);
+ }
+ else if (token === "quasiquote") {
+ token = source[++i];
+ ret = ret.concat(replaceCode(token));
+ }
+ else {
+ ret.push(token);
+ }
}
return ret;
};
@@ -271,29 +323,32 @@ var handleError = function(no, line, filename) {
return errors[no] + ((line) ? "\nLine no " + line : "") + ((filename) ? "\nFile " + filename : "");
};
-keywords["var"] = function(arr) {
- if (!validName.test(arr[1])) throw handleError(9, arr._line, arr._filename);
- return templates["var"]({rest: keywords.set(arr)});
-};
+var readers = {};
-keywords["new"] = function(arr) {
- if (arr.length < 2) throw handleError(0, arr._line, arr._filename);
- return templates["new"]({ rest: handleExpression(arr.slice(1)) });
-};
+readers["."] = function(expression) {
+ var invoker = expression[0];
+ var target = expression[1];
+ var params = expression.slice(2);
+ var owner = _.isArray(target) ? handleExpression(target)
+ : handleSymbol(target);
+ var method = handleExpression([ invoker.substr(1) ].concat(params));
+ return templates[".invoke"]({ target: owner, method: method });
+};
-keywords["set"] = function(arr) {
- if (arr.length != 3) throw handleError(0, arr._line, arr._filename);
- return templates["set"]({
- name: arr[1],
- value: (typeof arr[2] == "object") ? handleExpression(arr[2]) : arr[2]});
+keywords["js"] = function(arr) {
+ return handleSubExpressions(arr.slice(1)).join(' ');
};
keywords["function"] = function(arr) {
if (arr.length < 3) throw handleError(0, arr._line, arr._filename);
if (typeof arr[1] != "object") throw handleError(0, arr._line);
+ var params = arr[1].map(function(name) {
+ return handleSymbol(name, arr);
+ });
+
return templates["function"]({
- params: arr[1].join(","),
+ params: params.join(","),
expressions: handleExpressions(arr.slice(2)),
indent: " ".repeat(indent)});
};
@@ -307,6 +362,16 @@ keywords["try"] = function(arr) {
indent: " ".repeat(indent)});
};
+keywords["symbol"] = function(arr) {
+ if (arr.length < 2) throw handleError(0, arr._line, arr._filename);
+ return arr[1].substr(1, arr[1].length - 2)
+}
+
+keywords["symbols-join"] = function(arr) {
+ if (arr.length < 2) throw handleError(0, arr._line, arr._filename);
+ return handleSubExpressions(arr.slice(2)).join(arr[1]);
+};
+
keywords["if"] = function(arr) {
if (arr.length < 3 || arr.length > 4) throw handleError(0, arr._line, arr._filename);
indent += 4;
@@ -320,12 +385,6 @@ keywords["if"] = function(arr) {
return ret;
};
-
-keywords["get"] = function(arr) {
- if (arr.length != 3) throw handleError(0, arr._line, arr._filename);
- return templates["get"]({key: arr[1], list: arr[2]});
-};
-
keywords["str"] = function(arr) {
if (arr.length < 2) throw handleError(0, arr._line, arr._filename);
handleSubExpressions(arr);
@@ -385,6 +444,7 @@ keywords["||"] = handleOperator;
keywords["&&"] = handleOperator;
+
keywords["!"] = function(arr) {
if (arr.length != 2) throw handleError(0, arr._line, arr._filename);
handleSubExpressions(arr);
@@ -394,18 +454,20 @@ keywords["!"] = function(arr) {
errors = [];
errors[0] = "Syntax Error";
errors[1] = "Empty statement";
-errors[2] = "Invalid characters in function name";
+errors[2] = "Invalid function name";
errors[3] = "End of File encountered, unterminated string";
errors[4] = "Closing square bracket, without an opening square bracket";
errors[5] = "End of File encountered, unterminated array";
errors[6] = "Closing curly brace, without an opening curly brace";
errors[7] = "End of File encountered, unterminated javascript object '}'";
errors[8] = "End of File encountered, unterminated parenthesis";
-errors[9] = "Invalid character in var name";
+errors[9] = "Invalid variable name";
errors[10] = "Extra chars at end of file. Maybe an extra ')'.";
errors[11] = "Cannot Open include File";
+errors[12] = "Invalid symbol";
exports._compile = function(code, filename) {
+ indent = -4;
var tree = parse(code, filename);
return banner + handleExpressions(tree);
};
View
243 src/ls.ls
@@ -0,0 +1,243 @@
+;; LispyScript - Javascript using tree syntax!
+;; This is the compiler written in lispy itself.
+
+(define _ (require "underscore"))
+(define Module module.Constructor)
+
+(define version "0.1.6")
+
+
+(define macros {})
+
+
+;; templates
+(define templates {})
+(set! templates.var (template (rest) "var " rest))
+(set! templates.new (template (rest) "new " rest))
+(set! templates.set (template (name value) name " = " value))
+(set! templates.get (template (list key) list "[" key "]"))
+(set! templates.operator (template (left operator right) left operator right))
+(set! templates.str (template (rest) "[" rest "]"))
+(set! templates.banner (template (version)
+"// Generated by LispyScript v" version "\n"))
+(set! templates.function (template (params expressions indent)
+"function(" params ") {
+" indent expressions "
+}"))
+(set! templates.try (template (try-expression catch-expression indent)
+"(function() {
+" indent "try {
+" indent indent try-expression "
+" indent "} catch (e) {
+" indent indent "return (" catch-expression ")(e);
+" indent "}"
+"})()"))
+(set! templates.if (template (condition true-exp false-exp)
+condition " ?
+" indent true-exp " :
+" false-exp))
+
+
+(define banner (templates.banner version))
+
+;; utilities
+
+(define repeat
+ (lambda (string n)
+ (var array (new Array (+ n 1)))
+ (array.join string)))
+
+(define rest
+ (lambda (list) (Array.prototype.slice.call list 1)))
+
+(define expression?
+ (lambda (input) (array? input)))
+
+;; keywords
+(define keywords {})
+
+(define operators
+ (Array "+" "-" "*" "/" "%" "=" "!=" ">" ">=" "<" "<=" "||"))
+
+(each operators
+ (lambda (operator)
+ (set! keywords[operator] handle-operator)))
+
+(define operator-not "!")
+(set! keywords[operator-not]
+ (lambda (expression)
+ (if (!= expression.length 2)
+ (throw (handle-error
+ 0
+ expression._line
+ expression._filename)))
+
+ ;; TODO: Improve operators support in order to allow
+ ;; multi argument forms.
+ (+ (+ "(!" (handle-sub-expressions expression)) ")")))
+
+(set! keywords.var
+ (lambda (expr)
+ (if (< expr.length 2)
+ (expr.push "undefined"))
+ (templates.var (keywords.set expr))))
+
+(set! keywords.new
+ (lambda (expr)
+ (if (< expr.length 2)
+ (throw (handle-error 0 expr._line expr._filename)))
+
+ (templates.new (handle-expression (rest 1)))))
+
+(set! keywords.throw
+ (lambda (expr)
+ (if (< expr.length 2)
+ (throw (handle-error 0 expr._line expr._filename)))
+
+ (templates.throw (handle-expression (rest expr)))))
+
+(set! keywords.get
+ (lambda (expr)
+ (if (!= expr.length 3)
+ (throw (handle-error 0 expr._line expr._filename)))
+ (templates.get expr[1] expr[2])))
+
+(set! keywords.set
+ (lambda (expr)
+ (if (!= expr.length 3)
+ (throw (handle-error 0 expr._line expr._filename)))
+ (define name expr[1])
+ (define value expr[2])
+ (templates.set (handle-symbol name expr)
+ (if (expression? value)
+ (handle-expression value)
+ (handle-symbol value)))))
+
+(set! keywords.function
+ (lambda (expr)
+ (if (< expr.length 3)
+ (throw (handle-error 0 expr._line expr._filename)))
+
+ (define params expr[1])
+ (if (! (expression? params))
+ (throw (handle-error 0 expr._line expr._filename)))
+
+ (define param-symbols
+ (map params (lambda (symbol) (handle-symbol symbol expr))))
+
+ (templates.function
+ (param-symbols.join ", ")
+ (handle-expressions (rest (rest expr)))
+ (repeat " " indent))))
+
+(set! keywords.try
+ (lambda (expression)
+ (if (< expression.length 3)
+ (throw (handle-error
+ 0
+ expression._line
+ expression._filename)))
+
+ (templates.try
+ (handle-expressions (rest expression))
+ (if (expression? catch-expression)
+ (handle-expression catch-expression)
+ (handle-symbol catch-expression))
+ (repeat " " indent))
+
+ (set! catch-expression (expression.pop))))
+
+(set! keywords.if
+ (lambda (expression)
+ (if (|| (< expression.length 3)
+ (> expression.length 4))
+ (throw (handle-error
+ 0
+ expression._line
+ expression._filename)))
+
+ ;; TODO: pass indent-level rather than change globally
+ (set! indent (+ indent 4))
+ (set! expression (handle-sub-expressions expression))
+ (define result
+ (templates.if
+ expression[1]
+ expression[2]
+ (|| expressions[3] "undefined")))
+
+ (set! indent (- indent 4))
+ result))
+
+(set! keywords.str
+ (lambda (expression)
+ (if (< expression.length 2)
+ (throw (handle-error
+ 0
+ expression._line
+ expression._filename)))
+
+ (set! expression (handle-sub-expressions expression))
+ (define input (rest expression))
+
+ (templates.str (input.join ", "))))
+
+(set! keywords.macro
+ (lambda (expression)
+ (if (!= expression.length 4)
+ (throw (handle-error
+ 0
+ expression._line
+ expression._filename)))
+
+ (define name expression[1])
+ (define template expression[2])
+ (define code expression[3])
+
+ (set! macros[name] { template: expression[2], code: expression[3] })
+ ""))
+
+(set! keywords.include
+ (lambda (expression)
+ (if (!= expression.length 2)
+ (throw (handle-error
+ 0
+ expression._line
+ expression._filename)))
+
+ (set! indent (- indent 4))
+ (define filename expression[1])
+ (set! filename
+ (if (string? filename)
+ ;; Use two quotes in regexp or syntax highlighters go nuts.
+ (filename.replace /"|'|"/g "")
+ filename))
+
+ (define module (new Module expression._filename))
+ (set! module.filename expressions._filename)
+
+ (try
+ (require (Module._resolveFilename filename module))
+ (lambda (error)
+ (throw (handle-error
+ 11
+ expression._line
+ expression._filename))))
+
+ (set! indent (+ indent 4))
+ ""))
+
+
+;; predicates
+(define predicate
+ (lambda (regexp)
+ (lambda (input) (regexp.test input))))
+
+(define whitespace? (predicate /\s/))
+(define function? (predicate /^function/))
+(define validName? (predicate /^[a-zA-Z_$][0-9a-zA-Z_$]*$/))
+(define no-return? (predicate /^var|set|throw\b/))
+
+
+;; exports
+(set! exports.version version)
+
View
64 src/macros.ls
@@ -1,57 +1,85 @@
;; List of built in macros for LispyScript. This file is included by
;; default by the LispyScript compiler.
+
+(macro new (body...)
+ `(js 'new (~body...)))
+
+(macro throw (rest...)
+ `((function () (js 'throw ~@rest...))))
+
+(macro Array (body...)
+ `(js (symbol "[") (symbols-join ', ~body...) (symbol "]")))
+
+(macro define (rest...)
+ `(var ~rest...))
+
+(macro lambda (rest...)
+ `(function ~rest...))
+
+(macro var (name value)
+ `(js 'var (set ~name ~value)))
+
+(macro set (name value)
+ `(js ~name '= ~value))
+
+(macro set! (rest...)
+ `(set ~@rest...))
+
+(macro get (field object)
+ `(js ~object (symbol "[") ~field (symbol "]")))
+
(macro object? (obj)
- (= (typeof ~obj) "object"))
+ `(= (typeof ~obj) "object"))
(macro array? (obj)
- (= (toString.call ~obj) "[object Array]"))
+ `(= (toString.call ~obj) "[object Array]"))
(macro string? (obj)
- (= (toString.call ~obj) "[object String]"))
+ `(= (toString.call ~obj) "[object String]"))
(macro number? (obj)
- (= (toString.call ~obj) "[object Number]"))
+ `(= (toString.call ~obj) "[object Number]"))
(macro boolean? (obj)
- (= (typeof ~obj) "boolean"))
+ `(= (typeof ~obj) "boolean"))
(macro function? (obj)
- (= (toString.call ~obj) "[object Function]"))
+ `(= (toString.call ~obj) "[object Function]"))
(macro undefined? (obj)
- (= (typeof ~obj) "undefined"))
+ `(= (typeof ~obj) "undefined"))
(macro null? (obj)
- (= ~obj null))
+ `(= ~obj null))
(macro do (rest...)
- ((function () ~rest...)))
+ `((function (_) ~rest...)))
(macro when (cond rest...)
- (if ~cond (do ~rest...)))
+ `(if ~cond (do ~rest...)))
(macro unless (cond rest...)
- (when (! ~cond) (do ~rest...)))
+ `(when (! ~cond) (do ~rest...)))
(macro each (rest...)
- (Array.prototype.forEach.call ~rest...))
+ `(Array.prototype.forEach.call ~rest...))
(macro map (rest...)
- (Array.prototype.map.call ~rest...))
+ `(Array.prototype.map.call ~rest...))
(macro filter (rest...)
- (Array.prototype.filter.call ~rest...))
+ `(Array.prototype.filter.call ~rest...))
(macro some (rest...)
- (Array.prototype.some.call ~rest...))
+ `(Array.prototype.some.call ~rest...))
(macro every (rest...)
- (Array.prototype.every.call ~rest...))
+ `(Array.prototype.every.call ~rest...))
(macro reduce (rest...)
- (Array.prototype.reduce.call ~rest...))
+ `(Array.prototype.reduce.call ~rest...))
(macro template (args rest...)
- (function ~args
+ `(function ~args
(str ~rest...)))

No commit comments for this range

Something went wrong with that request. Please try again.