Skip to content
Browse files

move string lambdas to +to-function.js

  • Loading branch information...
1 parent 568b321 commit 85ada73629c61bcd0e930e4caeff9d3e33ab8298 @osteele osteele committed Nov 11, 2007
View
10 functional/agenda.txt
@@ -1,11 +1,9 @@
This release:
+- fix Array.slice technique
- add caching version
- jsmin version
-- download everything
+- different download options
-Next:
+Future:
- clone to separate repository
-- fix Array.slice technique
-- run in console
-- move lambda strings to separate file
-- add processes.js
+- test in console
View
191 functional/functional.js
@@ -813,192 +813,5 @@ Functional._attachMethodDelegates(Functional.__initalFunctionState.getChangedMet
delete Functional.__initalFunctionState;
-/// ^ String lambdas
-
-/**
- * Turns a string that contains a JavaScript expression into a
- * `Function` that returns the value of that expression.
- *
- * If the string contains a `->`, this separates the parameters from the body:
- * >> 'x -> x + 1'.lambda()(1) -> 2
- * >> 'x y -> x + 2*y'.lambda()(1, 2) -> 5
- * >> 'x, y -> x + 2*y'.lambda()(1, 2) -> 5
- *
- * Otherwise, if the string contains a `_`, this is the parameter:
- * >> '_ + 1'.lambda()(1) -> 2
- *
- * Otherwise if the string begins or ends with an operator or relation,
- * prepend or append a parameter. (The documentation refers to this type
- * of string as a "section".)
- * >> '/2'.lambda()(4) -> 2
- * >> '2/'.lambda()(4) -> 0.5
- * >> '/'.lambda()(2,4) -> 0.5
- * Sections can end, but not begin with, `-`. (This is to avoid interpreting
- * e.g. `-2*x` as a section). On the other hand, a string that either begins
- * or ends with `/` is a section, so an expression that begins or ends with a
- * regular expression literal needs an explicit parameter.
- *
- * Otherwise, each variable name is an implicit parameter:
- * >> 'x + 1'.lambda()(1) -> 2
- * >> 'x + 2*y'.lambda()(1, 2) -> 5
- * >> 'y + 2*x'.lambda()(1, 2) -> 5
- *
- * Implicit parameter detection ignores strings literals, variable names that
- * start with capitals, and identifiers that precede `:` or follow `.`:
- * >> map('"im"+root', ["probable", "possible"]) -> ["improbable", "impossible"]
- * >> 'Math.cos(angle)'.lambda()(Math.PI) -> -1
- * >> 'point.x'.lambda()({x:1, y:2}) -> 1
- * >> '({x:1, y:2})[key]'.lambda()('x') -> 1
- *
- * Implicit parameter detection mistakenly looks inside regular expression literals
- * for variable names. It also doesn't know to ignore JavaScript keywords and bound
- * variables. (The only way you can get these last two is with a function literal
- * inside the string. This is outside the intended use case for string lambdas.)
- * Use `_` (to define a unary function) or `->`, if the string contains anything
- * that looks like a free variable but shouldn't be used as a parameter, or
- * to specify parameters that are ordered differently from their first
- * occurrence in the string.
- *
- * Chain `->`s to create a function in uncurried form:
- * >> 'x -> y -> x + 2*y'.lambda()(1)(2) -> 5
- * >> 'x -> y -> z -> x + 2*y+3*z'.lambda()(1)(2)(3) -> 14
- *
- * `this` and `arguments` are special:
- * >> 'this'.call(1) -> 1
- * >> '[].slice.call(arguments, 0)'.call(null,1,2) -> [1, 2]
- */
-String.prototype.lambda = function() {
- var params = [];
- var expr = this;
- var sections = expr.ECMAsplit(/\s*->\s*/m);
- if (sections.length > 1) {
- while (sections.length) {
- expr = sections.pop();
- params = sections.pop().split(/\s*,\s*|\s+/m);
- sections.length && sections.push('(function('+params+'){return ('+expr+')})');
- }
- } else if (expr.match(/\b_\b/)) {
- params = '_';
- } else {
- // test whether an operator appears on the left (or right), respectively
- var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m);
- var rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m);
- if (leftSection || rightSection) {
- if (leftSection) {
- params.push('$1');
- expr = '$1' + expr;
- }
- if (rightSection) {
- params.push('$2');
- expr = expr + '$2';
- }
- } else {
- // `replace` removes symbols that are capitalized, follow '.', precede
- // ':', are 'this' or 'arguments'; and also the insides of strings
- // (by a crude test). `match` extracts the remaining symbols.
- var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*\s*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '').match(/([a-z_$][a-z_$\d]*)/gi) || []; // '
- for (var i = 0, v; v = vars[i++]; )
- params.indexOf(v) >= 0 || params.push(v);
- }
- }
- return new Function(params, 'return (' + expr + ')');
-}
-
-
-/**
- * ^^ Duck-Typing
- *
- * Strings support `call` and `apply`. This duck-types them as
- * functions, to some callers.
- */
-
-/**
- * Coerce the string to a function and then apply it.
- * >> 'x+1'.apply(null, [2]) -> 3
- * >> '/'.apply(null, [2, 4]) -> 0.5
- */
-String.prototype.apply = function(thisArg, args) {
- return this.toFunction().apply(thisArg, args);
-}
-
-/**
- * Coerce the string to a function and then call it.
- * >> 'x+1'.call(null, 2) -> 3
- * >> '/'.call(null, 2, 4) -> 0.5
- */
-String.prototype.call = function() {
- return this.toFunction().apply(arguments[0], [].slice.call(arguments, 1));
-}
-
-/// ^^ Coercion
-
-/**
- * Returns a `Function` that perfoms the action described by this
- * string. If the string contains a `return`, applies
- * `new Function` to it. Otherwise, this function returns
- * the result of `this.lambda()`.
- * >> '+1'.toFunction()(2) -> 3
- * >> 'return 1'.toFunction()(1) -> 1
- */
-String.prototype.toFunction = function() {
- var body = this;
- if (body.match(/\breturn\b/))
- return new Function(this);
- return this.lambda();
-}
-
-/**
- * Returns this function. `Function.toFunction` calls this.
- * >> '+1'.lambda().toFunction()(2) -> 3
- */
-Function.prototype.toFunction = function() {
- return this;
-}
-
-/**
- * Coerces `fn` into a function if it is not already one,
- * by calling its `toFunction` method.
- * >> Function.toFunction(function() {return 1})() -> 1
- * >> Function.toFunction('+1')(2) -> 3
- *
- * `Function.toFunction` requires an argument that can be
- * coerced to a function. A nullary version can be
- * constructed via `guard`:
- * >> Function.toFunction.guard()('1+') -> function()
- * >> Function.toFunction.guard()(null) -> null
- *
- * `Function.toFunction` doesn't coerce arbitrary values to functions.
- * It might seem convenient to treat
- * `Function.toFunction(value)` as though it were the
- * constant function that returned `value`, but it's rarely
- * useful and it hides errors. Use `Functional.K(value)` instead,
- * or a lambda string when the value is a compile-time literal:
- * >> Functional.K('a string')() -> "a string"
- * >> Function.toFunction('"a string"')() -> "a string"
- */
-Function.toFunction = function(value) {
- return value.toFunction();
-}
-
-// Utilities
-
-// IE6 split is not ECMAScript-compliant. This breaks '->1'.lambda().
-// ECMAsplit is an ECMAScript-compliant `split`, although only for
-// one argument.
-String.prototype.ECMAsplit =
- // The test is from the ECMAScript reference.
- ('ab'.split(/a*/).length > 1
- ? String.prototype.split
- : function(separator, limit) {
- if (typeof limit != 'undefined')
- throw "ECMAsplit: limit is unimplemented";
- var result = this.split.apply(this, arguments),
- re = RegExp(separator),
- savedIndex = re.lastIndex,
- match = re.exec(this);
- if (match && match.index == 0)
- result.unshift('');
- // in case `separator` was already a RegExp:
- re.lastIndex = savedIndex;
- return result;
- });
+// In case to-function.js isn't loaded.
+Function.toFunction = Function.toFunction || Functional.K;
View
1 functional/index.html
@@ -5,6 +5,7 @@
<script type="text/javascript" src="/javascripts/prototype.js"></script>
<script type="text/javascript" src="../gradients.js"></script>
<script type="text/javascript" src="functional.js"></script>
+ <script type="text/javascript" src="to-function.js"></script>
<script type="text/javascript" src="../osdoc/Base.js"></script>
<script type="text/javascript" src="../osdoc/osdoc.js"></script>
<script type="text/javascript" src="evaluator.js"></script>
View
2 functional/index.js
@@ -18,7 +18,7 @@ function initialize() {
/<span.*?<\/span>/,
'If this message remains on the screen,');
gExamples = new OSDoc.Examples({onSuccess: noteCompletion.saturate('examples'), target: $('examples')}).load('examples.js');
- gDocs = new OSDoc.APIDoc({onSuccess: noteCompletion.saturate('docs'), target: $('docs')}).load('functional.js');
+ gDocs = new OSDoc.APIDoc({onSuccess: noteCompletion.saturate('docs'), target: $('docs')}).load('functional.js', 'to-function.js');
gEval = new Evaluator('#evaluator', {onUpdate: showEvaluator});
initializeHeaderToggle();
initializeTestLinks();
View
206 functional/to-function.js
@@ -0,0 +1,206 @@
+/*
+ * Author: Oliver Steele
+ * Copyright: Copyright 2007 by Oliver Steele. All rights reserved.
+ * License: MIT License
+ * Homepage: http://osteele.com/javascripts/functional
+ * Source: http://osteele.com/javascripts/functional/functional.js
+ * Changes: http://osteele.com/javascripts/functional/CHANGES
+ * Created: 2007-07-11
+ * Modified: 2007-07-22
+ * Version: 1.0.2
+ *
+ *
+ * This defines "string lambdas", that allow strings such as `x+1` and
+ * `x -> x+1` to be used in some contexts as functions.
+ */
+
+
+/// ^ String lambdas
+
+/**
+ * Turns a string that contains a JavaScript expression into a
+ * `Function` that returns the value of that expression.
+ *
+ * If the string contains a `->`, this separates the parameters from the body:
+ * >> 'x -> x + 1'.lambda()(1) -> 2
+ * >> 'x y -> x + 2*y'.lambda()(1, 2) -> 5
+ * >> 'x, y -> x + 2*y'.lambda()(1, 2) -> 5
+ *
+ * Otherwise, if the string contains a `_`, this is the parameter:
+ * >> '_ + 1'.lambda()(1) -> 2
+ *
+ * Otherwise if the string begins or ends with an operator or relation,
+ * prepend or append a parameter. (The documentation refers to this type
+ * of string as a "section".)
+ * >> '/2'.lambda()(4) -> 2
+ * >> '2/'.lambda()(4) -> 0.5
+ * >> '/'.lambda()(2,4) -> 0.5
+ * Sections can end, but not begin with, `-`. (This is to avoid interpreting
+ * e.g. `-2*x` as a section). On the other hand, a string that either begins
+ * or ends with `/` is a section, so an expression that begins or ends with a
+ * regular expression literal needs an explicit parameter.
+ *
+ * Otherwise, each variable name is an implicit parameter:
+ * >> 'x + 1'.lambda()(1) -> 2
+ * >> 'x + 2*y'.lambda()(1, 2) -> 5
+ * >> 'y + 2*x'.lambda()(1, 2) -> 5
+ *
+ * Implicit parameter detection ignores strings literals, variable names that
+ * start with capitals, and identifiers that precede `:` or follow `.`:
+ * >> map('"im"+root', ["probable", "possible"]) -> ["improbable", "impossible"]
+ * >> 'Math.cos(angle)'.lambda()(Math.PI) -> -1
+ * >> 'point.x'.lambda()({x:1, y:2}) -> 1
+ * >> '({x:1, y:2})[key]'.lambda()('x') -> 1
+ *
+ * Implicit parameter detection mistakenly looks inside regular expression literals
+ * for variable names. It also doesn't know to ignore JavaScript keywords and bound
+ * variables. (The only way you can get these last two is with a function literal
+ * inside the string. This is outside the intended use case for string lambdas.)
+ * Use `_` (to define a unary function) or `->`, if the string contains anything
+ * that looks like a free variable but shouldn't be used as a parameter, or
+ * to specify parameters that are ordered differently from their first
+ * occurrence in the string.
+ *
+ * Chain `->`s to create a function in uncurried form:
+ * >> 'x -> y -> x + 2*y'.lambda()(1)(2) -> 5
+ * >> 'x -> y -> z -> x + 2*y+3*z'.lambda()(1)(2)(3) -> 14
+ *
+ * `this` and `arguments` are special:
+ * >> 'this'.call(1) -> 1
+ * >> '[].slice.call(arguments, 0)'.call(null,1,2) -> [1, 2]
+ */
+String.prototype.lambda = function() {
+ var params = [];
+ var expr = this;
+ var sections = expr.ECMAsplit(/\s*->\s*/m);
+ if (sections.length > 1) {
+ while (sections.length) {
+ expr = sections.pop();
+ params = sections.pop().split(/\s*,\s*|\s+/m);
+ sections.length && sections.push('(function('+params+'){return ('+expr+')})');
+ }
+ } else if (expr.match(/\b_\b/)) {
+ params = '_';
+ } else {
+ // test whether an operator appears on the left (or right), respectively
+ var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m);
+ var rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m);
+ if (leftSection || rightSection) {
+ if (leftSection) {
+ params.push('$1');
+ expr = '$1' + expr;
+ }
+ if (rightSection) {
+ params.push('$2');
+ expr = expr + '$2';
+ }
+ } else {
+ // `replace` removes symbols that are capitalized, follow '.', precede
+ // ':', are 'this' or 'arguments'; and also the insides of strings
+ // (by a crude test). `match` extracts the remaining symbols.
+ var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*\s*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '').match(/([a-z_$][a-z_$\d]*)/gi) || []; // '
+ for (var i = 0, v; v = vars[i++]; )
+ params.indexOf(v) >= 0 || params.push(v);
+ }
+ }
+ return new Function(params, 'return (' + expr + ')');
+}
+
+
+/**
+ * ^^ Duck-Typing
+ *
+ * Strings support `call` and `apply`. This duck-types them as
+ * functions, to some callers.
+ */
+
+/**
+ * Coerce the string to a function and then apply it.
+ * >> 'x+1'.apply(null, [2]) -> 3
+ * >> '/'.apply(null, [2, 4]) -> 0.5
+ */
+String.prototype.apply = function(thisArg, args) {
+ return this.toFunction().apply(thisArg, args);
+}
+
+/**
+ * Coerce the string to a function and then call it.
+ * >> 'x+1'.call(null, 2) -> 3
+ * >> '/'.call(null, 2, 4) -> 0.5
+ */
+String.prototype.call = function() {
+ return this.toFunction().apply(arguments[0], [].slice.call(arguments, 1));
+}
+
+/// ^^ Coercion
+
+/**
+ * Returns a `Function` that perfoms the action described by this
+ * string. If the string contains a `return`, applies
+ * `new Function` to it. Otherwise, this function returns
+ * the result of `this.lambda()`.
+ * >> '+1'.toFunction()(2) -> 3
+ * >> 'return 1'.toFunction()(1) -> 1
+ */
+String.prototype.toFunction = function() {
+ var body = this;
+ if (body.match(/\breturn\b/))
+ return new Function(this);
+ return this.lambda();
+}
+
+/**
+ * Returns this function. `Function.toFunction` calls this.
+ * >> '+1'.lambda().toFunction()(2) -> 3
+ */
+Function.prototype.toFunction = function() {
+ return this;
+}
+
+/**
+ * Coerces `fn` into a function if it is not already one,
+ * by calling its `toFunction` method.
+ * >> Function.toFunction(function() {return 1})() -> 1
+ * >> Function.toFunction('+1')(2) -> 3
+ *
+ * `Function.toFunction` requires an argument that can be
+ * coerced to a function. A nullary version can be
+ * constructed via `guard`:
+ * >> Function.toFunction.guard()('1+') -> function()
+ * >> Function.toFunction.guard()(null) -> null
+ *
+ * `Function.toFunction` doesn't coerce arbitrary values to functions.
+ * It might seem convenient to treat
+ * `Function.toFunction(value)` as though it were the
+ * constant function that returned `value`, but it's rarely
+ * useful and it hides errors. Use `Functional.K(value)` instead,
+ * or a lambda string when the value is a compile-time literal:
+ * >> Functional.K('a string')() -> "a string"
+ * >> Function.toFunction('"a string"')() -> "a string"
+ */
+Function.toFunction = function(value) {
+ return value.toFunction();
+}
+
+// Utilities
+
+// IE6 split is not ECMAScript-compliant. This breaks '->1'.lambda().
+// ECMAsplit is an ECMAScript-compliant `split`, although only for
+// one argument.
+String.prototype.ECMAsplit =
+ // The test is from the ECMAScript reference.
+ ('ab'.split(/a*/).length > 1
+ ? String.prototype.split
+ : function(separator, limit) {
+ if (typeof limit != 'undefined')
+ throw "ECMAsplit: limit is unimplemented";
+ var result = this.split.apply(this, arguments),
+ re = RegExp(separator),
+ savedIndex = re.lastIndex,
+ match = re.exec(this);
+ if (match && match.index == 0)
+ result.unshift('');
+ // in case `separator` was already a RegExp:
+ re.lastIndex = savedIndex;
+ return result;
+ });
View
3 osdoc/index.html
@@ -4,6 +4,7 @@
<title>OSDoc</title>
<script type="text/javascript" src="/javascripts/prototype.js"></script>
<script type="text/javascript" src="../functional/functional.js"></script>
+ <script type="text/javascript" src="../functional/to-function.js"></script>
<script type="text/javascript" src="Base.js"></script>
<script type="text/javascript" src="osdoc.js"></script>
<script type="text/javascript" src="index.js"></script>
@@ -31,7 +32,7 @@
a look around.</a></p>
<hr/>
</div>
-
+
<div id="noscript">
You either have JavaScript turned off or you're using a browser
I haven't tested. Either way, you're missing out on the page
View
24 osdoc/osdoc.apidoc.js
@@ -27,15 +27,25 @@ OSDoc.APIDoc = function(options) {
/// Load +url+ and parse its contents.
OSDoc.APIDoc.prototype.load = function(url) {
- var options = this.options;
+ var self = this,
+ options = this.options,
+ count = arguments.length,
+ results = new Array(count);
options.target && (options.target.innerHTML = OSDoc.loadingHeader);
- if (options.bustCache)
- url += '?ts='+new Date().getTime();
- new Ajax.Request(
- url,
- {method: 'GET',
- onSuccess: Functional.compose(this.parse.bind(this), '_.responseText').reporting()});
+ Array.prototype.slice.call(arguments, 0).each(function(url, ix) {
+ if (options.bustCache)
+ url += (/\?/(url) ? '&' : '?') + 'ts=' + new Date().getTime();
+ new Ajax.Request(
+ url,
+ {method: 'GET',
+ onSuccess: receive.reporting().bind(this, ix)});
+ });
return this;
+ function receive(ix, response) {
+ results[ix] = response.responseText;
+ if (!--count)
+ self.parse(results.join(''));
+ }
}
/// Parse +text+. If +options.target+ is specified, update it.
View
3 osdoc/osdoc.run.js
@@ -8,8 +8,9 @@
var window = {};
window.OSDoc = {loaded:true};
window.Prototype = {Version:1.5};
-load('../functional/functional.js');
load('../collection-utils.js');
+load('../functional/functional.js');
+load('../functional/to-function.js');
load('Base.js')
load('osdoc.js');
load('osdoc.utils.js');
View
2 osdoc/osdoc.utils.js
@@ -11,7 +11,7 @@ Function.prototype.reporting = function() {
try {
fn.apply(this, arguments);
} catch (e) {
- window.console && console.info(e);
+ window.console && console.error(e);
throw e;
}
}

0 comments on commit 85ada73

Please sign in to comment.
Something went wrong with that request. Please try again.