Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP


Beautiful literate programming. #1789

wants to merge 6 commits into from

5 participants


As discussed at the issue jashkenas/coffeescript#1786
This patch enables very beautiful literate programming.

If it pleases @jashkenas, adding a magic test (instead or in conjunction with the filename test) shall be implemented as well.

Implemented magic test.

@@ -32,7 +32,7 @@ exports.helpers = require './helpers'
# compiler.
exports.compile = compile = (code, options = {}) ->
- (parser.parse lexer.tokenize code).compile options
+ (parser.parse lexer.tokenize (if (options.filename or '').match /\.literatecoffee$/ then code.replace(/(^|\n)(\S)/gi, '$1#$2') else code)).compile options
@michaelficarra Collaborator

This line is WAY too long. Use a variable and split it.

Changed it in revence27/coffee-script@5a84fb4 to be three more lines, each of which is nevertheless shorter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@@ -32,7 +32,10 @@ exports.helpers = require './helpers'
# compiler.
exports.compile = compile = (code, options = {}) ->
- (parser.parse lexer.tokenize code).compile options
+ transformedCode = code
+ if (options.filename or '').match /\.literatecoffee$/ then
@michaelficarra Collaborator

if options.filename?.match /\.literatecoffee$/ then

Isn’t that the wrong use of it? We are not trying to soak up nulls; we are trying to return the better string for the method to work on. Soaking up nulls, because it is a monadic action, will also have to propagate, which is ugly, and which your snippet doesn’t do.

@michaelficarra Collaborator

@revence27: what are the possible values of options.filename? I believe we are only expecting it to be undefined or a string literal. In the case that it is undefined, the test will fail as soon as it notices that options.filename is undefined. In the case that it is a string literal (well, not undefined or null), it will proceed to the match invocation, causing the test to fail if the result is falsey and pass if the result is truthy.

You are right.
Fixed it in revence27/coffee-script@95ac581 and now I am seriously going to bed. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment


code.indexOf('Literate CoffeeScript\n') == 0

to for example

/^[^\n]*Literate CoffeeScript.*\n/.test code

would allow the first line to be inside a comment

    <!-- Literate CoffeeScript -->


    # Literate CoffeeScript

which can make it invisible when used with some tools.

No, when you need tools to ignore a preamble that is not “Literate CoffeeScript”, then you should use the file extension, .literatecoffee.
Your suggestion would make your comment, for example, pass as Literate CoffeeScript, even though it is not. :o) When the design is of something that executes arbitrary code, wooly heuristic tests are a strict no-no.

Or, tools that generate this stuff from the Literate CoffeeScript could be told to skip the first line (which, in their case, is generally easy to do).
Making the test any more complex than checking for exactly two tokens (known to be illegal in that position under valid CoffeeScript) would throw us back to having to use heuristics and, ultimately, to having false positives/negatives.

We should be readier to evolve new tools to work with Literate CoffeeScript (as @jashkenas said, regarding Docco) than to make the compiler less-correct. Beauty for the win.

It was indeed to make Literate CoffeeScript more compatible with existing markdown renderers. Love your direction and since you invoke beauty as a criteria: I retract the suggestion and look forward to new tools.


@jashkenas As discussed in the issue, I have added the magic test.
It should be an alternative way to tell the compiler that we are doing Literate CoffeeScript.


any update on this?


Let's move this conversation back over to #1786.

@jashkenas jashkenas closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
2  Cakefile
@@ -224,7 +224,7 @@ runTests = (CoffeeScript) ->
# Run every test in the `test` folder, recording failures.
files = fs.readdirSync 'test'
- for file in files when file.match /\.coffee$/i
+ for file in files when file.match /\.(literate)?coffee$/i
currentFile = filename = path.join 'test', file
code = fs.readFileSync filename
7 lib/coffee-script/coffee-script.js
@@ -31,9 +31,14 @@
exports.helpers = require('./helpers');
exports.compile = compile = function(code, options) {
+ var transformedCode, _ref2;
if (options == null) options = {};
try {
- return (parser.parse(lexer.tokenize(code))).compile(options);
+ transformedCode = code;
+ if ((_ref2 = options.filename) != null ? _ref2.match(/\.literatecoffee$/) : void 0) {
+ transformedCode = code.replace(/(^|\n)(\S)/gi, '$1#$2');
+ }
+ return (parser.parse(lexer.tokenize(transformedCode))).compile(options);
} catch (err) {
if (options.filename) {
err.message = "In " + options.filename + ", " + err.message;
8 src/
@@ -30,9 +30,15 @@ exports.helpers = require './helpers'
# Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
# compiler.
+# If the string fulfils any of two literacy criteria, it is considered
+# Literate Coffee.
exports.compile = compile = (code, options = {}) ->
- (parser.parse lexer.tokenize code).compile options
+ transformedCode = code
+ declaresLiteracy = code.indexOf('Literate CoffeeScript\n') == 0
+ if declaresLiteracy or options.filename?.match /\.literatecoffee$/
+ transformedCode = code.replace /(^|\n)(\S)/gi, '$1#$2'
+ (parser.parse lexer.tokenize transformedCode).compile options
catch err
err.message = "In #{options.filename}, #{err.message}" if options.filename
throw err
36 test/literate.literatecoffee
@@ -0,0 +1,36 @@
+Beautiful Literate Programming
+If this file parses at all, that is the test, and it has passed.
+Originally, literate programming did not mean heavily-commented code. However, owing to the System, that is what it evolved to mean.
+Literate programming is supposed to be where code invades the commentary, not lots of comments invading the code.
+ test "If this parses, literate coffee works.", ->
+ eq 'So beautiful, I want to cry.', 'So beautiful, I want to cry.'
+Under this scheme, everything is a comment.
+Except the bits that are indented. If a line does not start with whitespace, it is a comment.
+Everything else is code.
+ test "I parse, therefore I work.", ->
+ eq 4, 100 - 96
+This is how we shall proceed with writing a string reverse in CoffeeScript.
+ reverse = (str) ->
+ rez = ''
+ for chr in str
+ rez = chr + rez
+ rez
+And then we will use it.
+ test "Using a function defined within literate CoffeeScript.", ->
+ eq 'So beautiful, I want to cry.', reverse '.yrc ot tnaw I ,lufituaeb oS'
+See? Wasn't ugly, was it?
+Work on a syntax mode should not be difficult, in my opinion. (Although the only
+syntax things I know how to do are Vim, and even those, not *too* perfectly.)
+That will be all.
Something went wrong with that request. Please try again.