├── test ├── fixtures │ └── good │ │ ├── map.out │ │ ├── option_monad.out │ │ ├── where.out │ │ ├── accessors.out │ │ ├── primitive_types.out │ │ ├── tagged_unions.out │ │ ├── with.out │ │ ├── coercing_native_to_any.out │ │ ├── conditionals.out │ │ ├── deep_matching.out │ │ ├── functions.out │ │ ├── monoid.out │ │ ├── primitive_types.roy │ │ ├── match_expression_single_eval.out │ │ ├── coercing_native_to_any.roy │ │ ├── trace_monad.out │ │ ├── number.out │ │ ├── tarjan.out │ │ ├── object.out │ │ ├── unicode.out │ │ ├── accessors.roy │ │ ├── map.roy │ │ ├── object.roy │ │ ├── with.roy │ │ ├── functions.roy │ │ ├── match_expression_single_eval.roy │ │ ├── number.roy │ │ ├── trace_monad.roy │ │ ├── conditionals.roy │ │ ├── tagged_unions.roy │ │ ├── option_monad.roy │ │ ├── deep_matching.roy │ │ ├── unicode.roy │ │ ├── monoid.roy │ │ └── where.roy ├── TarjanSpec.js ├── TypeInferenceSpec.js ├── TypeParserSpec.js └── CompileSpec.js ├── roy ├── examples ├── helloworld.roy ├── node_module.roy ├── gcd.roy ├── stdlib.roy ├── console.roy ├── module.roy ├── funcs.roy ├── option.roy ├── deferredmonad.roy ├── ajaxmonad.roy ├── alias.roy ├── tracemonad.roy ├── types.roy ├── fizzbuzz.roy ├── structural.roy ├── data.roy └── sqrt.lroy ├── .travis.yml ├── site ├── logo.png ├── codemirror2 │ ├── lib │ │ ├── util │ │ │ ├── simple-hint.css │ │ │ ├── dialog.css │ │ │ ├── runmode.js │ │ │ ├── match-highlighter.js │ │ │ ├── overlay.js │ │ │ ├── dialog.js │ │ │ ├── simple-hint.js │ │ │ ├── search.js │ │ │ ├── searchcursor.js │ │ │ ├── javascript-hint.js │ │ │ ├── closetag.js │ │ │ ├── foldcode.js │ │ │ └── formatting.js │ │ └── codemirror.css │ ├── theme │ │ ├── neat.css │ │ ├── elegant.css │ │ ├── cobalt.css │ │ ├── eclipse.css │ │ ├── night.css │ │ ├── monokai.css │ │ ├── rubyblue.css │ │ ├── lesser-dark.css │ │ └── xq-dark.css │ └── mode │ │ ├── roy │ │ └── roy.js │ │ └── javascript │ │ └── index.html └── index.htm ├── misc ├── chrome-extension │ ├── roy_128.png │ ├── roy_16.png │ ├── roy_48.png │ ├── background.htm │ ├── add-compile-listener.js │ ├── manifest.json │ ├── autoroy.css │ └── autoroy.js └── lroy-to-pandoc-html.sh ├── .npmignore ├── .gitignore ├── .gitmodules ├── docs └── guide │ ├── index.rst │ ├── introduction.rst │ ├── types.rst │ ├── make.bat │ ├── Makefile │ └── conf.py ├── Makefile ├── AUTHORS ├── package.json ├── LICENSE ├── src ├── modules.js ├── prettyprint.js ├── tarjan.js ├── typegrammar.js ├── lexer.js ├── types.js ├── nodes.js └── freeVariables.js ├── README.md ├── lib └── prelude.roy ├── grunt.js └── CHANGELOG.md /test/fixtures/good/map.out: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/good/option_monad.out: -------------------------------------------------------------------------------- 1 | 6 2 | -------------------------------------------------------------------------------- /test/fixtures/good/where.out: -------------------------------------------------------------------------------- 1 | 6 2 | 2 3 | -------------------------------------------------------------------------------- /test/fixtures/good/accessors.out: -------------------------------------------------------------------------------- 1 | 2 2 | 42 3 | -------------------------------------------------------------------------------- /test/fixtures/good/primitive_types.out: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /test/fixtures/good/tagged_unions.out: -------------------------------------------------------------------------------- 1 | x is B 2 | -------------------------------------------------------------------------------- /test/fixtures/good/with.out: -------------------------------------------------------------------------------- 1 | Hello, World 2 | -------------------------------------------------------------------------------- /test/fixtures/good/coercing_native_to_any.out: -------------------------------------------------------------------------------- 1 | ok 2 | -------------------------------------------------------------------------------- /roy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./src/node-repl')(); 3 | -------------------------------------------------------------------------------- /test/fixtures/good/conditionals.out: -------------------------------------------------------------------------------- 1 | ok 2 | ok 3 | ok 4 | ok 5 | -------------------------------------------------------------------------------- /examples/helloworld.roy: -------------------------------------------------------------------------------- 1 | // Comment 2 | console.log "Hello world" 3 | -------------------------------------------------------------------------------- /test/fixtures/good/deep_matching.out: -------------------------------------------------------------------------------- 1 | 0 2 | 100 3 | 100 4 | 0 5 | -------------------------------------------------------------------------------- /test/fixtures/good/functions.out: -------------------------------------------------------------------------------- 1 | Hello world 2 | Hello world 3 | -------------------------------------------------------------------------------- /test/fixtures/good/monoid.out: -------------------------------------------------------------------------------- 1 | Hello! 2 | TESTTEST 3 | 4 4 | 2 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - 0.6 5 | -------------------------------------------------------------------------------- /test/fixtures/good/primitive_types.roy: -------------------------------------------------------------------------------- 1 | let a = 1 2 | console.log a 3 | -------------------------------------------------------------------------------- /site/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puffnfresh/roy/HEAD/site/logo.png -------------------------------------------------------------------------------- /test/fixtures/good/match_expression_single_eval.out: -------------------------------------------------------------------------------- 1 | This should only appear once 2 | 1 3 | -------------------------------------------------------------------------------- /test/fixtures/good/coercing_native_to_any.roy: -------------------------------------------------------------------------------- 1 | let x: Number = console 2 | console.log "ok" 3 | -------------------------------------------------------------------------------- /test/fixtures/good/trace_monad.out: -------------------------------------------------------------------------------- 1 | Binding: 1 2 | Binding: 3 3 | Binding: 4 4 | Return: 10 5 | 10 6 | -------------------------------------------------------------------------------- /test/fixtures/good/number.out: -------------------------------------------------------------------------------- 1 | 1e+32 2 | 1e+32 3 | 1e+32 4 | 1e+32 5 | 1e-32 6 | 1e-32 7 | 1000 8 | 0.001 9 | -------------------------------------------------------------------------------- /test/fixtures/good/tarjan.out: -------------------------------------------------------------------------------- 1 | f 2 | g 3 | 4 | c 5 | d 6 | h 7 | 8 | a 9 | b 10 | e 11 | 12 | -------------------------------------------------------------------------------- /examples/node_module.roy: -------------------------------------------------------------------------------- 1 | import "./structural" 2 | 3 | console.log (structural.obj.x + structural.obj.y) 4 | -------------------------------------------------------------------------------- /misc/chrome-extension/roy_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puffnfresh/roy/HEAD/misc/chrome-extension/roy_128.png -------------------------------------------------------------------------------- /misc/chrome-extension/roy_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puffnfresh/roy/HEAD/misc/chrome-extension/roy_16.png -------------------------------------------------------------------------------- /misc/chrome-extension/roy_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puffnfresh/roy/HEAD/misc/chrome-extension/roy_48.png -------------------------------------------------------------------------------- /test/fixtures/good/object.out: -------------------------------------------------------------------------------- 1 | {} 2 | { a: 1 } 3 | { b: 10 } 4 | { c: 11 } 5 | { '4': 100 } 6 | {} 7 | { test: 'this' } 8 | -------------------------------------------------------------------------------- /examples/gcd.roy: -------------------------------------------------------------------------------- 1 | let gcd a b = 2 | if b == 0 then 3 | a 4 | else 5 | gcd b (a % b) 6 | 7 | console.log (gcd 49 35) 8 | -------------------------------------------------------------------------------- /test/fixtures/good/unicode.out: -------------------------------------------------------------------------------- 1 | Hello world 2 | Hello world 3 | Binding: 1 4 | Binding: 3 5 | Binding: 4 6 | Return: 10 7 | 10 8 | test 9 | -------------------------------------------------------------------------------- /test/fixtures/good/accessors.roy: -------------------------------------------------------------------------------- 1 | let arr = [1, 2, 3] 2 | console.log (arr @ 1) 3 | 4 | let obj = {answer: 42} 5 | console.log obj.answer 6 | -------------------------------------------------------------------------------- /examples/stdlib.roy: -------------------------------------------------------------------------------- 1 | console.log (id 1) 2 | console.log (odd 2) 3 | 4 | console.log (maybe 0 id None) 5 | 6 | console.log (head (tail (replicate 10 'a'))) 7 | -------------------------------------------------------------------------------- /examples/console.roy: -------------------------------------------------------------------------------- 1 | console.log "One" 2 | console.info "Two" 3 | console.warn "Three" 4 | console.error "Four" 5 | console.trace "Five" 6 | console.assert "Six" 7 | -------------------------------------------------------------------------------- /test/fixtures/good/map.roy: -------------------------------------------------------------------------------- 1 | data List a = Empty | Cons a (List a) 2 | 3 | let map f l = match l 4 | case (Cons x xs) = Cons (f x) (map f xs) 5 | case Empty = Empty 6 | -------------------------------------------------------------------------------- /misc/chrome-extension/background.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | docs/guide/_build/ 4 | examples/*.js 5 | roy.brianmckenna.org 6 | roy.js 7 | roy-min.js 8 | .DS_Store 9 | *~ 10 | *.swp 11 | *.roym 12 | *.js.map 13 | *.sublime* 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/*.js 2 | 3 | node_modules 4 | dist 5 | docs/guide/_build/ 6 | examples/*.js 7 | roy.brianmckenna.org 8 | roy.js 9 | roy-min.js 10 | .DS_Store 11 | *~ 12 | *.swp 13 | *.roym 14 | *.js.map 15 | *.sublime* 16 | -------------------------------------------------------------------------------- /examples/module.roy: -------------------------------------------------------------------------------- 1 | // Roy modules have a descriptor format. 2 | // The demo site manually supplies the 'dom' descriptor. 3 | 4 | import "dom" 5 | 6 | let button = document.getElementById "jsrun" 7 | 8 | console.log (button.getAttribute "value") 9 | -------------------------------------------------------------------------------- /test/fixtures/good/object.roy: -------------------------------------------------------------------------------- 1 | console.log {} 2 | console.log {a: 1} 3 | console.log {'b': 10} 4 | console.log {"c": 11} 5 | console.log {4: 100} 6 | 7 | let x = {} 8 | console.log x 9 | 10 | let y = {'test': 'this'} 11 | console.log y 12 | -------------------------------------------------------------------------------- /examples/funcs.roy: -------------------------------------------------------------------------------- 1 | let print x = 2 | console.log x 3 | 4 | print "Hello" 5 | print 100 6 | print [1, 2, 3] 7 | 8 | let log = console.log 9 | 10 | log "Hello" 11 | log 100 12 | log [1, 2, 3] 13 | 14 | // Lambdas 15 | console.log ((\x -> x + 1) 100) 16 | -------------------------------------------------------------------------------- /test/fixtures/good/with.roy: -------------------------------------------------------------------------------- 1 | // Example of 'with' keyword 2 | let getGreeting x = x.greeting ++ x.spacing ++ x.target 3 | 4 | console.log (getGreeting { 5 | greeting: "Hello" 6 | spacing: ", " 7 | extra: true 8 | } with { 9 | target: "World" 10 | }) 11 | -------------------------------------------------------------------------------- /test/fixtures/good/functions.roy: -------------------------------------------------------------------------------- 1 | let named x = x 2 | let anon = λx → x 3 | 4 | let multiArgs x y z = x 5 | let anonMultiArgs = λx y z -> x 6 | 7 | console.log (named "Hello") (anon "world") 8 | console.log (multiArgs "Hello" "world" "!") (anonMultiArgs "world" "!" "Hello") 9 | -------------------------------------------------------------------------------- /test/fixtures/good/match_expression_single_eval.roy: -------------------------------------------------------------------------------- 1 | data Either a b = 2 | Left a | Right b 3 | 4 | let f = \() -> 5 | console.log "This should only appear once" 6 | Right 1 7 | 8 | match f() 9 | case (Left x) = console.log x 10 | case (Right x) = console.log x 11 | -------------------------------------------------------------------------------- /test/fixtures/good/number.roy: -------------------------------------------------------------------------------- 1 | // Test some large exponents 2 | console.log 1e32 3 | console.log 1E32 4 | console.log 1e+32 5 | console.log 1E+32 6 | console.log 1e-32 7 | console.log 1E-32 8 | 9 | // Test some small exponents 10 | console.log 1E+3 11 | console.log 1E-3 12 | -------------------------------------------------------------------------------- /misc/chrome-extension/add-compile-listener.js: -------------------------------------------------------------------------------- 1 | chrome.extension.onRequest.addListener(function(request, sender, sendResponse) { 2 | var js; 3 | try { 4 | js = roy.compile(request.code).output; 5 | } catch(e) { 6 | js = e.toString(); 7 | } 8 | sendResponse({js: js}); 9 | }); 10 | -------------------------------------------------------------------------------- /test/fixtures/good/trace_monad.roy: -------------------------------------------------------------------------------- 1 | let traceMonad = { 2 | return: λx → 3 | console.log "Return:" x 4 | x 5 | bind: λx f → 6 | console.log "Binding:" x 7 | f x 8 | } 9 | 10 | console.log (do traceMonad 11 | w ← 1 12 | let x = 2 13 | y ← 3 14 | z ← 4 15 | return w + x + y + z 16 | ) 17 | -------------------------------------------------------------------------------- /test/fixtures/good/conditionals.roy: -------------------------------------------------------------------------------- 1 | let x = if 2 > 0 then 2 | "ok" 3 | else 4 | "fail" 5 | 6 | console.log x 7 | 8 | console.log (if 2 > 0 then "ok" else "fail") 9 | console.log (if 3 > 0 then (if 3 < 0 then "fail" else "ok") else "fail") 10 | console.log (if 3 < 0 then "fail" else (if 3 > 0 then "ok" else "fail")) 11 | -------------------------------------------------------------------------------- /test/fixtures/good/tagged_unions.roy: -------------------------------------------------------------------------------- 1 | // Simple tagged unions 2 | 3 | data A = B | C 4 | 5 | let x = B () 6 | 7 | console.log (match x 8 | case B = "x is B" 9 | case C = "x is C" 10 | ) 11 | 12 | // Multiple argument unions 13 | 14 | data XYZ a b c = Z a b c | X b c | Y c 15 | 16 | Z 1 true "three" 17 | X 1 true 18 | Y 1 19 | -------------------------------------------------------------------------------- /examples/option.roy: -------------------------------------------------------------------------------- 1 | data Option a = 2 | Some a | None 3 | 4 | let optionMonad = { 5 | return: \x -> 6 | Some x 7 | bind: \x f -> match x 8 | case (Some a) = f a 9 | case None = None () 10 | } 11 | 12 | console.log (do optionMonad 13 | x <- Some 1 14 | let y = 2 15 | z <- Some 3 16 | return x + y + z 17 | ) 18 | -------------------------------------------------------------------------------- /misc/lroy-to-pandoc-html.sh: -------------------------------------------------------------------------------- 1 | # Usage: ./misc/lroy-to-pandoc-html.sh examples/sqrt.lroy 2 | # 3 | # Generates a nice-looking .htm document from a specified .lroy file. 4 | # 5 | # Uses Kevin Burke's Markdown.css 6 | 7 | pandoc -s -5 -f markdown -c "http://kevinburke.bitbucket.org/markdowncss/markdown.css" $1 -o $(dirname $1)/$(basename $1 lroy)htm 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "misc/roy.tmbundle"] 2 | path = misc/roy.tmbundle 3 | url = https://github.com/paulmillr/roy.tmbundle.git 4 | [submodule "misc/roy-mode"] 5 | path = misc/roy-mode 6 | url = https://github.com/folone/roy-mode.git 7 | [submodule "misc/vim-roy"] 8 | path = misc/vim-roy 9 | url = https://github.com/jb55/Vim-Roy.git 10 | -------------------------------------------------------------------------------- /docs/guide/index.rst: -------------------------------------------------------------------------------- 1 | Poignant Guide To Learn You a Roy, The Hard Way 2 | =============================================== 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | introduction 10 | types 11 | 12 | Indices and tables 13 | ================== 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | -------------------------------------------------------------------------------- /examples/deferredmonad.roy: -------------------------------------------------------------------------------- 1 | let deferred = { 2 | return: \x -> 3 | let d = $.Deferred () 4 | d.resolve x 5 | d.promise () 6 | bind: \x f -> x.pipe f 7 | } 8 | 9 | let v = do deferred 10 | hello <- $.ajax 'examples/helloworld.roy' 11 | alias <- $.ajax 'examples/alias.roy' 12 | return (hello ++ alias) 13 | 14 | v.done console.log 15 | -------------------------------------------------------------------------------- /examples/ajaxmonad.roy: -------------------------------------------------------------------------------- 1 | type Request = {url: String, payload: String} 2 | 3 | let ajaxRequest = { 4 | return: \x -> x 5 | bind: \(x : Request) f -> 6 | $.get x.url x.payload f 7 | } 8 | 9 | let v = (do ajaxRequest 10 | value <- {url: '/examples/helloworld.roy', payload: 'stuff'} 11 | console.log value 12 | return value 13 | ) 14 | 15 | console.log v 16 | -------------------------------------------------------------------------------- /test/fixtures/good/option_monad.roy: -------------------------------------------------------------------------------- 1 | data Option a = Some a | None 2 | 3 | let optionMonad = { 4 | return: λx → 5 | Some x 6 | bind: λx f → match x 7 | case (Some a) = f a 8 | case None = None 9 | } 10 | 11 | let m = (do optionMonad 12 | x ← Some 1 13 | let y = 2 14 | z ← Some 3 15 | return x + y + z 16 | ) 17 | 18 | match m 19 | case (Some x) = console.log x 20 | -------------------------------------------------------------------------------- /examples/alias.roy: -------------------------------------------------------------------------------- 1 | // Alias for a primitive type 2 | type Int = Number 3 | 4 | // Alias for a structural type 5 | type Person = {name: String, age: Int} 6 | 7 | // Function with aliased type annotation 8 | let personName (p: Person) = p.name 9 | console.log (personName {name: "Brian", age: 21}) 10 | 11 | // Value with aliased type annotation 12 | let ben: Person = {name: "Ben", age: 18} 13 | let anyName a = a.name 14 | console.log (anyName ben) 15 | -------------------------------------------------------------------------------- /examples/tracemonad.roy: -------------------------------------------------------------------------------- 1 | // Caution: this example horribly breaks the monad laws. 2 | // See Hacker News for more details: 3 | // http://news.ycombinator.com/item?id=3277360 4 | 5 | let traceMonad = { 6 | return: \x -> 7 | console.log "Return:" x 8 | x 9 | bind: \x f -> 10 | console.log "Binding:" x 11 | f x 12 | } 13 | 14 | console.log (do traceMonad 15 | w <- 1 16 | let x = 2 17 | y <- 3 18 | z <- 4 19 | return w + x + y + z 20 | ) 21 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/simple-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-completions { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 6 | -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 7 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 8 | } 9 | .CodeMirror-completions select { 10 | background: #fafafa; 11 | outline: none; 12 | border: none; 13 | padding: 0; 14 | margin: 0; 15 | font-family: monospace; 16 | } 17 | -------------------------------------------------------------------------------- /examples/types.roy: -------------------------------------------------------------------------------- 1 | // Strong 2 | console.log 40 + 2 3 | 4 | // Won't compile: 5 | // console.log "40" + 2 6 | 7 | // Explicit 8 | let f x: Number = x 9 | 10 | console.log (f 100) 11 | 12 | // Won't compile: 13 | // console.log (f "100") 14 | 15 | type Person = {firstName: String, lastName: String} 16 | let getName (x: Person) = x.firstName ++ " " ++ x.lastName 17 | 18 | console.log (getName {firstName: "Brian", lastName: "McKenna"}) 19 | 20 | // Won't compile: 21 | // console.log (getName {}) 22 | -------------------------------------------------------------------------------- /examples/fizzbuzz.roy: -------------------------------------------------------------------------------- 1 | let isFizzBuzz x = 2 | if x % 15 == 0 then 3 | console.log "FizzBuzz" 4 | else 5 | isBuzz x 6 | 7 | let isBuzz x = 8 | if x % 5 == 0 then 9 | console.log "Buzz" 10 | else 11 | isFizz x 12 | 13 | let isFizz x = 14 | if x % 3 == 0 then 15 | console.log "Fizz" 16 | else 17 | console.log x 18 | 19 | let fizzBuzz x y = 20 | if x < y + 1 then 21 | isFizzBuzz x 22 | fizzBuzz (x + 1) y 23 | else 24 | console.log "end" 25 | 26 | fizzBuzz 1 25 27 | -------------------------------------------------------------------------------- /test/fixtures/good/deep_matching.roy: -------------------------------------------------------------------------------- 1 | data Either a b = Left a | Right b 2 | 3 | let xor e = match e 4 | case (Left (Left n)) = 0 5 | case (Left (Right n)) = n 6 | case (Right (Left n)) = n 7 | case (Right (Right n)) = 0 8 | 9 | console.log (xor (Left (Left 100))) 10 | console.log (xor (Left (Right 100))) 11 | console.log (xor (Right (Left 100))) 12 | console.log (xor (Right (Right 100))) 13 | 14 | data XX = X 15 | data YY = Y XX 16 | let yy = Y (X ()) 17 | let idXX (x:XX) = x 18 | match yy 19 | case (Y x) = idXX x 20 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/dialog.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-dialog { 2 | position: relative; 3 | } 4 | 5 | .CodeMirror-dialog > div { 6 | position: absolute; 7 | top: 0; left: 0; right: 0; 8 | background: white; 9 | border-bottom: 1px solid #eee; 10 | z-index: 15; 11 | padding: .1em .8em; 12 | overflow: hidden; 13 | color: #333; 14 | } 15 | 16 | .CodeMirror-dialog input { 17 | border: none; 18 | outline: none; 19 | background: transparent; 20 | width: 20em; 21 | color: inherit; 22 | font-family: monospace; 23 | } 24 | -------------------------------------------------------------------------------- /site/codemirror2/theme/neat.css: -------------------------------------------------------------------------------- 1 | .cm-s-neat span.cm-comment { color: #a86; } 2 | .cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; } 3 | .cm-s-neat span.cm-string { color: #a22; } 4 | .cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; } 5 | .cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; } 6 | .cm-s-neat span.cm-variable { color: black; } 7 | .cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; } 8 | .cm-s-neat span.cm-meta {color: #555;} 9 | .cm-s-neat span.cm-link { color: #3a3; } 10 | -------------------------------------------------------------------------------- /misc/chrome-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rome - Roy Compiler", 3 | "version": "0.1", 4 | "manifest_version": 2, 5 | "description": "Compiles and executes local Roy files.", 6 | 7 | "icons": { "16": "roy_16.png", 8 | "48": "roy_48.png", 9 | "128": "roy_128.png" }, 10 | "background": { "page": "background.htm" }, 11 | "content_scripts": [ 12 | { 13 | "matches": ["file:///*/*.roy"], 14 | "js": ["autoroy.js"], 15 | "css": ["autoroy.css"], 16 | "run_at": "document_idle", 17 | "all_frames": true 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /test/fixtures/good/unicode.roy: -------------------------------------------------------------------------------- 1 | let named x = x 2 | let anon = \x -> x 3 | 4 | let multiArgs x y z = x 5 | let anonMultiArgs = \x y z -> x 6 | 7 | console.log (named "Hello") (anon "world") 8 | console.log (multiArgs "Hello" "world" "!") (anonMultiArgs "world" "!" "Hello") 9 | 10 | 11 | let traceMonad = { 12 | return: \x -> 13 | console.log "Return:" x 14 | x 15 | bind: \x f -> 16 | console.log "Binding:" x 17 | f x 18 | } 19 | 20 | console.log (do traceMonad 21 | w <- 1 22 | let x = 2 23 | y <- 3 24 | z <- 4 25 | return w + x + y + z 26 | ) 27 | 28 | let тест = "test" 29 | console.log тест 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: deps site extension lint test 2 | 3 | all: 4 | ./node_modules/.bin/grunt 5 | 6 | deps: 7 | npm install 8 | npm prune 9 | 10 | site: all 11 | [ -e roy.brianmckenna.org ] || mkdir roy.brianmckenna.org 12 | cp -r site/* roy.brianmckenna.org/ 13 | cp -r examples roy.brianmckenna.org/ 14 | cp node_modules/underscore/underscore-min.js roy.brianmckenna.org/ 15 | cp package.json roy.brianmckenna.org/ 16 | cp roy-min.js roy.brianmckenna.org/ 17 | 18 | extension: 19 | cp roy-min.js misc/chrome-extension/ 20 | 21 | # Tests 22 | 23 | lint: 24 | ./node_modules/.bin/grunt lint 25 | 26 | test: 27 | ./node_modules/.bin/grunt jasmine 28 | -------------------------------------------------------------------------------- /site/codemirror2/theme/elegant.css: -------------------------------------------------------------------------------- 1 | .cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom {color: #762;} 2 | .cm-s-elegant span.cm-comment {color: #262; font-style: italic; line-height: 1em;} 3 | .cm-s-elegant span.cm-meta {color: #555; font-style: italic; line-height: 1em;} 4 | .cm-s-elegant span.cm-variable {color: black;} 5 | .cm-s-elegant span.cm-variable-2 {color: #b11;} 6 | .cm-s-elegant span.cm-qualifier {color: #555;} 7 | .cm-s-elegant span.cm-keyword {color: #730;} 8 | .cm-s-elegant span.cm-builtin {color: #30a;} 9 | .cm-s-elegant span.cm-error {background-color: #fdd;} 10 | .cm-s-elegant span.cm-link {color: #762;} 11 | -------------------------------------------------------------------------------- /misc/chrome-extension/autoroy.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | h1, input { 6 | margin: 0; 7 | font-weight: normal; 8 | margin: 0.62em 0; 9 | } 10 | 11 | h1 { 12 | text-align: right; 13 | font-size: 0.62em; 14 | } 15 | 16 | input { 17 | padding: 3em; 18 | } 19 | 20 | textarea { 21 | width: 100%; 22 | } 23 | 24 | textarea, pre { 25 | height: 14em; 26 | padding: 0.5em; 27 | background: #FCFCFC; 28 | color: #030303; 29 | margin: 0; 30 | border: 1px solid; 31 | } 32 | 33 | pre { 34 | overflow: auto; 35 | } 36 | 37 | textarea, code { 38 | font-family: Consolas, monospace; 39 | font-size: 1em; 40 | } 41 | -------------------------------------------------------------------------------- /test/fixtures/good/monoid.roy: -------------------------------------------------------------------------------- 1 | typeclass Monoid #a { 2 | append: Function(#a, #a, #a) 3 | empty: #a 4 | } 5 | 6 | instance stringMonoid = Monoid String { 7 | append: \x y -> x ++ y 8 | empty: "" 9 | } 10 | 11 | instance productMonoid = Monoid {product: Number} { 12 | append: \x y -> {product: x.product * y.product} 13 | empty: {product: 1} 14 | } 15 | 16 | instance sumMonoid = Monoid {sum: Number} { 17 | append: \x y -> {sum: x.sum + y.sum} 18 | empty: {sum: 0} 19 | } 20 | 21 | let f x = append (append x empty) x 22 | let g x = f x 23 | 24 | console.log (append (append "Hello!" empty) empty) 25 | 26 | console.log (g "TEST") 27 | console.log (g (append {product: 2} empty)).product 28 | console.log (g {sum: 1}).sum 29 | -------------------------------------------------------------------------------- /test/fixtures/good/where.roy: -------------------------------------------------------------------------------- 1 | let f x = 2 | let r: Number = g 1 3 | r 4 | where 5 | g y = 6 | if (x == 0) then 7 | h y 8 | else 9 | x + y + (z ()) 10 | where 11 | z () = 3 12 | h y = 13 | if (x == 1) then 14 | g y 15 | else 16 | x * y 17 | 18 | console.log (f 2) 19 | 20 | let g x = 21 | let zero = Zero () 22 | evenToNumber (SuccEven (SuccOdd zero)) 23 | where 24 | evenToNumber y = match y 25 | case Zero = if id true then id 0 else id 0 26 | case (SuccEven odd) = (oddToNumber odd) + 1 27 | oddToNumber z = match z 28 | case (SuccOdd even) = (evenToNumber even) + 1 29 | id x = x 30 | data Even = Zero | SuccEven Odd 31 | data Odd = SuccOdd Even 32 | 33 | console.log (g 0) 34 | -------------------------------------------------------------------------------- /examples/structural.roy: -------------------------------------------------------------------------------- 1 | let obj = {x: 1, y: 2, t: "test"} 2 | 3 | let getX a = a.x + 2 4 | 5 | console.log (getX obj) 6 | console.log (obj.x + obj.y) 7 | 8 | let plusXY a = a.x + a.y 9 | console.log (plusXY obj) 10 | console.log (plusXY {x: 2, y: 1}) 11 | 12 | let makeXY x y = {x: x, y: y} 13 | console.log (plusXY (makeXY 1 2)) 14 | console.log (makeXY 1 2) 15 | 16 | // Won't compile 17 | // plusXY {x: 1, z: 2} 18 | 19 | console.log ((\obj -> obj.a.a + obj.a.b) {a: {a: 1, b: 2}}) 20 | 21 | let makeFun a = {f: \b -> a.x + b.y} 22 | console.log ((makeFun {x: 1}).f {y: 2}) 23 | 24 | // Example of 'with' keyword 25 | let getGreeting x = x.greeting ++ x.spacing ++ x.target 26 | 27 | console.log (getGreeting { 28 | greeting: "Hello" 29 | spacing: ", " 30 | extra: true 31 | } with { 32 | target: "World" 33 | }) 34 | 35 | export obj 36 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # List of contributors in alphabetical order. 2 | # If you'd like to use your real name, email and/or website, please edit this file to do so. 3 | # Format is: Name (website) 4 | 5 | afcowie 6 | alsonkemp 7 | alvivi 8 | Brian McKenna (http://brianmckenna.org/) 9 | brow 10 | Constellation 11 | DamonOehlman 12 | dbousamra 13 | esehara 14 | gregwebs 15 | Hardy Jones (https://github.com/joneshf) 16 | i80and 17 | Bill Casarin (https://github.com/jb55) 18 | jedws 19 | joseanpg 20 | kasperlanger 21 | miikka 22 | mpenet 23 | nickmeharry 24 | non 25 | paulmillr 26 | Alexander Solovyov (https://solovyov.net/) 27 | raimohanska 28 | rehno-lindeque 29 | Richard Feldman (http://rtfeldman.com) 30 | stew 31 | taku0 32 | tokland 33 | twopoint718 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roy", 3 | "description": "Small functional language that compiles to JavaScript", 4 | "keywords": [ 5 | "javascript", 6 | "language", 7 | "roy", 8 | "compiler" 9 | ], 10 | "author": "Brian McKenna (http://brianmckenna.org/)", 11 | "version": "0.2.2", 12 | "engines": { 13 | "node": ">=0.4.0" 14 | }, 15 | "main": "src/compile.js", 16 | "bin": { 17 | "roy": "./roy" 18 | }, 19 | "scripts": { 20 | "prepublish": "node_modules/.bin/grunt jison", 21 | "test": "node_modules/.bin/grunt jison lint jasmine" 22 | }, 23 | "homepage": "http://roy.brianmckenna.org/", 24 | "dependencies": { 25 | "escodegen": "0.0.22", 26 | "source-map": "0.1.8", 27 | "underscore": "1.5.2", 28 | "unicode-categories": "0.9.1" 29 | }, 30 | "devDependencies": { 31 | "jison": "0.2.7", 32 | "grunt": "0.3.15", 33 | "commonjs-everywhere": "~0.8.0", 34 | "jasmine-node": "1.5.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /misc/chrome-extension/autoroy.js: -------------------------------------------------------------------------------- 1 | function updateOutput() { 2 | chrome.extension.sendRequest({ 3 | code: document.getElementById('input').value 4 | }, function(response) { 5 | document.getElementById('output').innerText = response.js; 6 | }); 7 | } 8 | 9 | function runOutput() { 10 | var script = document.createElement('script'); 11 | script.type= 'text/javascript'; 12 | script.innerHTML = document.getElementById('output').innerText; 13 | document.body.appendChild(script); 14 | } 15 | 16 | var source = document.body.innerText; 17 | document.body.innerHTML = '

Roy

JavaScript

'; 18 | document.getElementById('input').value = source; 19 | document.getElementById('compile').addEventListener('click', updateOutput); 20 | document.getElementById('run').addEventListener('click', runOutput); 21 | updateOutput(); 22 | -------------------------------------------------------------------------------- /examples/data.roy: -------------------------------------------------------------------------------- 1 | data Maybe a = 2 | Some a | None 3 | 4 | let none = None () 5 | 6 | let printSomething m = match m 7 | case (Some x) = console.log x 8 | case None = console.log "Empty" 9 | 10 | printSomething (Some 1) 11 | printSomething none 12 | 13 | data Either a b = 14 | Left a | Right b 15 | 16 | let printString (s: String) = 17 | console.log s 18 | 19 | let printResult e = match e 20 | case (Left x) = console.log x 21 | case (Right x) = printString x 22 | 23 | printResult (Left 10) 24 | printResult (Right "Error") 25 | 26 | // Won't compile: 27 | // printResult (Right 10) 28 | 29 | data Bool = 30 | True | False 31 | 32 | let getBool b ifTrue ifFalse = match b 33 | case True = ifTrue 34 | case False = ifFalse 35 | 36 | console.log (getBool (True ()) 1 2) 37 | console.log (getBool (False ()) 1 2) 38 | 39 | data MyList a = Empty | Many a (MyList a) 40 | 41 | let empty = Empty () 42 | console.log (Many true (Many true empty)) 43 | // Won't compile: 44 | // console.log (Many "TEST" (Many true Empty)) 45 | -------------------------------------------------------------------------------- /site/codemirror2/mode/roy/roy.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("roy", function(config, parserConfig) { 2 | return { 3 | token: function(stream, state) { 4 | var token, sliced = stream.string.slice(stream.pos); 5 | try { 6 | token = roy.lexer.tokenise(sliced)[0]; 7 | if(!token[1].length) { 8 | stream.next(); 9 | return; 10 | } 11 | stream.pos += sliced.match(/\s*/)[0].length + token[1].length; 12 | } catch(e) { 13 | stream.next(); 14 | return; 15 | } 16 | 17 | switch(token[0]) { 18 | case 'LET': 19 | case 'IF': 20 | case 'THEN': 21 | case 'ELSE': 22 | case 'DATA': 23 | case 'TYPE': 24 | case 'MATCH': 25 | case 'CASE': 26 | case 'DO': 27 | case 'RETURN': 28 | case 'MACRO': 29 | case 'WITH': 30 | case 'WHERE': 31 | return 'keyword'; 32 | case 'BOOLEAN': 33 | return 'builtin'; 34 | } 35 | return token[0].toLowerCase(); 36 | } 37 | }; 38 | }); 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Brian McKenna 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /site/codemirror2/theme/cobalt.css: -------------------------------------------------------------------------------- 1 | .cm-s-cobalt { background: #002240; color: white; } 2 | .cm-s-cobalt div.CodeMirror-selected { background: #b36539 !important; } 3 | .cm-s-cobalt .CodeMirror-gutter { background: #002240; border-right: 1px solid #aaa; } 4 | .cm-s-cobalt .CodeMirror-gutter-text { color: #d0d0d0; } 5 | .cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; } 6 | 7 | .cm-s-cobalt span.cm-comment { color: #08f; } 8 | .cm-s-cobalt span.cm-atom { color: #845dc4; } 9 | .cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; } 10 | .cm-s-cobalt span.cm-keyword { color: #ffee80; } 11 | .cm-s-cobalt span.cm-string { color: #3ad900; } 12 | .cm-s-cobalt span.cm-meta { color: #ff9d00; } 13 | .cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; } 14 | .cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def { color: white; } 15 | .cm-s-cobalt span.cm-error { color: #9d1e15; } 16 | .cm-s-cobalt span.cm-bracket { color: #d8d8d8; } 17 | .cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; } 18 | .cm-s-cobalt span.cm-link { color: #845dc4; } 19 | -------------------------------------------------------------------------------- /site/codemirror2/theme/eclipse.css: -------------------------------------------------------------------------------- 1 | .cm-s-eclipse span.cm-meta {color: #FF1717;} 2 | .cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } 3 | .cm-s-eclipse span.cm-atom {color: #219;} 4 | .cm-s-eclipse span.cm-number {color: #164;} 5 | .cm-s-eclipse span.cm-def {color: #00f;} 6 | .cm-s-eclipse span.cm-variable {color: black;} 7 | .cm-s-eclipse span.cm-variable-2 {color: #0000C0;} 8 | .cm-s-eclipse span.cm-variable-3 {color: #0000C0;} 9 | .cm-s-eclipse span.cm-property {color: black;} 10 | .cm-s-eclipse span.cm-operator {color: black;} 11 | .cm-s-eclipse span.cm-comment {color: #3F7F5F;} 12 | .cm-s-eclipse span.cm-string {color: #2A00FF;} 13 | .cm-s-eclipse span.cm-string-2 {color: #f50;} 14 | .cm-s-eclipse span.cm-error {color: #f00;} 15 | .cm-s-eclipse span.cm-qualifier {color: #555;} 16 | .cm-s-eclipse span.cm-builtin {color: #30a;} 17 | .cm-s-eclipse span.cm-bracket {color: #cc7;} 18 | .cm-s-eclipse span.cm-tag {color: #170;} 19 | .cm-s-eclipse span.cm-attribute {color: #00c;} 20 | .cm-s-eclipse span.cm-link {color: #219;} 21 | 22 | .cm-s-eclipse .CodeMirror-matchingbracket { 23 | border:1px solid grey; 24 | color:black !important;; 25 | } 26 | -------------------------------------------------------------------------------- /site/codemirror2/theme/night.css: -------------------------------------------------------------------------------- 1 | /* Loosely based on the Midnight Textmate theme */ 2 | 3 | .cm-s-night { background: #0a001f; color: #f8f8f8; } 4 | .cm-s-night div.CodeMirror-selected { background: #a8f !important; } 5 | .cm-s-night .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; } 6 | .cm-s-night .CodeMirror-gutter-text { color: #f8f8f8; } 7 | .cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; } 8 | 9 | .cm-s-night span.cm-comment { color: #6900a1; } 10 | .cm-s-night span.cm-atom { color: #845dc4; } 11 | .cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; } 12 | .cm-s-night span.cm-keyword { color: #599eff; } 13 | .cm-s-night span.cm-string { color: #37f14a; } 14 | .cm-s-night span.cm-meta { color: #7678e2; } 15 | .cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; } 16 | .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; } 17 | .cm-s-night span.cm-error { color: #9d1e15; } 18 | .cm-s-night span.cm-bracket { color: #8da6ce; } 19 | .cm-s-night span.cm-comment { color: #6900a1; } 20 | .cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; } 21 | .cm-s-night span.cm-link { color: #845dc4; } 22 | -------------------------------------------------------------------------------- /examples/sqrt.lroy: -------------------------------------------------------------------------------- 1 | % Roy - Babylonian Square Root 2 | 3 | This is a Markdown file. It's also literate Roy code! 4 | 5 | We're going to define a `sqrt` function using the 6 | [Babylonian method][] for approximation. 7 | 8 | let sqrt n = 9 | 10 | Making a good initial guess helps reduce the amount of 11 | iterations until convergence. One method is to find the 12 | highest-bit (`b`) and use `2^(b/2)`. 13 | 14 | let highestBit n i = 15 | if (1 << i) > n then 16 | i 17 | else 18 | highestBit n (i + 1) 19 | 20 | let guess = 1 << ((highestBit n 1) / 2) 21 | 22 | Iteratively average the guess and the number divided by the guess 23 | until convergence with a cut-off of 0.001. The guessing is started 24 | from the initial value, calculated above. 25 | 26 | let nextGuess g = 27 | if (Math.abs (g - (n / g))) < 0.001 then 28 | g 29 | else 30 | nextGuess ((g + (n / g)) / 2) 31 | 32 | nextGuess guess 33 | 34 | Now we can calculate the square root of 314 and print it. 35 | 36 | console.log (sqrt 314) 37 | 38 | We should get an answer of around 17.720. 39 | 40 | [Babylonian method]: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method 41 | -------------------------------------------------------------------------------- /site/codemirror2/theme/monokai.css: -------------------------------------------------------------------------------- 1 | /* Based on Sublime Text's Monokai theme */ 2 | 3 | .cm-s-monokai {background: #272822; color: #f8f8f2;} 4 | .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} 5 | .cm-s-monokai .CodeMirror-gutter {background: #272822; border-right: 0px;} 6 | .cm-s-monokai .CodeMirror-gutter-text {color: #d0d0d0;} 7 | .cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;} 8 | 9 | .cm-s-monokai span.cm-comment {color: #75715e;} 10 | .cm-s-monokai span.cm-atom {color: #ae81ff;} 11 | .cm-s-monokai span.cm-number {color: #ae81ff;} 12 | 13 | .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #a6e22e;} 14 | .cm-s-monokai span.cm-keyword {color: #f92672;} 15 | .cm-s-monokai span.cm-string {color: #e6db74;} 16 | 17 | .cm-s-monokai span.cm-variable {color: #a6e22e;} 18 | .cm-s-monokai span.cm-variable-2 {color: #9effff;} 19 | .cm-s-monokai span.cm-def {color: #fd971f;} 20 | .cm-s-monokai span.cm-error {background: #f92672; color: #f8f8f0;} 21 | .cm-s-monokai span.cm-bracket {color: #f8f8f2;} 22 | .cm-s-monokai span.cm-tag {color: #f92672;} 23 | .cm-s-monokai span.cm-link {color: #ae81ff;} 24 | 25 | .cm-s-monokai .CodeMirror-matchingbracket { 26 | text-decoration: underline; 27 | color: white !important; 28 | } 29 | -------------------------------------------------------------------------------- /site/codemirror2/theme/rubyblue.css: -------------------------------------------------------------------------------- 1 | .cm-s-rubyblue { font:13px/1.4em Trebuchet, Verdana, sans-serif; } /* - customized editor font - */ 2 | 3 | .cm-s-rubyblue { background: #112435; color: white; } 4 | .cm-s-rubyblue div.CodeMirror-selected { background: #38566F !important; } 5 | .cm-s-rubyblue .CodeMirror-gutter { background: #1F4661; border-right: 7px solid #3E7087; min-width:2.5em; } 6 | .cm-s-rubyblue .CodeMirror-gutter-text { color: white; } 7 | .cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white !important; } 8 | 9 | .cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; line-height: 1em; } 10 | .cm-s-rubyblue span.cm-atom { color: #F4C20B; } 11 | .cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; } 12 | .cm-s-rubyblue span.cm-keyword { color: #F0F; } 13 | .cm-s-rubyblue span.cm-string { color: #F08047; } 14 | .cm-s-rubyblue span.cm-meta { color: #F0F; } 15 | .cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; } 16 | .cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def { color: white; } 17 | .cm-s-rubyblue span.cm-error { color: #AF2018; } 18 | .cm-s-rubyblue span.cm-bracket { color: #F0F; } 19 | .cm-s-rubyblue span.cm-link { color: #F4C20B; } 20 | .cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; } 21 | .cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; } 22 | -------------------------------------------------------------------------------- /test/TarjanSpec.js: -------------------------------------------------------------------------------- 1 | describe('tarjan', function(){ 2 | var tarjan = require('../src/tarjan.js'), 3 | stronglyConnectedComponents = tarjan.stronglyConnectedComponents, 4 | _ = require('underscore'); 5 | 6 | it('should identify strongly connected components', function() { 7 | var a = {id: "a"}; 8 | var b = {id: "b"}; 9 | var c = {id: "c"}; 10 | var d = {id: "d"}; 11 | var e = {id: "e"}; 12 | var f = {id: "f"}; 13 | var g = {id: "g"}; 14 | var h = {id: "h"}; 15 | var vertices = [a, b, c, d, e, f, g, h]; 16 | var edges = { 17 | "a": [b], 18 | "b": [c, e, f], 19 | "c": [d, g], 20 | "d": [c, h], 21 | "e": [a, f], 22 | "f": [g], 23 | "g": [f], 24 | "h": [d, g] 25 | }; 26 | 27 | var components = stronglyConnectedComponents({vertices: vertices, edges: edges}); 28 | 29 | var sortedComponents = _.map(components, function(component) { 30 | return _.sortBy(component, function(vertex) { 31 | return vertex.id; 32 | }); 33 | }); 34 | 35 | expect(sortedComponents).toEqual([ 36 | [ 37 | {id: 'f'}, 38 | {id: 'g'} 39 | ], 40 | [ 41 | {id: 'c'}, 42 | {id: 'd'}, 43 | {id: 'h'}, 44 | ], 45 | [ 46 | {id: 'a'}, 47 | {id: 'b'}, 48 | {id: 'e'} 49 | ] 50 | ]); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/runmode.js: -------------------------------------------------------------------------------- 1 | CodeMirror.runMode = function(string, modespec, callback, options) { 2 | var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); 3 | var isNode = callback.nodeType == 1; 4 | var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; 5 | if (isNode) { 6 | var node = callback, accum = [], col = 0; 7 | callback = function(text, style) { 8 | if (text == "\n") { 9 | accum.push("
"); 10 | col = 0; 11 | return; 12 | } 13 | var escaped = ""; 14 | // HTML-escape and replace tabs 15 | for (var pos = 0;;) { 16 | var idx = text.indexOf("\t", pos); 17 | if (idx == -1) { 18 | escaped += CodeMirror.htmlEscape(text.slice(pos)); 19 | col += text.length - pos; 20 | break; 21 | } else { 22 | col += idx - pos; 23 | escaped += CodeMirror.htmlEscape(text.slice(pos, idx)); 24 | var size = tabSize - col % tabSize; 25 | col += size; 26 | for (var i = 0; i < size; ++i) escaped += " "; 27 | pos = idx + 1; 28 | } 29 | } 30 | 31 | if (style) 32 | accum.push("" + escaped + ""); 33 | else 34 | accum.push(escaped); 35 | } 36 | } 37 | var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); 38 | for (var i = 0, e = lines.length; i < e; ++i) { 39 | if (i) callback("\n"); 40 | var stream = new CodeMirror.StringStream(lines[i]); 41 | while (!stream.eol()) { 42 | var style = mode.token(stream, state); 43 | callback(stream.current(), style, i, stream.start); 44 | stream.start = stream.pos; 45 | } 46 | } 47 | if (isNode) 48 | node.innerHTML = accum.join(""); 49 | }; 50 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/match-highlighter.js: -------------------------------------------------------------------------------- 1 | // Define match-highlighter commands. Depends on searchcursor.js 2 | // Use by attaching the following function call to the onCursorActivity event: 3 | //myCodeMirror.matchHighlight(minChars); 4 | // And including a special span.CodeMirror-matchhighlight css class (also optionally a separate one for .CodeMirror-focused -- see demo matchhighlighter.html) 5 | 6 | (function() { 7 | var DEFAULT_MIN_CHARS = 2; 8 | 9 | function MatchHighlightState() { 10 | this.marked = []; 11 | } 12 | function getMatchHighlightState(cm) { 13 | return cm._matchHighlightState || (cm._matchHighlightState = new MatchHighlightState()); 14 | } 15 | 16 | function clearMarks(cm) { 17 | var state = getMatchHighlightState(cm); 18 | for (var i = 0; i < state.marked.length; ++i) 19 | state.marked[i].clear(); 20 | state.marked = []; 21 | } 22 | 23 | function markDocument(cm, className, minChars) { 24 | clearMarks(cm); 25 | minChars = (typeof minChars !== 'undefined' ? minChars : DEFAULT_MIN_CHARS); 26 | if (cm.somethingSelected() && cm.getSelection().length >= minChars) { 27 | var state = getMatchHighlightState(cm); 28 | var query = cm.getSelection(); 29 | cm.operation(function() { 30 | if (cm.lineCount() < 2000) { // This is too expensive on big documents. 31 | for (var cursor = cm.getSearchCursor(query); cursor.findNext();) { 32 | //Only apply matchhighlight to the matches other than the one actually selected 33 | if (!(cursor.from().line === cm.getCursor(true).line && cursor.from().ch === cm.getCursor(true).ch)) 34 | state.marked.push(cm.markText(cursor.from(), cursor.to(), className)); 35 | } 36 | } 37 | }); 38 | } 39 | } 40 | 41 | CodeMirror.defineExtension("matchHighlight", function(className, minChars) { 42 | markDocument(this, className, minChars); 43 | }); 44 | })(); 45 | -------------------------------------------------------------------------------- /src/modules.js: -------------------------------------------------------------------------------- 1 | var lexer = require('./lexer'), 2 | typeparser = require('../lib/typeparser').parser, 3 | nodes = require('./nodes').nodes, 4 | types = require('./types'), 5 | _ = require('underscore'); 6 | 7 | var resolveNodeModule = function(moduleName, filename) { 8 | var path = require('path'); 9 | 10 | // node.js uses a few prefixes to decide where to load from: 11 | // http://nodejs.org/docs/latest/api/all.html#loading_from_node_modules_Folders 12 | var relative = _.any(['/', './', '../'], function(e) { 13 | return moduleName.indexOf(e) === 0; 14 | }); 15 | 16 | if(relative) { 17 | return path.resolve(path.dirname(filename), moduleName); 18 | } else { 19 | var resolved = require.resolve(moduleName); 20 | return path.join(path.dirname(resolved), path.basename(resolved, '.js')); 21 | } 22 | }; 23 | 24 | exports.loadModule = function(moduleName, opts) { 25 | if(!opts.modules) opts.modules = {}; 26 | var source = opts.modules[moduleName] || ''; 27 | 28 | if(!source && opts.nodejs) { 29 | var fs = require('fs'), 30 | targetFile = resolveNodeModule(moduleName, opts.filename) + '.roym'; 31 | 32 | if(fs.existsSync(targetFile)) { 33 | source = fs.readFileSync(targetFile, 'utf8'); 34 | } 35 | } 36 | 37 | var tokens = lexer.tokenise(source); 38 | var moduleTypes = typeparser.parse(tokens); 39 | return moduleTypes; 40 | }; 41 | 42 | exports.exportType = function(arg, env, exported, nodejs) { 43 | var name = arg.value; 44 | exported[name] = env[name]; 45 | if(env[name] instanceof types.TagType) { 46 | return new nodes.Comment("// Exported type: " + name); 47 | } 48 | var scope = nodejs ? "exports" : "this"; 49 | return new nodes.Assignment(new nodes.Access(new nodes.Identifier(scope), new nodes.String(JSON.stringify(name))), arg); 50 | }; 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Roy [![Build Status](https://travis-ci.org/puffnfresh/roy.png?branch=master)](https://travis-ci.org/puffnfresh/roy) 2 | Roy is a small functional language that compiles to JavaScript. It has a few main features: 3 | 4 | * Damas-Hindley-Milner type inference 5 | * Whitespace significant syntax 6 | * Simple tagged unions 7 | * Pattern matching 8 | * Structural typing 9 | * Monad syntax 10 | * Not-horrible JS output 11 | 12 | ## Usage 13 | To compile: 14 | 15 | make deps 16 | make 17 | 18 | To enter a REPL: 19 | 20 | ./roy 21 | 22 | To compile and run a `.roy` file: 23 | 24 | ./roy -r examples/helloworld.roy 25 | 26 | To compile a `.roy` file to `.js`: 27 | 28 | ./roy examples/helloworld.roy 29 | cat examples/helloworld.js 30 | 31 | ## Example 32 | Input (test.roy): 33 | 34 | ```roy 35 | let addTwo n = 36 | n + 2 37 | 38 | console.log (addTwo 40) 39 | ``` 40 | 41 | Output (test.js): 42 | 43 | ```roy 44 | var addTwo = function(n) { 45 | return n + 2; 46 | } 47 | console.log(addTwo(40)) 48 | ``` 49 | 50 | Calling `addTwo "test"` will result in a compile-time error (`addTwo` can only take a Number). 51 | 52 | See the examples directory for more. 53 | 54 | ## License 55 | MIT 56 | 57 | ## Editor Support 58 | * [Vim](https://github.com/jb55/Vim-Roy) 59 | * [Emacs](https://github.com/folone/roy-mode) 60 | * [Chocolat, SublimeText2, TextMate](https://github.com/paulmillr/roy.tmbundle) 61 | * [SublimeText](https://github.com/joneshf/sublime-roy) 62 | 63 | ## Resources 64 | * Roy website: http://roy.brianmckenna.org/ 65 | * Roy Google Group: http://groups.google.com/group/roylang 66 | * Roy docs: http://guide.roylang.org/ 67 | * Roy Twitter: http://twitter.com/roylangjs 68 | * Bitbucket repo: https://bitbucket.org/puffnfresh/roy 69 | * GitHub repo: https://github.com/puffnfresh/roy 70 | * Brian's blog: http://brianmckenna.org/ 71 | * altJS channel: [irc://irc.freenode.net/altJS](irc://irc.freenode.net/#altJS) 72 | * roy channel: [irc://irc.freenode.net/roy](irc://irc.freenode.net/#roy) 73 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/overlay.js: -------------------------------------------------------------------------------- 1 | // Utility function that allows modes to be combined. The mode given 2 | // as the base argument takes care of most of the normal mode 3 | // functionality, but a second (typically simple) mode is used, which 4 | // can override the style of text. Both modes get to parse all of the 5 | // text, but when both assign a non-null style to a piece of code, the 6 | // overlay wins, unless the combine argument was true, in which case 7 | // the styles are combined. 8 | 9 | CodeMirror.overlayParser = function(base, overlay, combine) { 10 | return { 11 | startState: function() { 12 | return { 13 | base: CodeMirror.startState(base), 14 | overlay: CodeMirror.startState(overlay), 15 | basePos: 0, baseCur: null, 16 | overlayPos: 0, overlayCur: null 17 | }; 18 | }, 19 | copyState: function(state) { 20 | return { 21 | base: CodeMirror.copyState(base, state.base), 22 | overlay: CodeMirror.copyState(overlay, state.overlay), 23 | basePos: state.basePos, baseCur: null, 24 | overlayPos: state.overlayPos, overlayCur: null 25 | }; 26 | }, 27 | 28 | token: function(stream, state) { 29 | if (stream.start == state.basePos) { 30 | state.baseCur = base.token(stream, state.base); 31 | state.basePos = stream.pos; 32 | } 33 | if (stream.start == state.overlayPos) { 34 | stream.pos = stream.start; 35 | state.overlayCur = overlay.token(stream, state.overlay); 36 | state.overlayPos = stream.pos; 37 | } 38 | stream.pos = Math.min(state.basePos, state.overlayPos); 39 | if (stream.eol()) state.basePos = state.overlayPos = 0; 40 | 41 | if (state.overlayCur == null) return state.baseCur; 42 | if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur; 43 | else return state.overlayCur; 44 | }, 45 | 46 | indent: function(state, textAfter) { 47 | return base.indent(state.base, textAfter); 48 | }, 49 | electricChars: base.electricChars 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/dialog.js: -------------------------------------------------------------------------------- 1 | // Open simple dialogs on top of an editor. Relies on dialog.css. 2 | 3 | (function() { 4 | function dialogDiv(cm, template) { 5 | var wrap = cm.getWrapperElement(); 6 | var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild); 7 | dialog.className = "CodeMirror-dialog"; 8 | dialog.innerHTML = '
' + template + '
'; 9 | return dialog; 10 | } 11 | 12 | CodeMirror.defineExtension("openDialog", function(template, callback) { 13 | var dialog = dialogDiv(this, template); 14 | var closed = false, me = this; 15 | function close() { 16 | if (closed) return; 17 | closed = true; 18 | dialog.parentNode.removeChild(dialog); 19 | } 20 | var inp = dialog.getElementsByTagName("input")[0]; 21 | if (inp) { 22 | CodeMirror.connect(inp, "keydown", function(e) { 23 | if (e.keyCode == 13 || e.keyCode == 27) { 24 | CodeMirror.e_stop(e); 25 | close(); 26 | me.focus(); 27 | if (e.keyCode == 13) callback(inp.value); 28 | } 29 | }); 30 | inp.focus(); 31 | CodeMirror.connect(inp, "blur", close); 32 | } 33 | return close; 34 | }); 35 | 36 | CodeMirror.defineExtension("openConfirm", function(template, callbacks) { 37 | var dialog = dialogDiv(this, template); 38 | var buttons = dialog.getElementsByTagName("button"); 39 | var closed = false, me = this, blurring = 1; 40 | function close() { 41 | if (closed) return; 42 | closed = true; 43 | dialog.parentNode.removeChild(dialog); 44 | me.focus(); 45 | } 46 | buttons[0].focus(); 47 | for (var i = 0; i < buttons.length; ++i) { 48 | var b = buttons[i]; 49 | (function(callback) { 50 | CodeMirror.connect(b, "click", function(e) { 51 | CodeMirror.e_preventDefault(e); 52 | close(); 53 | if (callback) callback(me); 54 | }); 55 | })(callbacks[i]); 56 | CodeMirror.connect(b, "blur", function() { 57 | --blurring; 58 | setTimeout(function() { if (blurring <= 0) close(); }, 200); 59 | }); 60 | CodeMirror.connect(b, "focus", function() { ++blurring; }); 61 | } 62 | }); 63 | })(); -------------------------------------------------------------------------------- /site/codemirror2/theme/lesser-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | http://lesscss.org/ dark theme 3 | Ported to CodeMirror by Peter Kroon 4 | */ 5 | .CodeMirror{ 6 | line-height: 15px; 7 | } 8 | .cm-s-lesser-dark { 9 | font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Monaco', Courier, monospace !important; 10 | font-size:12px; 11 | } 12 | 13 | .cm-s-lesser-dark { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } 14 | .cm-s-lesser-dark div.CodeMirror-selected {background: #45443B !important;} /* 33322B*/ 15 | .cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; } 16 | .cm-s-lesser-dark .CodeMirror-lines { margin-left:3px; margin-right:3px; }/*editable code holder*/ 17 | 18 | div.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ 19 | 20 | .cm-s-lesser-dark .CodeMirror-gutter { background: #262626; border-right:1px solid #aaa; padding-right:3px; min-width:2.5em; } 21 | .cm-s-lesser-dark .CodeMirror-gutter-text { color: #777; } 22 | 23 | .cm-s-lesser-dark span.cm-keyword { color: #599eff; } 24 | .cm-s-lesser-dark span.cm-atom { color: #C2B470; } 25 | .cm-s-lesser-dark span.cm-number { color: #B35E4D; } 26 | .cm-s-lesser-dark span.cm-def {color: color: white;} 27 | .cm-s-lesser-dark span.cm-variable { color:#D9BF8C; } 28 | .cm-s-lesser-dark span.cm-variable-2 { color: #669199; } 29 | .cm-s-lesser-dark span.cm-variable-3 { color: white; } 30 | .cm-s-lesser-dark span.cm-property {color: #92A75C;} 31 | .cm-s-lesser-dark span.cm-operator {color: #92A75C;} 32 | .cm-s-lesser-dark span.cm-comment { color: #666; } 33 | .cm-s-lesser-dark span.cm-string { color: #BCD279; } 34 | .cm-s-lesser-dark span.cm-string-2 {color: #f50;} 35 | .cm-s-lesser-dark span.cm-meta { color: #738C73; } 36 | .cm-s-lesser-dark span.cm-error { color: #9d1e15; } 37 | .cm-s-lesser-dark span.cm-qualifier {color: #555;} 38 | .cm-s-lesser-dark span.cm-builtin { color: #ff9e59; } 39 | .cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; } 40 | .cm-s-lesser-dark span.cm-tag { color: #669199; } 41 | .cm-s-lesser-dark span.cm-attribute {color: #00c;} 42 | .cm-s-lesser-dark span.cm-header {color: #a0a;} 43 | .cm-s-lesser-dark span.cm-quote {color: #090;} 44 | .cm-s-lesser-dark span.cm-hr {color: #999;} 45 | .cm-s-lesser-dark span.cm-link {color: #00c;} 46 | -------------------------------------------------------------------------------- /lib/prelude.roy: -------------------------------------------------------------------------------- 1 | let id a = a 2 | let flip f a b = f b a 3 | 4 | let not a = 5 | if a then 6 | false 7 | else 8 | true 9 | 10 | let even a = (a % 2) == 0 11 | let odd a = not (even a) 12 | 13 | let succ n = n + 1 14 | let pred n = n - 1 15 | 16 | //let compose f g = λx → f (g x) 17 | //let ∘ = compose 18 | //let elem = 19 | //let ∈ = elem 20 | //let ∉ = not ∘ elem 21 | //let negate n = -n 22 | 23 | // List type 24 | 25 | // data List a = Cons a (List a) | Nil 26 | 27 | // let nil = Nil () 28 | 29 | // let map f l = match l 30 | // case (Cons v r) = Cons (f v) (map f r) 31 | // case Nil = nil 32 | 33 | // let filter f l = match l 34 | // case (Cons v r) = if (f v) then 35 | // Cons v r 36 | // else 37 | // r 38 | // case Nil = nil 39 | 40 | // let head l = match l 41 | // case (Cons v _) = v 42 | 43 | // let tail l = match l 44 | // case (Cons _ r) = r 45 | // case Nil = nil 46 | 47 | // let empty l = match l 48 | // case (Cons a b) = false 49 | // case Nil = true 50 | 51 | // let length l = match l 52 | // case (Cons _ r) = 1 + (length r) 53 | // case Nil = 0 54 | 55 | // let foldl f i l = match l 56 | // case (Cons v r) = foldl f (f i v) r 57 | // case Nil = i 58 | 59 | // let replicate n v = 60 | // if (n == 0) then 61 | // nil 62 | // else 63 | // Cons v (replicate (n - 1) v) 64 | 65 | // let take n l = match l 66 | // case (Cons v r) = if n == 0 then 67 | // nil 68 | // else 69 | // Cons v (take (n - 1) r) 70 | // case Nil = nil 71 | 72 | // Option type 73 | 74 | data Option a = Some a | None 75 | 76 | let maybe n f o = match o 77 | case (Some s) = f s 78 | case None = n 79 | 80 | let optionMonad = { 81 | return: λx → 82 | Some x 83 | bind: λx f → match x 84 | case (Some a) = f a 85 | case None = None 86 | } 87 | 88 | // Either type. 89 | data Either a b = Left a | Right b 90 | 91 | let rightMonad = { 92 | return: λx → Right x 93 | 94 | bind: λx f → match x 95 | case (Left a) = Left a 96 | case (Right a) = f a 97 | } 98 | 99 | let leftMonad = { 100 | return: λx → Left x 101 | 102 | bind: λx f → match x 103 | case (Left a) = f a 104 | case (Right a) = Right a 105 | } 106 | 107 | // Math constants. 108 | let π: Number = Math.PI 109 | let π2 = π * 2 110 | // tauday.com 111 | let τ = π2 112 | -------------------------------------------------------------------------------- /src/prettyprint.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var prettyPrint = function(n) { 4 | return n.accept({ 5 | visitFunction: function() { 6 | return "\\" + _.map(n.args, prettyPrint).join(" ") + " -> " + _.map(n.body, prettyPrint); 7 | }, 8 | visitArg: function() { 9 | return n.name; 10 | }, 11 | visitLet: function() { 12 | return "let " + n.name + " = " + prettyPrint(n.value); 13 | }, 14 | visitCall: function() { 15 | return prettyPrint(n.func) + " " + _.map(n.args, prettyPrint).join(" "); 16 | }, 17 | visitAccess: function() { 18 | return prettyPrint(n.value) + "." + n.property; 19 | }, 20 | visitBinaryGenericOperator: function() { 21 | return [prettyPrint(n.left), n.name, prettyPrint(n.right)].join(" "); 22 | }, 23 | visitBinaryNumberOperator: function() { 24 | return [prettyPrint(n.left), n.name, prettyPrint(n.right)].join(" "); 25 | }, 26 | visitBinaryBooleanOperator: function() { 27 | return [prettyPrint(n.left), n.name, prettyPrint(n.right)].join(" "); 28 | }, 29 | visitBinaryStringOperator: function() { 30 | return [prettyPrint(n.left), n.name, prettyPrint(n.right)].join(" "); 31 | }, 32 | visitComment: function() { 33 | return n.value; 34 | }, 35 | visitIdentifier: function() { 36 | return n.value; 37 | }, 38 | visitNumber: function() { 39 | return n.value; 40 | }, 41 | visitString: function() { 42 | return n.value; 43 | }, 44 | visitBoolean: function() { 45 | return n.value; 46 | }, 47 | visitUnit: function() { 48 | return "()"; 49 | }, 50 | visitArray: function() { 51 | return '[' + _.map(n.values, prettyPrint).join(', ') + ']'; 52 | }, 53 | visitTuple: function() { 54 | return '(' + _.map(n.values, prettyPrint).join(', ') + ')'; 55 | }, 56 | visitObject: function() { 57 | var key; 58 | var pairs = []; 59 | for(key in n.values) { 60 | pairs.push(key + ": " + prettyPrint(n.values[key])); 61 | } 62 | return "{" + pairs.join(", ") + "}"; 63 | } 64 | }); 65 | }; 66 | 67 | exports.prettyPrint = prettyPrint; 68 | -------------------------------------------------------------------------------- /site/codemirror2/theme/xq-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 by MarkLogic Corporation 3 | Author: Mike Brevoort 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | .cm-s-xq-dark { background: #0a001f; color: #f8f8f8; } 24 | .cm-s-xq-dark span.CodeMirror-selected { background: #a8f !important; } 25 | .cm-s-xq-dark .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; } 26 | .cm-s-xq-dark .CodeMirror-gutter-text { color: #f8f8f8; } 27 | .cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white !important; } 28 | 29 | .cm-s-xq-dark span.cm-keyword {color: #FFBD40;} 30 | .cm-s-xq-dark span.cm-atom {color: #6C8CD5;} 31 | .cm-s-xq-dark span.cm-number {color: #164;} 32 | .cm-s-xq-dark span.cm-def {color: #FFF; text-decoration:underline;} 33 | .cm-s-xq-dark span.cm-variable {color: #FFF;} 34 | .cm-s-xq-dark span.cm-variable-2 {color: #EEE;} 35 | .cm-s-xq-dark span.cm-variable-3 {color: #DDD;} 36 | .cm-s-xq-dark span.cm-property {} 37 | .cm-s-xq-dark span.cm-operator {} 38 | .cm-s-xq-dark span.cm-comment {color: gray;} 39 | .cm-s-xq-dark span.cm-string {color: #9FEE00;} 40 | .cm-s-xq-dark span.cm-meta {color: yellow;} 41 | .cm-s-xq-dark span.cm-error {color: #f00;} 42 | .cm-s-xq-dark span.cm-qualifier {color: #FFF700;} 43 | .cm-s-xq-dark span.cm-builtin {color: #30a;} 44 | .cm-s-xq-dark span.cm-bracket {color: #cc7;} 45 | .cm-s-xq-dark span.cm-tag {color: #FFBD40;} 46 | .cm-s-xq-dark span.cm-attribute {color: #FFF700;} 47 | -------------------------------------------------------------------------------- /src/tarjan.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | /** 4 | * Decomposes a graph into strongly connected components and return them. 5 | * 6 | * @param {{vertices: !Array., edges: Object.} 7 | * graph 8 | * Graph to be decomposed. 9 | * vertices are any objects with id. 10 | * edges are map from id to array of vertices. 11 | * @return {!Array.>} An array of strongly connected components. 12 | */ 13 | function stronglyConnectedComponents(graph) { 14 | // Tarjan's strongly connected components algorithm 15 | var index = 0; 16 | var stack = []; 17 | var isInStack = []; 18 | var indices = []; 19 | var smallestReachableIndex = []; 20 | var components = []; 21 | 22 | var visit = function (vertex) { 23 | indices[vertex.id] = index; 24 | smallestReachableIndex[vertex.id] = index; 25 | index += 1; 26 | 27 | stack.push(vertex); 28 | isInStack[vertex.id] = true; 29 | 30 | _.each(graph.edges[vertex.id], function(following) { 31 | if (indices[following.id] === undefined) { 32 | visit(following); 33 | smallestReachableIndex[vertex.id] = 34 | Math.min(smallestReachableIndex[vertex.id], 35 | smallestReachableIndex[following.id]); 36 | } else if (isInStack[following.id]) { 37 | smallestReachableIndex[vertex.id] = 38 | Math.min(smallestReachableIndex[vertex.id], 39 | indices[following.id]); 40 | } 41 | }); 42 | 43 | if (smallestReachableIndex[vertex.id] === indices[vertex.id]) { 44 | var currentComponent = [], 45 | popped; 46 | 47 | do { 48 | popped = stack.pop(); 49 | 50 | isInStack[popped.id] = false; 51 | 52 | currentComponent.push(popped); 53 | } while (vertex.id != popped.id); 54 | 55 | components.push(currentComponent); 56 | } 57 | }; 58 | 59 | _.each(graph.vertices, function(vertex) { 60 | if (indices[vertex.id] === undefined) { 61 | visit(vertex); 62 | } 63 | }); 64 | 65 | return components; 66 | } 67 | 68 | exports.stronglyConnectedComponents = stronglyConnectedComponents; -------------------------------------------------------------------------------- /site/codemirror2/mode/javascript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CodeMirror: JavaScript mode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

CodeMirror: JavaScript mode

13 | 14 |
63 | 64 | 70 | 71 |

JavaScript mode supports a single configuration 72 | option, json, which will set the mode to expect JSON 73 | data rather than a JavaScript program.

74 | 75 |

MIME types defined: text/javascript, application/json.

76 | 77 | 78 | -------------------------------------------------------------------------------- /test/TypeInferenceSpec.js: -------------------------------------------------------------------------------- 1 | describe('type inference', function(){ 2 | var typeinference = require('../src/typeinference'), 3 | types = require('../src/types'); 4 | lexer = require('../src/lexer'); 5 | parser = require('../lib/parser'); 6 | 7 | beforeEach(function() { 8 | this.addMatchers({ 9 | toStringEqual: function(expected) { 10 | return expected == this.actual.toString(); 11 | } 12 | }); 13 | }); 14 | 15 | function parseCode(s) { 16 | return parser.parse(lexer.tokenise(s)).body; 17 | } 18 | 19 | function typeOfCode(s) { 20 | return typeinference.typecheck(parseCode(s), {}, {}); 21 | } 22 | 23 | describe('should type literal', function() { 24 | it('numbers', function(){ 25 | expect(typeOfCode('-1')).toStringEqual('Number'); 26 | expect(typeOfCode('-99999')).toStringEqual('Number'); 27 | expect(typeOfCode('0')).toStringEqual('Number'); 28 | expect(typeOfCode('100')).toStringEqual('Number'); 29 | }); 30 | 31 | it('strings', function(){ 32 | expect(typeOfCode('"100"')).toStringEqual('String'); 33 | expect(typeOfCode('""')).toStringEqual('String'); 34 | expect(typeOfCode("'100'")).toStringEqual('String'); 35 | expect(typeOfCode("''")).toStringEqual('String'); 36 | }); 37 | 38 | it('booleans', function(){ 39 | expect(typeOfCode('false')).toStringEqual('Boolean'); 40 | expect(typeOfCode('true')).toStringEqual('Boolean'); 41 | }); 42 | 43 | it('arrays of primitives', function(){ 44 | expect(typeOfCode('[""]')).toStringEqual('[String]'); 45 | expect(typeOfCode('[true, false]')).toStringEqual('[Boolean]'); 46 | expect(typeOfCode('[1, 2, 3]')).toStringEqual('[Number]'); 47 | }); 48 | 49 | it('empty arrays as generic', function() { 50 | var type = typeOfCode('[]'); 51 | expect(type instanceof types.ArrayType).toBe(true); 52 | expect(type.type instanceof types.Variable).toBe(true); 53 | }); 54 | 55 | it('structures', function() { 56 | expect(typeOfCode('{}')).toStringEqual('{}'); 57 | expect(typeOfCode('{a: 1}')).toStringEqual('{a: Number}'); 58 | expect(typeOfCode('{a: 1, b: true}')).toStringEqual('{a: Number, b: Boolean}'); 59 | expect(typeOfCode("{'a': 1}")).toStringEqual('{"a": Number}'); 60 | expect(typeOfCode('{"a": 1, \'b\': true}')).toStringEqual('{"a": Number, "b": Boolean}'); 61 | expect(typeOfCode("{4: '1'}")).toStringEqual("{4: String}"); 62 | expect(typeOfCode("{4: {'1': 1}}")).toStringEqual('{4: {"1": Number}}'); 63 | }); 64 | }); 65 | 66 | describe("shouldn't type literal", function() { 67 | it('heterogeneous arrays', function() { 68 | expect(function() { 69 | typeOfCode('[1, true]'); 70 | }).toThrow(new Error("Type error on line 0: Number is not Boolean")); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/simple-hint.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | CodeMirror.simpleHint = function(editor, getHints) { 3 | // We want a single cursor position. 4 | if (editor.somethingSelected()) return; 5 | var result = getHints(editor); 6 | if (!result || !result.list.length) return; 7 | var completions = result.list; 8 | function insert(str) { 9 | editor.replaceRange(str, result.from, result.to); 10 | } 11 | // When there is only one completion, use it directly. 12 | if (completions.length == 1) {insert(completions[0]); return true;} 13 | 14 | // Build the select widget 15 | var complete = document.createElement("div"); 16 | complete.className = "CodeMirror-completions"; 17 | var sel = complete.appendChild(document.createElement("select")); 18 | // Opera doesn't move the selection when pressing up/down in a 19 | // multi-select, but it does properly support the size property on 20 | // single-selects, so no multi-select is necessary. 21 | if (!window.opera) sel.multiple = true; 22 | for (var i = 0; i < completions.length; ++i) { 23 | var opt = sel.appendChild(document.createElement("option")); 24 | opt.appendChild(document.createTextNode(completions[i])); 25 | } 26 | sel.firstChild.selected = true; 27 | sel.size = Math.min(10, completions.length); 28 | var pos = editor.cursorCoords(); 29 | complete.style.left = pos.x + "px"; 30 | complete.style.top = pos.yBot + "px"; 31 | document.body.appendChild(complete); 32 | // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. 33 | var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); 34 | if(winW - pos.x < sel.clientWidth) 35 | complete.style.left = (pos.x - sel.clientWidth) + "px"; 36 | // Hack to hide the scrollbar. 37 | if (completions.length <= 10) 38 | complete.style.width = (sel.clientWidth - 1) + "px"; 39 | 40 | var done = false; 41 | function close() { 42 | if (done) return; 43 | done = true; 44 | complete.parentNode.removeChild(complete); 45 | } 46 | function pick() { 47 | insert(completions[sel.selectedIndex]); 48 | close(); 49 | setTimeout(function(){editor.focus();}, 50); 50 | } 51 | CodeMirror.connect(sel, "blur", close); 52 | CodeMirror.connect(sel, "keydown", function(event) { 53 | var code = event.keyCode; 54 | // Enter 55 | if (code == 13) {CodeMirror.e_stop(event); pick();} 56 | // Escape 57 | else if (code == 27) {CodeMirror.e_stop(event); close(); editor.focus();} 58 | else if (code != 38 && code != 40) { 59 | close(); editor.focus(); 60 | // Pass the event to the CodeMirror instance so that it can handle things like backspace properly. 61 | editor.triggerOnKeyDown(event); 62 | setTimeout(function(){CodeMirror.simpleHint(editor, getHints);}, 50); 63 | } 64 | }); 65 | CodeMirror.connect(sel, "dblclick", pick); 66 | 67 | sel.focus(); 68 | // Opera sometimes ignores focusing a freshly created node 69 | if (window.opera) setTimeout(function(){if (!done) sel.focus();}, 100); 70 | return true; 71 | }; 72 | })(); 73 | -------------------------------------------------------------------------------- /test/TypeParserSpec.js: -------------------------------------------------------------------------------- 1 | describe('compiler', function(){ 2 | var jison = require('jison'), 3 | typegrammar = require('../src/typegrammar'), 4 | typeinference = require('../src/typeinference'), 5 | types = require('../src/types'), 6 | lexer = require('../src/lexer'), 7 | nodes = require('../src/nodes').nodes; 8 | 9 | typegrammar.startSymbol = 'oneType'; 10 | typegrammar.bnf.oneType = [['type EOF', 'return $1;']]; 11 | 12 | typeparser = new jison.Parser(typegrammar, { debug: true, noDefaultResolve: true }); 13 | 14 | typeparser.yy = nodes 15 | 16 | typeparser.lexer = { 17 | "lex": function() { 18 | var token = this.tokens[this.pos] ? this.tokens[this.pos++] : ['EOF']; 19 | if ( token[2] != this.yylineno ) { 20 | this.column = 0 21 | } else { 22 | this.column += token[1].length; 23 | } 24 | 25 | this.yytext = token[1]; 26 | this.yylineno = token[2]; 27 | return token[0]; 28 | }, 29 | "setInput": function(tokens) { 30 | this.tokens = tokens; 31 | this.pos = 0; 32 | this.column = 0; 33 | }, 34 | "upcomingInput": function() { 35 | return ""; 36 | }, 37 | "showPosition": function() { 38 | return 'column: ' + this.column; 39 | } 40 | }; 41 | 42 | function parsedType(s) { 43 | var tokens = lexer.tokenise(s); 44 | var v = typeparser.parse(tokens); 45 | return typeinference.nodeToType(v, {}, {}); 46 | } 47 | 48 | function expectEqualTypes(subject, target) { 49 | // FIXME: Use intentional equality once it is implemented 50 | expect(subject.toString()).toEqual(target.toString()); 51 | } 52 | 53 | it('should parse atomic types', function() { 54 | expectEqualTypes( parsedType('Number'), new types.NumberType() ); 55 | expectEqualTypes( parsedType('Boolean'),new types.BooleanType() ); 56 | expectEqualTypes( parsedType('String'), new types.StringType() ); 57 | }); 58 | 59 | it('should parse object types', function() { 60 | expectEqualTypes( parsedType('{}'), new types.ObjectType({}) ); 61 | expectEqualTypes( parsedType('{foo:String}'), new types.ObjectType({foo: new types.StringType()}) ); 62 | 63 | expectEqualTypes( parsedType('{foo:String, baz:Number}'), 64 | new types.ObjectType({ 65 | foo: new types.StringType(), 66 | baz: new types.NumberType() 67 | })); 68 | // TODO: expectEqualTypes( parsedType('{\n\tfoo:String\n}'), new types.ObjectType({foo: new types.StringType()}) ); 69 | }); 70 | 71 | it('should parse function types', function() { 72 | expectEqualTypes( parsedType('Function(String, String)'), 73 | new types.FunctionType([new types.StringType(), new types.StringType()]) ); 74 | }); 75 | 76 | it('should parse array types', function() { 77 | expectEqualTypes( parsedType('[Number]'), 78 | new types.ArrayType(new types.NumberType()) ); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /site/codemirror2/lib/codemirror.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | line-height: 1em; 3 | font-family: monospace; 4 | } 5 | 6 | .CodeMirror-scroll { 7 | overflow: auto; 8 | height: 300px; 9 | /* This is needed to prevent an IE[67] bug where the scrolled content 10 | is visible outside of the scrolling box. */ 11 | position: relative; 12 | outline: none; 13 | } 14 | 15 | .CodeMirror-gutter { 16 | position: absolute; left: 0; top: 0; 17 | z-index: 10; 18 | background-color: #f7f7f7; 19 | border-right: 1px solid #eee; 20 | min-width: 2em; 21 | height: 100%; 22 | } 23 | .CodeMirror-gutter-text { 24 | color: #aaa; 25 | text-align: right; 26 | padding: .4em .2em .4em .4em; 27 | white-space: pre !important; 28 | } 29 | .CodeMirror-lines { 30 | padding: .4em; 31 | white-space: pre; 32 | } 33 | 34 | .CodeMirror pre { 35 | -moz-border-radius: 0; 36 | -webkit-border-radius: 0; 37 | -o-border-radius: 0; 38 | border-radius: 0; 39 | border-width: 0; margin: 0; padding: 0; background: transparent; 40 | font-family: inherit; 41 | font-size: inherit; 42 | padding: 0; margin: 0; 43 | white-space: pre; 44 | word-wrap: normal; 45 | } 46 | 47 | .CodeMirror-wrap pre { 48 | word-wrap: break-word; 49 | white-space: pre-wrap; 50 | } 51 | .CodeMirror-wrap .CodeMirror-scroll { 52 | overflow-x: hidden; 53 | } 54 | 55 | .CodeMirror textarea { 56 | outline: none !important; 57 | } 58 | 59 | .CodeMirror pre.CodeMirror-cursor { 60 | z-index: 10; 61 | position: absolute; 62 | visibility: hidden; 63 | border-left: 1px solid black; 64 | border-right:none; 65 | width:0; 66 | } 67 | .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} 68 | .CodeMirror-focused pre.CodeMirror-cursor { 69 | visibility: visible; 70 | } 71 | 72 | div.CodeMirror-selected { background: #d9d9d9; } 73 | .CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } 74 | 75 | .CodeMirror-searching { 76 | background: #ffa; 77 | background: rgba(255, 255, 0, .4); 78 | } 79 | 80 | /* Default theme */ 81 | 82 | .cm-s-default span.cm-keyword {color: #708;} 83 | .cm-s-default span.cm-atom {color: #219;} 84 | .cm-s-default span.cm-number {color: #164;} 85 | .cm-s-default span.cm-def {color: #00f;} 86 | .cm-s-default span.cm-variable {color: black;} 87 | .cm-s-default span.cm-variable-2 {color: #05a;} 88 | .cm-s-default span.cm-variable-3 {color: #085;} 89 | .cm-s-default span.cm-property {color: black;} 90 | .cm-s-default span.cm-operator {color: black;} 91 | .cm-s-default span.cm-comment {color: #a50;} 92 | .cm-s-default span.cm-string {color: #a11;} 93 | .cm-s-default span.cm-string-2 {color: #f50;} 94 | .cm-s-default span.cm-meta {color: #555;} 95 | .cm-s-default span.cm-error {color: #f00;} 96 | .cm-s-default span.cm-qualifier {color: #555;} 97 | .cm-s-default span.cm-builtin {color: #30a;} 98 | .cm-s-default span.cm-bracket {color: #cc7;} 99 | .cm-s-default span.cm-tag {color: #170;} 100 | .cm-s-default span.cm-attribute {color: #00c;} 101 | .cm-s-default span.cm-header {color: #a0a;} 102 | .cm-s-default span.cm-quote {color: #090;} 103 | .cm-s-default span.cm-hr {color: #999;} 104 | .cm-s-default span.cm-link {color: #00c;} 105 | 106 | span.cm-header, span.cm-strong {font-weight: bold;} 107 | span.cm-em {font-style: italic;} 108 | span.cm-emstrong {font-style: italic; font-weight: bold;} 109 | span.cm-link {text-decoration: underline;} 110 | 111 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 112 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 113 | -------------------------------------------------------------------------------- /src/typegrammar.js: -------------------------------------------------------------------------------- 1 | var bnf = { 2 | // For type annotations 3 | "type": [ 4 | ["IDENTIFIER optTypeParamList", "$$ = new yy.TypeName($1, $2);"], 5 | ["FUNCTION ( optTypeFunctionArgList )", "$$ = new yy.TypeFunction($3);"], 6 | ["GENERIC", "$$ = new yy.Generic($1);"], 7 | ["[ type ]", "$$ = new yy.TypeArray($2);"], 8 | ["( typeList )", "$$ = new yy.TypeObject($2);"], 9 | ["{ optTypePairs }", "$$ = new yy.TypeObject($2);"] 10 | ], 11 | "typeList": [ 12 | ["type", "$$ = [$1];"], 13 | ["typeList , type", "$$ = $1; $1.push($3);"] 14 | ], 15 | "optTypeParamList": [ 16 | ["", "$$ = [];"], 17 | ["typeParamList", "$$ = $1;"] 18 | ], 19 | "typeParamList": [ 20 | ["IDENTIFIER", "$$ = [new yy.TypeName($1, [])];"], 21 | ["GENERIC", "$$ = [new yy.Generic($1, [])];"], 22 | ["( type )", "$$ = [$2];"], 23 | ["typeParamList IDENTIFIER", "$$ = $1; $1.push(new yy.TypeName($2, []));"], 24 | ["typeParamList GENERIC", "$$ = $1; $1.push(new yy.Generic($2, []));"], 25 | ["typeParamList ( type )", "$$ = $1; $1.push($3);"] 26 | ], 27 | "optTypeFunctionArgList": [ 28 | ["", "$$ = [];"], 29 | ["typeFunctionArgList", "$$ = $1;"] 30 | ], 31 | "typeFunctionArgList": [ 32 | ["type", "$$ = [$1];"], 33 | ["typeFunctionArgList , type", "$$ = $1; $1.push($3);"] 34 | ], 35 | "optTypePairs": [ 36 | ["", "$$ = {};"], 37 | ["keywordOrIdentifier : type", "$$ = {}; $$[$1] = $3;"], 38 | ["optTypePairs , keywordOrIdentifier : type", "$$ = $1; $1[$3] = $5;"] 39 | ], 40 | "dataParamList": [ 41 | ["IDENTIFIER", "$$ = [new yy.Arg($1)];"], 42 | ["dataParamList IDENTIFIER", "$$ = $1; $1.push(new yy.Arg($2));"] 43 | ], 44 | "optDataParamList": [ 45 | ["", "$$ = [];"], 46 | ["dataParamList", "$$ = $1;"] 47 | ], 48 | "keywordOrIdentifier": [ 49 | ["THEN", "$$ = $1;"], 50 | ["ELSE", "$$ = $1;"], 51 | ["DATA", "$$ = $1;"], 52 | ["TYPE", "$$ = $1;"], 53 | ["MATCH", "$$ = $1;"], 54 | ["CASE", "$$ = $1;"], 55 | ["DO", "$$ = $1;"], 56 | ["RETURN", "$$ = $1;"], 57 | ["WITH", "$$ = $1;"], 58 | ["WHERE", "$$ = $1;"], 59 | ["IDENTIFIER", "$$ = $1;"] 60 | ] 61 | }; 62 | exports.bnf = bnf; 63 | 64 | var grammar = { 65 | "startSymbol": "typefile", 66 | 67 | "bnf": { 68 | "typefile": [ 69 | ["EOF", "return {};"], 70 | ["body EOF", "return $1;"] 71 | ], 72 | "body": [ 73 | ["pair", "$$ = {types: {}, env: {}}; if($1.data) { $$.types[$1.name] = $1.params; } else { $$.env[$1.name] = $1.type; }"], 74 | ["body TERMINATOR pair", "$$ = $1; if($3.data) { $$.types[$3.name] = $3.params; } else { $$.env[$3.name] = $3.type; }"], 75 | ["body TERMINATOR", "$$ = $1;"] 76 | ], 77 | 78 | "pair": [ 79 | ["IDENTIFIER : type", "$$ = {name: $1, type: $3, data: false};"], 80 | ["TYPE IDENTIFIER optDataParamList", "$$ = {name: $2, params: $3, data: true};"] 81 | ], 82 | 83 | "type": bnf.type, 84 | "typeList": bnf.typeList, 85 | "optTypeParamList": bnf.optTypeParamList, 86 | "typeParamList": bnf.typeParamList, 87 | "optTypeFunctionArgList": bnf.optTypeFunctionArgList, 88 | "typeFunctionArgList": bnf.typeFunctionArgList, 89 | "optTypePairs": bnf.optTypePairs, 90 | "dataParamList": bnf.dataParamList, 91 | "optDataParamList": bnf.optDataParamList, 92 | "keywordOrIdentifier": bnf.keywordOrIdentifier 93 | } 94 | }; 95 | exports.grammar = grammar; 96 | -------------------------------------------------------------------------------- /grunt.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | lint: { 4 | src: [ 5 | './src/*.js' 6 | ] 7 | }, 8 | jison: { 9 | './lib/typeparser.js': './src/typegrammar.js', 10 | './lib/parser.js': './src/grammar.js' 11 | }, 12 | cjsify: { 13 | 'roy.js': { 14 | entry: 'src/compile.js', 15 | dir: __dirname, 16 | options: { 17 | 'export': 'roy', 18 | 'ignoreMissing': true, 19 | 'node': false 20 | } 21 | } 22 | }, 23 | jasmine: { 24 | src: './test' 25 | }, 26 | min: { 27 | 'roy-min.js': 'roy.js' 28 | }, 29 | watch: { 30 | parsers: { 31 | files: './src/*grammar.js', 32 | tasks: 'jison' 33 | }, 34 | jasmine: { 35 | files: ['./src/*.js', './test/*Spec.js'], 36 | tasks: 'jasmine' 37 | } 38 | }, 39 | jshint: { 40 | options: { 41 | es3: true, 42 | indent: 4, 43 | noarg: true, 44 | node: true, 45 | trailing: true, 46 | undef: true, 47 | unused: true 48 | } 49 | } 50 | }); 51 | 52 | grunt.registerMultiTask('jison', 'Parser generator by jison.', function() { 53 | var Parser = require('jison').Parser, 54 | grammar = require(this.data).grammar; 55 | parser = new Parser(grammar, {debug: grunt.option('debug')}), 56 | fs = require('fs'); 57 | 58 | fs.writeFileSync(this.target, parser.generate()); 59 | }); 60 | 61 | grunt.registerMultiTask('cjsify', 'Bundling by commonjs-everywhere.', function() { 62 | var cjsify = require('commonjs-everywhere').cjsify, 63 | escodegen = require('escodegen'), 64 | target = this.target, 65 | ast = cjsify(this.data.entry, this.data.dir, this.data.options), 66 | output = escodegen.generate(ast); 67 | 68 | grunt.file.write(target, output); 69 | }); 70 | 71 | // Watching the task doesn't work. Sadly jasmine-node 72 | // executeSpecsInFolder is not idempotent 73 | grunt.registerMultiTask('jasmine', 'Testing by jasmine.', function() { 74 | var path = require('path'), 75 | specDir = this.file.src, 76 | badCache = grunt.file.expand(specDir).concat([ 77 | path.dirname(require.resolve("jasmine-node")), 78 | ]), 79 | jasmine, 80 | done = this.async(), 81 | key; 82 | 83 | // Would be nice to use grunt.file.clearRequireCache 84 | grunt.utils._.each(require.cache, function(v, k) { 85 | var isBad = grunt.utils._.any(badCache, function(dir) { 86 | return k.indexOf(path.resolve(dir)) === 0; 87 | }); 88 | if(!isBad) return; 89 | delete require.cache[k]; 90 | }); 91 | 92 | jasmine = require("jasmine-node"); 93 | 94 | // Not nice (necessary for jasmine-node's asyncSpecWait, etc) 95 | for(key in jasmine) if(jasmine[key] instanceof Function) global[key] = jasmine[key]; 96 | 97 | function onComplete(runner) { 98 | if (runner.results().failedCount > 0) { 99 | process.exit(1); 100 | return; 101 | } 102 | done(true); 103 | }; 104 | 105 | jasmine.executeSpecsInFolder({ 106 | 'specFolders': [specDir], 107 | 'onComplete': onComplete, 108 | 'isVerbose': false, 109 | 'showColors': true 110 | }); 111 | }); 112 | 113 | grunt.registerTask('default', 'jison lint jasmine cjsify min'); 114 | }; 115 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Roy Change Log 2 | 3 | ### 0.2.2 4 | ##### 2013-06-29 5 | 6 | * Fix #182: TypeError: Cannot set property 'leadingComments' of undefined 7 | 8 | ### 0.2.1 9 | ##### 2013-06-29 10 | 11 | * Fix #180: Support for scientific notated numbers? 12 | * Fix evaluation of Roy code on website. 13 | * Move jison from dependencies to devDependencies. 14 | 15 | ### 0.2.0 16 | ##### 2013-06-29 17 | 18 | * **BREAKING CHANGE** Removed metaprogramming. 19 | * **BREAKING CHANGE** Changed array accessor from `!` to `@`. 20 | 21 | * Compiler rewritten as proper AST using escodegen. 22 | * New test suite. 23 | * Replaced interleaved with rigger. 24 | * Started using codemirror on the site. 25 | 26 | * Added boolean not `!`. 27 | * Added CompileSpec, TarjanSpec and TypeInferenceSpec to test suite. 28 | * Added dependencies (escodegen, source-map). 29 | * Added devDependencies (grunt, jasmine-node, rigger). 30 | * Added documentation for array access. 31 | * Added grunt support. 32 | * Added module (vim-roy). 33 | * Added scientific notation support. 34 | * Added shebang. 35 | * Added strings as keys for objects. 36 | * Added tests (match_expression_single_eval, monoid, object). 37 | * Added travis support. 38 | * Added type classes. 39 | 40 | * Removed devDependencies (interleave). 41 | * Removed example (macro). 42 | * Removed meta-programming example from site. 43 | 44 | * Updated dependencies (jison, unicode-categories). 45 | * Updated examples (defferedmonad, option). 46 | * Updated miscellaneous (chrome-extension, roy-mode, roy.tmbundle). 47 | * Updated prelude (nil, leftMonad, rightMonad). 48 | 49 | ### 0.1.5 50 | ##### 2012-03-19 51 | 52 | * Fixed typo in introduction documentation. 53 | 54 | * Added examples (module, node_module). 55 | * Added files (module, macroexpand, typegrammar). 56 | * Added module example to site. 57 | * Added strings, object type example, native types and regex to types documentation. 58 | * Added tests (accessors, coercing_native_to_any, conditionals, where). 59 | 60 | * Updated examples (data, option, structural). 61 | * Updated grammar (generic, function types, where). 62 | * Updated prelude (π). 63 | * Updated tests (deep_matching, tagged_unions). 64 | 65 | ### 0.1.4 66 | ##### 2012-01-23 67 | * **BREAKING CHANGE** lambda syntax `\[args] -> ` instead of `fn [args] = `. 68 | * **BREAKING CHANGE** do syntax ` <- ` instead of `bind = `. 69 | 70 | * New features 71 | * Optional type parameter lists for arguments. 72 | 73 | * Color console for repl. 74 | * Conversion from mercurial to git. 75 | * Converted examples to new lambda style (funcs, option, structural). 76 | * Converted tests to new syntax (functions, trace_monad). 77 | * Help in repl and binary. 78 | * Makefile supports extensions. 79 | * Renamed stdlib to prelude and extended. 80 | * Started documentation (index, introduction, types). 81 | 82 | * Added ajax and deferred examples to site. 83 | * Added chrome extension. 84 | * Added dependencies (unicode-categories). 85 | * Added examples (ajaxmonad, deferredmonad, fizzbuzz, sqrt, tracemonad). 86 | * Added MIT license. 87 | * Added tests (map, option_monad, unicode). 88 | 89 | * Updated devDependencies (interleave). 90 | * Updated examples (data, types). 91 | * Updated roy-mode and roy.tmbundle extensions. 92 | 93 | ### 0.1.3 94 | ##### 2011-11-19 95 | 96 | * Specific `jison` version. 97 | * Fixed fall-through bug in repl. 98 | 99 | ### 0.1.2 100 | ##### 2011-11-18 101 | 102 | * New features 103 | * Compile-time meta-programming. 104 | * Simple tagged unions. 105 | * Pattern matching. 106 | * Structural typing. 107 | * Monad syntax. 108 | * Not-horrible JS output. 109 | 110 | * Move from toy language to full fledged language. 111 | 112 | * Added examples (alias, data, macro, option, stdlib, structural). 113 | * Added stdlib. 114 | * Added website. 115 | * Added test suite (deep_matching, functions, primitive_types, tagged_unions, trace_monad). 116 | 117 | * Updated examples (funcs, types). 118 | -------------------------------------------------------------------------------- /docs/guide/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Roy is a programming language that targets JavaScript. It has a few 5 | main features: 6 | 7 | * Damas-Hindley-Milner type inference 8 | * Whitespace significant syntax 9 | * Simple tagged unions 10 | * Pattern matching 11 | * Structural typing 12 | * Monad syntax 13 | * Not-horrible JS output 14 | 15 | Most of these features are common in statically-typed, functional 16 | languages, such as: 17 | 18 | * Haskell_ 19 | * OCaml_ 20 | * Scala_ 21 | 22 | Why JavaScript? 23 | --------------- 24 | 25 | JavaScript is a necessity for web applications. It is the only 26 | feasible language you can natively run in your web browser. 27 | 28 | A lot of developers are now using JavaScript outside of the browser; 29 | you can use `node.js`_ and Rhino_ to write server-side JavaScript. 30 | 31 | What not just write JavaScript? 32 | ------------------------------- 33 | 34 | As universal as JavaScript has become, the language itself has 35 | problems: 36 | 37 | Boilerplate 38 | *********** 39 | 40 | Creating functions is something that functional programmers want to do 41 | but JavaScript makes this a bit too verbose: 42 | 43 | .. code-block:: javascript 44 | 45 | var x = function(a, b, c){ 46 | return a + b + c; 47 | }; 48 | 49 | One solution would be to come up with a shorthand syntax for functions 50 | and have implicit returns. We might also be able to get rid of those 51 | braces. 52 | 53 | Tolerance 54 | ********* 55 | 56 | JavaScript tries to guess what the developer is trying to do: 57 | 58 | .. code-block:: javascript 59 | 60 | var two = 1 + true; 61 | console.log(two == "2"); // true 62 | 63 | It doesn't really make sense to add the number ``1`` to the Boolean 64 | ``true`` value. It doesn't really make sense to compare the number 65 | ``2`` to the string ``"2"``. 66 | 67 | For correctness, we should show an error instead of trying to guess 68 | what the programmer wants. 69 | 70 | Complexity 71 | ********** 72 | 73 | JavaScript allows multiple ways to do the same thing. Which is the 74 | correct way to construct a Person? 75 | 76 | .. code-block:: javascript 77 | 78 | var p1 = Person(); 79 | var p2 = new Person(); 80 | 81 | It depends on the library and can be a source of confusion. 82 | 83 | Dangerous 84 | ********* 85 | 86 | It can be easy to do things with unintentional 87 | side-effects. JavaScript has the ``var`` keyword to create local 88 | variables but unqualified assignments write to the global object: 89 | 90 | .. code-block:: javascript 91 | 92 | var x = 10; 93 | 94 | function getX() { 95 | x = 100; // forgot 'var' 96 | return x; 97 | } 98 | 99 | console.log(getX()); // 100 100 | console.log(x); // 100 101 | 102 | What can we do? 103 | --------------- 104 | 105 | We've identified some problems with JavaScript, what can we do to fix 106 | it? 107 | 108 | * Try to replace JavaScript in the browser with another language 109 | * Try to replace JavaScript in the browser with a general purpose 110 | bytecode 111 | * Change the JavaScript standard 112 | * Compile from another language to JavaScript 113 | 114 | The last option is the path of least resistance. In fact, there's 115 | already `quite a few languages`_ that compile to JavaScript, the most 116 | popular being CoffeeScript_, haXe_ and Objective-J_. 117 | 118 | There also ways to compile Haskell, OCaml and Scala to 119 | JavaScript. These can help with writing statically-typed, functional 120 | code for the browser but they usually have a few downsides: 121 | 122 | * Hard/impossible to interoperate with JavaScript libraries 123 | * Generate a lot of code 124 | * Generate code that requires a hefty runtime 125 | * Must be compiled on the server-side (not in the browser) 126 | 127 | The Roy solution 128 | ---------------- 129 | 130 | After trying to write correct programs in JavaScript and languages 131 | that compile to JavaScript, Roy was created. Roy tries to keep close 132 | to JavaScript semantics for ease of interoperability and code 133 | generation. It's also written in JavaScript so that it can compile 134 | code from the browser. 135 | 136 | One of the biggest ideas when coming from JavaScript is the use of 137 | compile-time type-checking to remove type errors. We'll cover that in 138 | the next chapter. 139 | 140 | .. _node.js: http://nodejs.org/ 141 | .. _Rhino: http://www.mozilla.org/rhino/ 142 | .. _Haskell: http://haskell.org/ 143 | .. _OCaml: http://caml.inria.fr/ 144 | .. _Scala: http://scala-lang.org/ 145 | .. _quite a few languages: http://altjs.org/ 146 | .. _CoffeeScript: http://coffeescript.org/ 147 | .. _haXe: http://haxe.org/ 148 | .. _Objective-J: http://cappuccino.org/ 149 | -------------------------------------------------------------------------------- /test/CompileSpec.js: -------------------------------------------------------------------------------- 1 | describe('compiler', function(){ 2 | var roy = require('../src/compile'), 3 | fs = require('fs'), 4 | path = require('path'), 5 | child_process = require('child_process'), 6 | processBin = process.argv[0]; 7 | 8 | function compilerOutput(s) { 9 | return roy.compile(s, {}, {}, {nodejs: true}).output; 10 | } 11 | 12 | function fixtureCompilerOutput(s) { 13 | return compilerOutput(fs.readFileSync(path.join('test', 'fixtures', s + '.roy'), 'utf8')); 14 | } 15 | 16 | function fixtureExpectedOutput(s) { 17 | return fs.readFileSync(path.join('test', 'fixtures', s + '.out'), 'utf8'); 18 | } 19 | 20 | function expectExecutionToHaveExpectedOutput(s) { 21 | var expected = fixtureExpectedOutput(s); 22 | var compiled = fixtureCompilerOutput(s); 23 | 24 | var child = child_process.spawn(processBin); 25 | child.stdin.write(compiled, 'utf8'); 26 | child.stdin.end(); 27 | child.stdout.setEncoding('utf8'); 28 | 29 | var actual = ''; 30 | asyncSpecWait(); 31 | child.stdout.on('data', function(d) { 32 | actual += d; 33 | }); 34 | child.stdout.on('end', function() { 35 | expect(actual).toEqual(expected); 36 | asyncSpecDone(); 37 | }); 38 | } 39 | 40 | it('should preserve comments', function(){ 41 | expect(compilerOutput('// HELLO\nconsole.log 123')).toEqual('// HELLO\nconsole.log(123);'); 42 | }); 43 | it('should preserve comments on non output nodes', function(){ 44 | expect(compilerOutput('// Comment\ntype X = {a: Number}\nlet x:X = {a: 123}')) 45 | .toEqual('// Comment\nvar x = { \'a\': 123 };'); 46 | }); 47 | 48 | it('should compile literal', function(){ 49 | expect(compilerOutput('42')).toEqual('42;'); 50 | expect(compilerOutput('\'string\'')).toEqual('\'string\';'); 51 | expect(compilerOutput('null')).toEqual('null;'); 52 | }); 53 | 54 | it('should compile identifier', function(){ 55 | expect(compilerOutput('roy')).toEqual('roy;'); 56 | }); 57 | 58 | it('should only execute a match expression once', function(){ 59 | expectExecutionToHaveExpectedOutput('good/match_expression_single_eval'); 60 | }); 61 | 62 | describe('should execute', function() { 63 | it('accessors.roy with expected output', function() { 64 | expectExecutionToHaveExpectedOutput('good/accessors'); 65 | }); 66 | it('coercing_native_to_any.roy with expected output', function() { 67 | expectExecutionToHaveExpectedOutput('good/coercing_native_to_any'); 68 | }); 69 | it('conditionals.roy with expected output', function() { 70 | expectExecutionToHaveExpectedOutput('good/conditionals'); 71 | }); 72 | it('deep_matching.roy with expected output', function() { 73 | expectExecutionToHaveExpectedOutput('good/deep_matching'); 74 | }); 75 | it('functions.roy with expected output', function() { 76 | expectExecutionToHaveExpectedOutput('good/functions'); 77 | }); 78 | it('map.roy with expected output', function() { 79 | expectExecutionToHaveExpectedOutput('good/map'); 80 | }); 81 | it('monoid.roy with expected output', function() { 82 | expectExecutionToHaveExpectedOutput('good/monoid'); 83 | }); 84 | it('number.roy with expected output', function() { 85 | expectExecutionToHaveExpectedOutput('good/number'); 86 | }); 87 | it('object.roy with expected output', function() { 88 | expectExecutionToHaveExpectedOutput('good/object'); 89 | }); 90 | it('option_monad.roy with expected output', function() { 91 | expectExecutionToHaveExpectedOutput('good/option_monad'); 92 | }); 93 | it('primitive_types.roy with expected output', function() { 94 | expectExecutionToHaveExpectedOutput('good/primitive_types'); 95 | }); 96 | it('tagged_unions.roy with expected output', function() { 97 | expectExecutionToHaveExpectedOutput('good/tagged_unions'); 98 | }); 99 | it('trace_monad.roy with expected output', function() { 100 | expectExecutionToHaveExpectedOutput('good/trace_monad'); 101 | }); 102 | it('unicode.roy with expected output', function() { 103 | expectExecutionToHaveExpectedOutput('good/unicode'); 104 | }); 105 | it('where.roy with expected output', function() { 106 | expectExecutionToHaveExpectedOutput('good/where'); 107 | }); 108 | it('with.roy with expected output', function() { 109 | expectExecutionToHaveExpectedOutput('good/with'); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /docs/guide/types.rst: -------------------------------------------------------------------------------- 1 | Types 2 | ===== 3 | 4 | The `Curry-Howard isomorphism`_ states that types are theorems and 5 | programs are proofs. Roy takes the stance of not generating programs 6 | (proofs) if the types (theorems) don't make sense. 7 | 8 | This is called static type-checking and is a useful tool for writing 9 | correct programs. 10 | 11 | Primitives 12 | ---------- 13 | 14 | Roy implements the same primitive types as JavaScript: 15 | 16 | * Number 17 | * Boolean 18 | * String 19 | * Array 20 | * Object 21 | 22 | In Roy, Arrays and Objects have special semantics and are composed of 23 | multiple types. We'll separately cover typing of :ref:`arrays` and 24 | typing of :ref:`objects`. 25 | 26 | The Read Evaluate Print Loop 27 | ---------------------------- 28 | 29 | Roy has an interactive mode which allows compilation and execution of 30 | code. 31 | 32 | The simplest example is a value. Putting in the number ``1`` will 33 | respond back with that same value and its type:: 34 | 35 | roy> 1 36 | 1 : Number 37 | 38 | We could also give a string of characters:: 39 | 40 | roy> "Hello world!" 41 | Hello world! : String 42 | 43 | Or a Boolean:: 44 | 45 | roy> true 46 | true : Boolean 47 | 48 | Evaluating expressions will also tell you the type of the result:: 49 | 50 | roy> 1 + 1 51 | 2 : Number 52 | 53 | Let's make the type-checking fail:: 54 | 55 | roy> 1 + "Test" 56 | Error: Type error: Number is not String 57 | 58 | Notice that it doesn't give you an answer to the 59 | expression. JavaScript at this point would instead guess what you 60 | meant and give you an answer of ``"1Test"``. 61 | 62 | .. _strings: 63 | 64 | Strings 65 | ------- 66 | 67 | These behave similar to a Javascript String, but they won't have methods attached to them. 68 | String concatentation is done with the '++' operator. 69 | 70 | .. _arrays: 71 | 72 | Arrays 73 | ------ 74 | 75 | Arrays are homogeneous, meaning that they can only hold elements of 76 | the same type. For example, we can make an Array of Numbers:: 77 | 78 | roy> [1, 2, 3] 79 | 1,2,3 : [Number] 80 | 81 | Trying to treat it as a heterogeneous collection will result in a type 82 | error:: 83 | 84 | roy> [1, true, 3] 85 | Error: Type error: Number is not Boolean 86 | 87 | Access array elements with the ``@`` operator:: 88 | 89 | roy> [1,2,3] @ 1 90 | 2 : Number 91 | 92 | .. _objects: 93 | 94 | Objects 95 | ------- 96 | 97 | Objects use `structural subtyping`_. An object is a "subtype" of 98 | another object if it satisfies all of the properties that the other 99 | object has. 100 | 101 | An empty object is the supertype of all objects:: 102 | 103 | roy> {} 104 | [object Object] : {} 105 | 106 | An object containing a single property is a subtype of only the empty 107 | object:: 108 | 109 | roy> {a: 100} 110 | [object Object] : {a: Number} 111 | 112 | This property can be used to write well-typed code that works on object properties:: 113 | 114 | roy> let a = {a:100} 115 | roy> let b = {a:5, b:5} 116 | roy> let f o = o.a + 6 117 | roy> f a 118 | 106 : Number 119 | roy> f b 120 | 11 : Number 121 | roy> let d = {b:100} 122 | roy> f d 123 | Error: Type error: {b: Number} is not {a: Number} 124 | 125 | Interoperating with JavaScript 126 | ------------------------------ 127 | 128 | Referring to unknown identifier will assume that the identifier refers 129 | to a native JavaScript global. 130 | 131 | For example, you can refer to ``console.log``, something not known 132 | natively to Roy:: 133 | 134 | roy> console.log "Hello!" 135 | Hello! 136 | 137 | 138 | Using Native types 139 | -------------------- 140 | 141 | Given Roy's current limitations, you may want to use a Native type sometimes:: 142 | 143 | roy> "abc".length 144 | Error: Parse error on line 2: Unexpected '.' 145 | 146 | roy> (String "abc") 147 | abc : Native 148 | roy> (String "abc").length 149 | 3 : Native 150 | 151 | Regular Expressions 152 | ------------------------------ 153 | 154 | Roy does not have direct support for regular expressions, including literals like /exp/ 155 | 156 | To use a regular expression in Roy you need one of the following approaches: 157 | 158 | * Have an existing RegExp 159 | * Create a native RegExp using the RegExp constructor 160 | * Invoke match on a Native String, which converts the matching String to a RegExp 161 | 162 | :: 163 | 164 | roy> (String "abcd").match "a.c" 165 | ["abc"] : Native 166 | 167 | roy> (RegExp("a.c")).exec 'abcd' 168 | ["abc"] : Native 169 | 170 | If you want, you can try and shorten up RegExp construction:: 171 | 172 | roy> let r s = RegExp s 173 | roy> r "a.c" 174 | /a.c/ : Native 175 | roy> r"a.c" 176 | /a.c/ : Native 177 | 178 | roy> (r"a.c").exec "abcd" 179 | ["abc"] : Native 180 | 181 | .. _Curry-Howard isomorphism: http://en.wikipedia.org/wiki/Curry-Howard_correspondence 182 | .. _structural subtyping: http://en.wikipedia.org/wiki/Structural_type_system 183 | Access array elements with the @ operator 184 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/search.js: -------------------------------------------------------------------------------- 1 | // Define search commands. Depends on dialog.js or another 2 | // implementation of the openDialog method. 3 | 4 | // Replace works a little oddly -- it will do the replace on the next 5 | // Ctrl-G (or whatever is bound to findNext) press. You prevent a 6 | // replace by making sure the match is no longer selected when hitting 7 | // Ctrl-G. 8 | 9 | (function() { 10 | function SearchState() { 11 | this.posFrom = this.posTo = this.query = null; 12 | this.marked = []; 13 | } 14 | function getSearchState(cm) { 15 | return cm._searchState || (cm._searchState = new SearchState()); 16 | } 17 | function dialog(cm, text, shortText, f) { 18 | if (cm.openDialog) cm.openDialog(text, f); 19 | else f(prompt(shortText, "")); 20 | } 21 | function confirmDialog(cm, text, shortText, fs) { 22 | if (cm.openConfirm) cm.openConfirm(text, fs); 23 | else if (confirm(shortText)) fs[0](); 24 | } 25 | function parseQuery(query) { 26 | var isRE = query.match(/^\/(.*)\/$/); 27 | return isRE ? new RegExp(isRE[1]) : query; 28 | } 29 | var queryDialog = 30 | 'Search: (Use /re/ syntax for regexp search)'; 31 | function doSearch(cm, rev) { 32 | var state = getSearchState(cm); 33 | if (state.query) return findNext(cm, rev); 34 | dialog(cm, queryDialog, "Search for:", function(query) { 35 | cm.operation(function() { 36 | if (!query || state.query) return; 37 | state.query = parseQuery(query); 38 | if (cm.lineCount() < 2000) { // This is too expensive on big documents. 39 | for (var cursor = cm.getSearchCursor(query); cursor.findNext();) 40 | state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching")); 41 | } 42 | state.posFrom = state.posTo = cm.getCursor(); 43 | findNext(cm, rev); 44 | }); 45 | }); 46 | } 47 | function findNext(cm, rev) {cm.operation(function() { 48 | var state = getSearchState(cm); 49 | var cursor = cm.getSearchCursor(state.query, rev ? state.posFrom : state.posTo); 50 | if (!cursor.find(rev)) { 51 | cursor = cm.getSearchCursor(state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0}); 52 | if (!cursor.find(rev)) return; 53 | } 54 | cm.setSelection(cursor.from(), cursor.to()); 55 | state.posFrom = cursor.from(); state.posTo = cursor.to(); 56 | })} 57 | function clearSearch(cm) {cm.operation(function() { 58 | var state = getSearchState(cm); 59 | if (!state.query) return; 60 | state.query = null; 61 | for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear(); 62 | state.marked.length = 0; 63 | })} 64 | 65 | var replaceQueryDialog = 66 | 'Replace: (Use /re/ syntax for regexp search)'; 67 | var replacementQueryDialog = 'With: '; 68 | var doReplaceConfirm = "Replace? "; 69 | function replace(cm, all) { 70 | dialog(cm, replaceQueryDialog, "Replace:", function(query) { 71 | if (!query) return; 72 | query = parseQuery(query); 73 | dialog(cm, replacementQueryDialog, "Replace with:", function(text) { 74 | if (all) { 75 | cm.operation(function() { 76 | for (var cursor = cm.getSearchCursor(query); cursor.findNext();) { 77 | if (typeof query != "string") { 78 | var match = cm.getRange(cursor.from(), cursor.to()).match(query); 79 | cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];})); 80 | } else cursor.replace(text); 81 | } 82 | }); 83 | } else { 84 | clearSearch(cm); 85 | var cursor = cm.getSearchCursor(query, cm.getCursor()); 86 | function advance() { 87 | var start = cursor.from(), match; 88 | if (!(match = cursor.findNext())) { 89 | cursor = cm.getSearchCursor(query); 90 | if (!(match = cursor.findNext()) || 91 | (cursor.from().line == start.line && cursor.from().ch == start.ch)) return; 92 | } 93 | cm.setSelection(cursor.from(), cursor.to()); 94 | confirmDialog(cm, doReplaceConfirm, "Replace?", 95 | [function() {doReplace(match);}, advance]); 96 | } 97 | function doReplace(match) { 98 | cursor.replace(typeof query == "string" ? text : 99 | text.replace(/\$(\d)/, function(w, i) {return match[i];})); 100 | advance(); 101 | } 102 | advance(); 103 | } 104 | }); 105 | }); 106 | } 107 | 108 | CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; 109 | CodeMirror.commands.findNext = doSearch; 110 | CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; 111 | CodeMirror.commands.clearSearch = clearSearch; 112 | CodeMirror.commands.replace = replace; 113 | CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; 114 | })(); 115 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/searchcursor.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function SearchCursor(cm, query, pos, caseFold) { 3 | this.atOccurrence = false; this.cm = cm; 4 | if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase(); 5 | 6 | pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0}; 7 | this.pos = {from: pos, to: pos}; 8 | 9 | // The matches method is filled in based on the type of query. 10 | // It takes a position and a direction, and returns an object 11 | // describing the next occurrence of the query, or null if no 12 | // more matches were found. 13 | if (typeof query != "string") // Regexp match 14 | this.matches = function(reverse, pos) { 15 | if (reverse) { 16 | var line = cm.getLine(pos.line).slice(0, pos.ch), match = line.match(query), start = 0; 17 | while (match) { 18 | var ind = line.indexOf(match[0]); 19 | start += ind; 20 | line = line.slice(ind + 1); 21 | var newmatch = line.match(query); 22 | if (newmatch) match = newmatch; 23 | else break; 24 | start++; 25 | } 26 | } 27 | else { 28 | var line = cm.getLine(pos.line).slice(pos.ch), match = line.match(query), 29 | start = match && pos.ch + line.indexOf(match[0]); 30 | } 31 | if (match) 32 | return {from: {line: pos.line, ch: start}, 33 | to: {line: pos.line, ch: start + match[0].length}, 34 | match: match}; 35 | }; 36 | else { // String query 37 | if (caseFold) query = query.toLowerCase(); 38 | var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; 39 | var target = query.split("\n"); 40 | // Different methods for single-line and multi-line queries 41 | if (target.length == 1) 42 | this.matches = function(reverse, pos) { 43 | var line = fold(cm.getLine(pos.line)), len = query.length, match; 44 | if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1) 45 | : (match = line.indexOf(query, pos.ch)) != -1) 46 | return {from: {line: pos.line, ch: match}, 47 | to: {line: pos.line, ch: match + len}}; 48 | }; 49 | else 50 | this.matches = function(reverse, pos) { 51 | var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln)); 52 | var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match)); 53 | if (reverse ? offsetA >= pos.ch || offsetA != match.length 54 | : offsetA <= pos.ch || offsetA != line.length - match.length) 55 | return; 56 | for (;;) { 57 | if (reverse ? !ln : ln == cm.lineCount() - 1) return; 58 | line = fold(cm.getLine(ln += reverse ? -1 : 1)); 59 | match = target[reverse ? --idx : ++idx]; 60 | if (idx > 0 && idx < target.length - 1) { 61 | if (line != match) return; 62 | else continue; 63 | } 64 | var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length); 65 | if (reverse ? offsetB != line.length - match.length : offsetB != match.length) 66 | return; 67 | var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB}; 68 | return {from: reverse ? end : start, to: reverse ? start : end}; 69 | } 70 | }; 71 | } 72 | } 73 | 74 | SearchCursor.prototype = { 75 | findNext: function() {return this.find(false);}, 76 | findPrevious: function() {return this.find(true);}, 77 | 78 | find: function(reverse) { 79 | var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to); 80 | function savePosAndFail(line) { 81 | var pos = {line: line, ch: 0}; 82 | self.pos = {from: pos, to: pos}; 83 | self.atOccurrence = false; 84 | return false; 85 | } 86 | 87 | for (;;) { 88 | if (this.pos = this.matches(reverse, pos)) { 89 | this.atOccurrence = true; 90 | return this.pos.match || true; 91 | } 92 | if (reverse) { 93 | if (!pos.line) return savePosAndFail(0); 94 | pos = {line: pos.line-1, ch: this.cm.getLine(pos.line-1).length}; 95 | } 96 | else { 97 | var maxLine = this.cm.lineCount(); 98 | if (pos.line == maxLine - 1) return savePosAndFail(maxLine); 99 | pos = {line: pos.line+1, ch: 0}; 100 | } 101 | } 102 | }, 103 | 104 | from: function() {if (this.atOccurrence) return this.pos.from;}, 105 | to: function() {if (this.atOccurrence) return this.pos.to;}, 106 | 107 | replace: function(newText) { 108 | var self = this; 109 | if (this.atOccurrence) 110 | self.pos.to = this.cm.replaceRange(newText, self.pos.from, self.pos.to); 111 | } 112 | }; 113 | 114 | CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { 115 | return new SearchCursor(this, query, pos, caseFold); 116 | }); 117 | })(); 118 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/javascript-hint.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | function forEach(arr, f) { 3 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); 4 | } 5 | 6 | function arrayContains(arr, item) { 7 | if (!Array.prototype.indexOf) { 8 | var i = arr.length; 9 | while (i--) { 10 | if (arr[i] === item) { 11 | return true; 12 | } 13 | } 14 | return false; 15 | } 16 | return arr.indexOf(item) != -1; 17 | } 18 | 19 | function scriptHint(editor, keywords, getToken) { 20 | // Find the token at the cursor 21 | var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; 22 | // If it's not a 'word-style' token, ignore the token. 23 | if (!/^[\w$_]*$/.test(token.string)) { 24 | token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, 25 | className: token.string == "." ? "property" : null}; 26 | } 27 | // If it is a property, find out what it is a property of. 28 | while (tprop.className == "property") { 29 | tprop = getToken(editor, {line: cur.line, ch: tprop.start}); 30 | if (tprop.string != ".") return; 31 | tprop = getToken(editor, {line: cur.line, ch: tprop.start}); 32 | if (tprop.string == ')') { 33 | var level = 1; 34 | do { 35 | tprop = getToken(editor, {line: cur.line, ch: tprop.start}); 36 | switch (tprop.string) { 37 | case ')': level++; break; 38 | case '(': level--; break; 39 | default: break; 40 | } 41 | } while (level > 0) 42 | tprop = getToken(editor, {line: cur.line, ch: tprop.start}); 43 | if (tprop.className == 'variable') 44 | tprop.className = 'function'; 45 | else return; // no clue 46 | } 47 | if (!context) var context = []; 48 | context.push(tprop); 49 | } 50 | return {list: getCompletions(token, context, keywords), 51 | from: {line: cur.line, ch: token.start}, 52 | to: {line: cur.line, ch: token.end}}; 53 | } 54 | 55 | CodeMirror.javascriptHint = function(editor) { 56 | return scriptHint(editor, javascriptKeywords, 57 | function (e, cur) {return e.getTokenAt(cur);}); 58 | } 59 | 60 | function getCoffeeScriptToken(editor, cur) { 61 | // This getToken, it is for coffeescript, imitates the behavior of 62 | // getTokenAt method in javascript.js, that is, returning "property" 63 | // type and treat "." as indepenent token. 64 | var token = editor.getTokenAt(cur); 65 | if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') { 66 | token.end = token.start; 67 | token.string = '.'; 68 | token.className = "property"; 69 | } 70 | else if (/^\.[\w$_]*$/.test(token.string)) { 71 | token.className = "property"; 72 | token.start++; 73 | token.string = token.string.replace(/\./, ''); 74 | } 75 | return token; 76 | } 77 | 78 | CodeMirror.coffeescriptHint = function(editor) { 79 | return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken); 80 | } 81 | 82 | var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + 83 | "toUpperCase toLowerCase split concat match replace search").split(" "); 84 | var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " + 85 | "lastIndexOf every some filter forEach map reduce reduceRight ").split(" "); 86 | var funcProps = "prototype apply call bind".split(" "); 87 | var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " + 88 | "if in instanceof new null return switch throw true try typeof var void while with").split(" "); 89 | var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + 90 | "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); 91 | 92 | function getCompletions(token, context, keywords) { 93 | var found = [], start = token.string; 94 | function maybeAdd(str) { 95 | if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); 96 | } 97 | function gatherCompletions(obj) { 98 | if (typeof obj == "string") forEach(stringProps, maybeAdd); 99 | else if (obj instanceof Array) forEach(arrayProps, maybeAdd); 100 | else if (obj instanceof Function) forEach(funcProps, maybeAdd); 101 | for (var name in obj) maybeAdd(name); 102 | } 103 | 104 | if (context) { 105 | // If this is a property, see if it belongs to some object we can 106 | // find in the current environment. 107 | var obj = context.pop(), base; 108 | if (obj.className == "variable") 109 | base = window[obj.string]; 110 | else if (obj.className == "string") 111 | base = ""; 112 | else if (obj.className == "atom") 113 | base = 1; 114 | else if (obj.className == "function") { 115 | if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && 116 | (typeof window.jQuery == 'function')) 117 | base = window.jQuery(); 118 | else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function')) 119 | base = window._(); 120 | } 121 | while (base != null && context.length) 122 | base = base[context.pop().string]; 123 | if (base != null) gatherCompletions(base); 124 | } 125 | else { 126 | // If not, just look in the window object and any local scope 127 | // (reading into JS mode internals to get at the local variables) 128 | for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); 129 | gatherCompletions(window); 130 | forEach(keywords, maybeAdd); 131 | } 132 | return found; 133 | } 134 | })(); 135 | -------------------------------------------------------------------------------- /docs/guide/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Roy.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Roy.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/guide/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Roy.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Roy.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Roy" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Roy" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/closetag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tag-closer extension for CodeMirror. 3 | * 4 | * This extension adds a "closeTag" utility function that can be used with key bindings to 5 | * insert a matching end tag after the ">" character of a start tag has been typed. It can 6 | * also complete " 18 | * Contributed under the same license terms as CodeMirror. 19 | */ 20 | (function() { 21 | /** Option that allows tag closing behavior to be toggled. Default is true. */ 22 | CodeMirror.defaults['closeTagEnabled'] = true; 23 | 24 | /** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */ 25 | CodeMirror.defaults['closeTagIndent'] = ['applet', 'blockquote', 'body', 'button', 'div', 'dl', 'fieldset', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'iframe', 'layer', 'legend', 'object', 'ol', 'p', 'select', 'table', 'ul']; 26 | 27 | /** 28 | * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass. 29 | * - cm: The editor instance. 30 | * - ch: The character being processed. 31 | * - indent: Optional. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option. 32 | * Pass false to disable indentation. Pass an array to override the default list of tag names. 33 | */ 34 | CodeMirror.defineExtension("closeTag", function(cm, ch, indent) { 35 | if (!cm.getOption('closeTagEnabled')) { 36 | throw CodeMirror.Pass; 37 | } 38 | 39 | var mode = cm.getOption('mode'); 40 | 41 | if (mode == 'text/html') { 42 | 43 | /* 44 | * Relevant structure of token: 45 | * 46 | * htmlmixed 47 | * className 48 | * state 49 | * htmlState 50 | * type 51 | * context 52 | * tagName 53 | * mode 54 | * 55 | * xml 56 | * className 57 | * state 58 | * tagName 59 | * type 60 | */ 61 | 62 | var pos = cm.getCursor(); 63 | var tok = cm.getTokenAt(pos); 64 | var state = tok.state; 65 | 66 | if (state.mode && state.mode != 'html') { 67 | throw CodeMirror.Pass; // With htmlmixed, we only care about the html sub-mode. 68 | } 69 | 70 | if (ch == '>') { 71 | var type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml 72 | 73 | if (tok.className == 'tag' && type == 'closeTag') { 74 | throw CodeMirror.Pass; // Don't process the '>' at the end of an end-tag. 75 | } 76 | 77 | cm.replaceSelection('>'); // Mode state won't update until we finish the tag. 78 | pos = {line: pos.line, ch: pos.ch + 1}; 79 | cm.setCursor(pos); 80 | 81 | tok = cm.getTokenAt(cm.getCursor()); 82 | state = tok.state; 83 | type = state.htmlState ? state.htmlState.type : state.type; // htmlmixed : xml 84 | 85 | if (tok.className == 'tag' && type != 'selfcloseTag') { 86 | var tagName = state.htmlState ? state.htmlState.context.tagName : state.tagName; // htmlmixed : xml 87 | if (tagName.length > 0) { 88 | insertEndTag(cm, indent, pos, tagName); 89 | } 90 | return; 91 | } 92 | 93 | // Undo the '>' insert and allow cm to handle the key instead. 94 | cm.setSelection({line: pos.line, ch: pos.ch - 1}, pos); 95 | cm.replaceSelection(""); 96 | 97 | } else if (ch == '/') { 98 | if (tok.className == 'tag' && tok.string == '<') { 99 | var tagName = state.htmlState ? (state.htmlState.context ? state.htmlState.context.tagName : '') : state.context.tagName; // htmlmixed : xml # extra htmlmized check is for ' 0) { 101 | completeEndTag(cm, pos, tagName); 102 | return; 103 | } 104 | } 105 | } 106 | 107 | } else if (mode == 'xmlpure') { 108 | 109 | var pos = cm.getCursor(); 110 | var tok = cm.getTokenAt(pos); 111 | var tagName = tok.state.context.tagName; 112 | 113 | if (ch == '>') { 114 | // tagName=foo, string=foo 115 | // tagName=foo, string=/ # ignore 116 | // tagName=foo, string=/foo # ignore 117 | if (tok.string == tagName) { 118 | cm.replaceSelection('>'); // parity w/html modes 119 | pos = {line: pos.line, ch: pos.ch + 1}; 120 | cm.setCursor(pos); 121 | 122 | insertEndTag(cm, indent, pos, tagName); 123 | return; 124 | } 125 | 126 | } else if (ch == '/') { 127 | // ', 'end'); 142 | cm.indentLine(pos.line + 1); 143 | cm.indentLine(pos.line + 2); 144 | cm.setCursor({line: pos.line + 1, ch: cm.getLine(pos.line + 1).length}); 145 | } else { 146 | cm.replaceSelection(''); 147 | cm.setCursor(pos); 148 | } 149 | } 150 | 151 | function shouldIndent(cm, indent, tagName) { 152 | if (typeof indent == 'undefined' || indent == null || indent == true) { 153 | indent = cm.getOption('closeTagIndent'); 154 | } 155 | if (!indent) { 156 | indent = []; 157 | } 158 | return indexOf(indent, tagName.toLowerCase()) != -1; 159 | } 160 | 161 | // C&P from codemirror.js...would be nice if this were visible to utilities. 162 | function indexOf(collection, elt) { 163 | if (collection.indexOf) return collection.indexOf(elt); 164 | for (var i = 0, e = collection.length; i < e; ++i) 165 | if (collection[i] == elt) return i; 166 | return -1; 167 | } 168 | 169 | function completeEndTag(cm, pos, tagName) { 170 | cm.replaceSelection('/' + tagName + '>'); 171 | cm.setCursor({line: pos.line, ch: pos.ch + tagName.length + 2 }); 172 | } 173 | 174 | })(); 175 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/foldcode.js: -------------------------------------------------------------------------------- 1 | // the tagRangeFinder function is 2 | // Copyright (C) 2011 by Daniel Glazman 3 | // released under the MIT license (../../LICENSE) like the rest of CodeMirror 4 | CodeMirror.tagRangeFinder = function(cm, line) { 5 | var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; 6 | var nameChar = nameStartChar + "\-\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; 7 | var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*"); 8 | 9 | var lineText = cm.getLine(line); 10 | var found = false; 11 | var tag = null; 12 | var pos = 0; 13 | while (!found) { 14 | pos = lineText.indexOf("<", pos); 15 | if (-1 == pos) // no tag on line 16 | return; 17 | if (pos + 1 < lineText.length && lineText[pos + 1] == "/") { // closing tag 18 | pos++; 19 | continue; 20 | } 21 | // ok we weem to have a start tag 22 | if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { // not a tag name... 23 | pos++; 24 | continue; 25 | } 26 | var gtPos = lineText.indexOf(">", pos + 1); 27 | if (-1 == gtPos) { // end of start tag not in line 28 | var l = line + 1; 29 | var foundGt = false; 30 | var lastLine = cm.lineCount(); 31 | while (l < lastLine && !foundGt) { 32 | var lt = cm.getLine(l); 33 | var gt = lt.indexOf(">"); 34 | if (-1 != gt) { // found a > 35 | foundGt = true; 36 | var slash = lt.lastIndexOf("/", gt); 37 | if (-1 != slash && slash < gt) { 38 | var str = lineText.substr(slash, gt - slash + 1); 39 | if (!str.match( /\/\s*\>/ )) // yep, that's the end of empty tag 40 | return l+1; 41 | } 42 | } 43 | l++; 44 | } 45 | found = true; 46 | } 47 | else { 48 | var slashPos = lineText.lastIndexOf("/", gtPos); 49 | if (-1 == slashPos) { // cannot be empty tag 50 | found = true; 51 | // don't continue 52 | } 53 | else { // empty tag? 54 | // check if really empty tag 55 | var str = lineText.substr(slashPos, gtPos - slashPos + 1); 56 | if (!str.match( /\/\s*\>/ )) { // finally not empty 57 | found = true; 58 | // don't continue 59 | } 60 | } 61 | } 62 | if (found) { 63 | var subLine = lineText.substr(pos + 1); 64 | tag = subLine.match(xmlNAMERegExp); 65 | if (tag) { 66 | // we have an element name, wooohooo ! 67 | tag = tag[0]; 68 | // do we have the close tag on same line ??? 69 | if (-1 != lineText.indexOf("", pos)) // yep 70 | { 71 | found = false; 72 | } 73 | // we don't, so we have a candidate... 74 | } 75 | else 76 | found = false; 77 | } 78 | if (!found) 79 | pos++; 80 | } 81 | 82 | if (found) { 83 | var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\\s)|(\\<" + tag + "$)"; 84 | var startTagRegExp = new RegExp(startTag, "g"); 85 | var endTag = ""; 86 | var depth = 1; 87 | var l = line + 1; 88 | var lastLine = cm.lineCount(); 89 | while (l < lastLine) { 90 | lineText = cm.getLine(l); 91 | var match = lineText.match(startTagRegExp); 92 | if (match) { 93 | for (var i = 0; i < match.length; i++) { 94 | if (match[i] == endTag) 95 | depth--; 96 | else 97 | depth++; 98 | if (!depth) 99 | return l+1; 100 | } 101 | } 102 | l++; 103 | } 104 | return; 105 | } 106 | }; 107 | 108 | CodeMirror.braceRangeFinder = function(cm, line) { 109 | var lineText = cm.getLine(line); 110 | var startChar = lineText.lastIndexOf("{"); 111 | if (startChar < 0 || lineText.lastIndexOf("}") > startChar) return; 112 | var tokenType = cm.getTokenAt({line: line, ch: startChar}).className; 113 | var count = 1, lastLine = cm.lineCount(), end; 114 | outer: for (var i = line + 1; i < lastLine; ++i) { 115 | var text = cm.getLine(i), pos = 0; 116 | for (;;) { 117 | var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos); 118 | if (nextOpen < 0) nextOpen = text.length; 119 | if (nextClose < 0) nextClose = text.length; 120 | pos = Math.min(nextOpen, nextClose); 121 | if (pos == text.length) break; 122 | if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) { 123 | if (pos == nextOpen) ++count; 124 | else if (!--count) { end = i; break outer; } 125 | } 126 | ++pos; 127 | } 128 | } 129 | if (end == null || end == line + 1) return; 130 | return end; 131 | }; 132 | 133 | CodeMirror.indentRangeFinder = function(cm, line) { 134 | var tabSize = cm.getOption("tabSize"); 135 | var myIndent = cm.getLineHandle(line).indentation(tabSize), last; 136 | for (var i = line + 1, end = cm.lineCount(); i < end; ++i) { 137 | var handle = cm.getLineHandle(i); 138 | if (!/^\s*$/.test(handle.text)) { 139 | if (handle.indentation(tabSize) <= myIndent) break; 140 | last = i; 141 | } 142 | } 143 | if (!last) return null; 144 | return last + 1; 145 | }; 146 | 147 | CodeMirror.newFoldFunction = function(rangeFinder, markText) { 148 | var folded = []; 149 | if (markText == null) markText = '
%N%'; 150 | 151 | function isFolded(cm, n) { 152 | for (var i = 0; i < folded.length; ++i) { 153 | var start = cm.lineInfo(folded[i].start); 154 | if (!start) folded.splice(i--, 1); 155 | else if (start.line == n) return {pos: i, region: folded[i]}; 156 | } 157 | } 158 | 159 | function expand(cm, region) { 160 | cm.clearMarker(region.start); 161 | for (var i = 0; i < region.hidden.length; ++i) 162 | cm.showLine(region.hidden[i]); 163 | } 164 | 165 | return function(cm, line) { 166 | cm.operation(function() { 167 | var known = isFolded(cm, line); 168 | if (known) { 169 | folded.splice(known.pos, 1); 170 | expand(cm, known.region); 171 | } else { 172 | var end = rangeFinder(cm, line); 173 | if (end == null) return; 174 | var hidden = []; 175 | for (var i = line + 1; i < end; ++i) { 176 | var handle = cm.hideLine(i); 177 | if (handle) hidden.push(handle); 178 | } 179 | var first = cm.setMarker(line, markText); 180 | var region = {start: first, hidden: hidden}; 181 | cm.onDeleteLine(first, function() { expand(cm, region); }); 182 | folded.push(region); 183 | } 184 | }); 185 | }; 186 | }; 187 | -------------------------------------------------------------------------------- /docs/guide/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Roy documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Jan 7 18:12:13 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Roy' 44 | copyright = u'2012, Brian McKenna' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'Roydoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'Roy.tex', u'Roy Documentation', 187 | u'Brian McKenna', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'roy', u'Roy Documentation', 217 | [u'Brian McKenna'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'Roy', u'Roy Documentation', 231 | u'Brian McKenna', 'Roy', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /src/lexer.js: -------------------------------------------------------------------------------- 1 | var unicode = require('unicode-categories'), 2 | _ = require('underscore'); 3 | 4 | // http://es5.github.com/#x7.6 5 | // ECMAscript identifier starts with `$`, `_`, 6 | // or letter from (Lu Ll Lt Lm Lo Nl) unicode groups. 7 | // Then identifier can also be from groups (Nd, Mn, Mc, or Pc). 8 | // Roy identifier cannot have letter u03BB (greek lowercase lambda) 9 | // because it's used in anonymous functions. 10 | var IDENTIFIER = new RegExp( 11 | unicode.ECMA.identifier.source.replace('\\u03BB', '') 12 | ); 13 | 14 | var NUMBER = /^-?[0-9]+(\.[0-9]+)?([eE][\-\+]?[0-9]+)?/; 15 | var STRING = /^(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/; 16 | var COMMENT = /^\/\/.*/; 17 | var WHITESPACE = /^[^\n\S]+/; 18 | var INDENT = /^(?:\n[^\n\S]*)+/; 19 | var GENERIC = /^#([a-z]+)/; 20 | var SHEBANG = /^#!.*/; 21 | 22 | var keywordTokens = { 23 | 'true': 'BOOLEAN', 24 | 'false': 'BOOLEAN', 25 | 'Function': 'FUNCTION', 26 | 'let': 'LET', 27 | 'if': 'IF', 28 | 'instance': 'INSTANCE', 29 | 'then': 'THEN', 30 | 'else': 'ELSE', 31 | 'data': 'DATA', 32 | 'type': 'TYPE', 33 | 'typeclass': 'TYPECLASS', 34 | 'match': 'MATCH', 35 | 'case': 'CASE', 36 | 'do': 'DO', 37 | 'return': 'RETURN', 38 | 'with': 'WITH', 39 | 'where': 'WHERE' 40 | }; 41 | 42 | var indent; 43 | var indents; 44 | var tokens; 45 | var lineno; 46 | 47 | var identifierToken = function(chunk) { 48 | var token = IDENTIFIER.exec(chunk); 49 | 50 | if(token) { 51 | var value = token[0], 52 | name = keywordTokens[value] || 'IDENTIFIER'; 53 | 54 | tokens.push([name, value, lineno]); 55 | return token[0].length; 56 | } 57 | return 0; 58 | }; 59 | 60 | var numberToken = function(chunk) { 61 | var token = NUMBER.exec(chunk); 62 | if(token) { 63 | tokens.push(['NUMBER', token[0], lineno]); 64 | return token[0].length; 65 | } 66 | return 0; 67 | }; 68 | 69 | var stringToken = function(chunk) { 70 | var token = STRING.exec(chunk); 71 | if (token) { 72 | tokens.push(['STRING', token[0], lineno]); 73 | return token[0].length; 74 | 75 | } 76 | return 0; 77 | }; 78 | 79 | var genericToken = function(chunk) { 80 | var token = GENERIC.exec(chunk); 81 | if(token) { 82 | tokens.push(['GENERIC', token[1], lineno]); 83 | return token[0].length; 84 | } 85 | return 0; 86 | }; 87 | 88 | var commentToken = function(chunk) { 89 | var token = COMMENT.exec(chunk); 90 | if(token) { 91 | tokens.push(['COMMENT', token[0], lineno]); 92 | return token[0].length; 93 | } 94 | return 0; 95 | }; 96 | 97 | var whitespaceToken = function(chunk) { 98 | var token = WHITESPACE.exec(chunk); 99 | if(token) { 100 | return token[0].length; 101 | } 102 | return 0; 103 | }; 104 | 105 | // If an OUTDENT is followed by a line continuer, 106 | // the next TERMINATOR token is supressed. 107 | var lineContinuer = { 108 | "where": true 109 | }; 110 | 111 | var lineToken = function(chunk) { 112 | var token = INDENT.exec(chunk); 113 | if(token) { 114 | var lastNewline = token[0].lastIndexOf("\n") + 1; 115 | var size = token[0].length - lastNewline; 116 | if(size > indent) { 117 | indents.push(size); 118 | tokens.push(['INDENT', size - indent, lineno]); 119 | } else { 120 | if(size < indent) { 121 | var last = indents[indents.length - 1]; 122 | while(size < last) { 123 | tokens.push(['OUTDENT', last - size, lineno]); 124 | indents.pop(); 125 | last = indents[indents.length - 1]; 126 | } 127 | } 128 | if(tokens.length > 0) { 129 | var lookahead = IDENTIFIER.exec(chunk.slice(token[0].length)); 130 | 131 | if (!lookahead || !lineContinuer[lookahead[0]]) { 132 | tokens.push(['TERMINATOR', token[0].substring(0, lastNewline), lineno]); 133 | } 134 | } 135 | } 136 | 137 | indent = size; 138 | return token[0].length; 139 | } 140 | return 0; 141 | }; 142 | 143 | var literalToken = function(chunk) { 144 | var tag = chunk.slice(0, 1); 145 | var next; 146 | switch(tag) { 147 | case '<': 148 | next = chunk.slice(0, 2); 149 | if(next == '<=') { 150 | tokens.push(['COMPARE', next, lineno]); 151 | return 2; 152 | } else if(next == '<-') { 153 | tokens.push(['LEFTARROW', next, lineno]); 154 | return 2; 155 | } else if(next == '<<') { 156 | tokens.push(['MATH', next, lineno]); 157 | return 2; 158 | } 159 | tokens.push(['COMPARE', tag, lineno]); 160 | return 1; 161 | case '>': 162 | next = chunk.slice(0, 2); 163 | if(next == '>=') { 164 | tokens.push(['COMPARE', next, lineno]); 165 | return 2; 166 | } else if(next == '>>') { 167 | tokens.push(['MATH', next, lineno]); 168 | return 2; 169 | } 170 | tokens.push(['COMPARE', tag, lineno]); 171 | return 1; 172 | case '=': 173 | next = chunk.slice(0, 2); 174 | if(next == '==') { 175 | tokens.push(['COMPARE', next, lineno]); 176 | return 2; 177 | } 178 | tokens.push([tag, tag, lineno]); 179 | return 1; 180 | case '!': 181 | next = chunk.slice(0, 2); 182 | if(next == '!=') { 183 | tokens.push(['COMPARE', next, lineno]); 184 | return 2; 185 | } 186 | tokens.push([tag, tag, lineno]); 187 | return 1; 188 | case '*': 189 | case '/': 190 | case '%': 191 | tokens.push(['MATH', tag, lineno]); 192 | return 1; 193 | case '[': 194 | case '|': 195 | next = chunk.slice(0, 2); 196 | if(next == '||') { 197 | tokens.push(['BOOLOP', next, lineno]); 198 | return 2; 199 | } 200 | tokens.push([tag, tag, lineno]); 201 | return 1; 202 | case ')': 203 | if(tokens[tokens.length-1][0] == 'TERMINATOR') { 204 | tokens.pop(); 205 | } 206 | tokens.push([tag, tag, lineno]); 207 | return 1; 208 | case '+': 209 | next = chunk.slice(0, 2); 210 | if(next == '++') { 211 | tokens.push(['CONCAT', tag, lineno]); 212 | return 2; 213 | } 214 | tokens.push([tag, tag, lineno]); 215 | return 1; 216 | case '-': 217 | next = chunk.slice(0, 2); 218 | if (next == '->') { 219 | tokens.push(['RIGHTARROW', next, lineno]); 220 | return 2; 221 | } 222 | tokens.push([tag, tag, lineno]); 223 | return 1; 224 | case '&': 225 | next = chunk.slice(0, 2); 226 | if(next == '&&') { 227 | tokens.push(['BOOLOP', next, lineno]); 228 | return 2; 229 | } 230 | return 0; 231 | case 'λ': 232 | case '\\': 233 | tokens.push(['LAMBDA', tag, lineno]); 234 | return 1; 235 | case '←': 236 | tokens.push(['LEFTARROW', tag, lineno]); 237 | return 1; 238 | case '→': 239 | tokens.push(['RIGHTARROW', tag, lineno]); 240 | return 1; 241 | case '⇒': 242 | tokens.push(['RIGHTFATARROW', tag, lineno]); 243 | return 1; 244 | case '@': 245 | case ']': 246 | case ':': 247 | case '.': 248 | case ',': 249 | case '{': 250 | case '}': 251 | case '(': 252 | tokens.push([tag, tag, lineno]); 253 | return 1; 254 | } 255 | return 0; 256 | }; 257 | 258 | var shebangToken = function(chunk) { 259 | var token = SHEBANG.exec(chunk); 260 | if (token) { 261 | tokens.push(['SHEBANG', token[0], lineno]); 262 | return token[0].length; 263 | } 264 | return 0; 265 | }; 266 | 267 | var tokenise = function(source, tokenizers) { 268 | /*jshint boss:true*/ 269 | var i = 0, chunk; 270 | 271 | function getDiff(chunk) { 272 | return _.foldl(tokenizers, function(diff, tokenizer) { 273 | return diff ? diff : tokenizer.apply(tokenizer, [chunk]); 274 | }, 0); 275 | } 276 | 277 | while(chunk = source.slice(i)) { 278 | var diff = getDiff(chunk); 279 | if(!diff) { 280 | throw "Couldn't tokenise: " + chunk.substring(0, chunk.indexOf("\n") > -1 ? chunk.indexOf("\n") : chunk.length); 281 | } 282 | lineno += source.slice(i, i + diff).split('\n').length - 1; 283 | i += diff; 284 | } 285 | 286 | return tokens; 287 | }; 288 | 289 | exports.tokenise = function(source) { 290 | indent = 0; 291 | indents = []; 292 | tokens = []; 293 | lineno = 0; 294 | 295 | return tokenise(source, [identifierToken, numberToken, 296 | stringToken, genericToken, commentToken, whitespaceToken, 297 | lineToken, literalToken, shebangToken] 298 | ).concat([['EOF', '', lineno]]); 299 | }; 300 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | // ### Prune 4 | // 5 | // This will unchain variables until it gets to a type or variable without an 6 | // instance. See `unify` for some details about type variable instances. 7 | var prune = function(type) { 8 | if(type instanceof Variable && type.instance) { 9 | type.instance = prune(type.instance); 10 | return type.instance; 11 | } 12 | return type; 13 | }; 14 | exports.prune = prune; 15 | 16 | // ### Occurs check 17 | // 18 | // These functions check whether the type `t2` is equal to or contained within 19 | // the type `t1`. Used for checking recursive definitions in `unify` and 20 | // checking if a variable is non-generic in `fresh`. 21 | var occursInType = function(t1, t2) { 22 | t2 = prune(t2); 23 | if(t2 == t1) { 24 | return true; 25 | } else if(t2 instanceof ObjectType) { 26 | var types = []; 27 | for(var prop in t2.props) { 28 | types.push(t2.props[prop]); 29 | } 30 | return occursInTypeArray(t1, types); 31 | } else if(t2 instanceof BaseType) { 32 | return occursInTypeArray(t1, t2.types); 33 | } 34 | return false; 35 | }; 36 | exports.occursInType = occursInType; 37 | 38 | var occursInTypeArray = function(t1, types) { 39 | return _.any(types, function(t2) { 40 | return occursInType(t1, t2); 41 | }); 42 | }; 43 | 44 | // ## Type variable 45 | // 46 | // A type variable represents an parameter with an unknown type or any 47 | // polymorphic type. For example: 48 | // 49 | // fun id x = x 50 | // 51 | // Here, `id` has the polymorphic type `#a -> #a`. 52 | var Variable = function(idString) { 53 | if(!idString) { 54 | this.id = Variable.nextId; 55 | Variable.nextId++; 56 | } else { 57 | this.id = variableFromString(idString); 58 | } 59 | this.instance = null; 60 | }; 61 | Variable.nextId = 0; 62 | exports.Variable = Variable; 63 | 64 | // ### Fresh type 65 | // 66 | // Getting a "fresh" type will create a recursive copy. When a generic type 67 | // variable is encountered, a new variable is generated and substituted in. 68 | // 69 | // A fresh type is only returned when an identifier is found during analysis. 70 | // See `analyse` for some context. 71 | Variable.prototype.fresh = function(nonGeneric, mappings) { 72 | if(!mappings) mappings = {}; 73 | 74 | var type = prune(this); 75 | if(!(type instanceof Variable)) { 76 | return type.fresh(nonGeneric, mappings); 77 | } 78 | 79 | if(occursInTypeArray(type, nonGeneric)) { 80 | return type; 81 | } 82 | 83 | if(!mappings[type.id]) { 84 | mappings[type.id] = new Variable(); 85 | } 86 | return mappings[type.id]; 87 | }; 88 | 89 | var toChar = function(n) { 90 | return String.fromCharCode("a".charCodeAt(0) + n); 91 | }; 92 | // Type variables should look like `'a`. If the variable has an instance, that 93 | // should be used for the string instead. 94 | // 95 | // This is just bijective base 26. 96 | var variableToString = function(n) { 97 | var a = ''; 98 | if(n >= 26) { 99 | a = variableToString(n / 26 - 1); 100 | n = n % 26; 101 | } 102 | a += toChar(n); 103 | return a; 104 | }; 105 | Variable.prototype.toString = function() { 106 | if(!this.instance) { 107 | return "#" + variableToString(this.id); 108 | } 109 | return this.instance.toString(); 110 | }; 111 | 112 | var variableFromString = function(vs) { 113 | return _.reduce(_.map(vs.split(''), function(v, k) { 114 | return v.charCodeAt(0) - 'a'.charCodeAt(0) + 26 * k; 115 | }), function(accum, n) { 116 | return accum + n; 117 | }, 0); 118 | }; 119 | 120 | // ## Base type 121 | // 122 | // Base type for all specific types. Using this type as the prototype allows the 123 | // use of `instanceof` to detect a type variable or an actual type. 124 | var BaseType = function() { 125 | this.types = []; 126 | }; 127 | BaseType.prototype.toString = function() { 128 | return this.name; 129 | }; 130 | exports.BaseType = BaseType; 131 | 132 | // ## Specific types 133 | // 134 | // A `FunctionType` contains a `types` array. The last element represents the 135 | // return type. Each element before represents an argument type. 136 | var FunctionType = function(types, typeClasses) { 137 | this.types = types; 138 | this.typeClasses = typeClasses || []; 139 | }; 140 | FunctionType.prototype = new BaseType(); 141 | FunctionType.prototype.name = "Function"; 142 | FunctionType.prototype.fresh = function(nonGeneric, mappings) { 143 | if(!mappings) mappings = {}; 144 | 145 | var newTypeClasses = _.map(this.typeClasses, function(typeClass) { 146 | return typeClass.fresh(nonGeneric, mappings); 147 | }); 148 | 149 | return new FunctionType(_.map(this.types, function(t) { 150 | return t.fresh(nonGeneric, mappings); 151 | }), newTypeClasses); 152 | }; 153 | FunctionType.prototype.toString = function() { 154 | return this.name + "(" + _.map(this.types, function(type) { 155 | return type.toString(); 156 | }).join(', ') + ")"; 157 | }; 158 | exports.FunctionType = FunctionType; 159 | 160 | var NumberType = function() {}; 161 | NumberType.prototype = new BaseType(); 162 | NumberType.prototype.fresh = function() { 163 | return this; 164 | }; 165 | NumberType.prototype.name = "Number"; 166 | exports.NumberType = NumberType; 167 | 168 | var StringType = function() {}; 169 | StringType.prototype = new BaseType(); 170 | StringType.prototype.fresh = function() { 171 | return this; 172 | }; 173 | StringType.prototype.name = "String"; 174 | exports.StringType = StringType; 175 | 176 | var BooleanType = function() {}; 177 | BooleanType.prototype = new BaseType(); 178 | BooleanType.prototype.fresh = function() { 179 | return this; 180 | }; 181 | BooleanType.prototype.name = "Boolean"; 182 | exports.BooleanType = BooleanType; 183 | 184 | var ArrayType = function(type) { 185 | this.type = type; 186 | this.types = [type]; 187 | }; 188 | ArrayType.prototype = new BaseType(); 189 | ArrayType.prototype.name = "Array"; 190 | ArrayType.prototype.fresh = function(nonGeneric, mappings) { 191 | if(!mappings) mappings = {}; 192 | return new ArrayType(this.type.fresh(nonGeneric, mappings)); 193 | }; 194 | ArrayType.prototype.toString = function() { 195 | return '[' + this.type.toString() + ']'; 196 | }; 197 | exports.ArrayType = ArrayType; 198 | 199 | var ObjectType = function(props) { 200 | this.props = props; 201 | }; 202 | ObjectType.prototype = new BaseType(); 203 | ObjectType.prototype.name = "Object"; 204 | ObjectType.prototype.fresh = function(nonGeneric, mappings) { 205 | var props = {}; 206 | var name; 207 | for(name in this.props) { 208 | props[name] = this.props[name].fresh(nonGeneric, mappings); 209 | } 210 | var freshed = new ObjectType(props); 211 | if(this.aliased) freshed.aliased = this.aliased; 212 | return freshed; 213 | }; 214 | ObjectType.prototype.getPropertyType = function(prop) { 215 | return this.props[prop]; 216 | }; 217 | ObjectType.prototype.toString = function() { 218 | var strs = []; 219 | var p; 220 | var n; 221 | var e; 222 | for(p in this.props) { 223 | if (_.isString(p)) { 224 | // Replace any double quotes by their escaped version. 225 | // Also replace any escaped single quotes. 226 | e = p.replace(/"|\\"/g, '\\"').replace(/(\\\\)|\\(')/g, '$1$2'); 227 | // Normalize the string format to double quotes. 228 | n = e.replace(/^'(.*)'$|^\\"(.*)\\"$/, '"$1$2"'); 229 | strs.push(n + ': ' + this.props[p].toString()); 230 | } else { 231 | strs.push(p + ': ' + this.props[p].toString()); 232 | } 233 | } 234 | return '{' + strs.join(', ') + '}'; 235 | }; 236 | exports.ObjectType = ObjectType; 237 | 238 | var TagNameType = function(name) { 239 | this.name = name; 240 | }; 241 | TagNameType.prototype = new BaseType(); 242 | TagNameType.prototype.fresh = function() { 243 | return new TagNameType(this.name); 244 | }; 245 | exports.TagNameType = TagNameType; 246 | 247 | var TagType = function(types) { 248 | this.types = types; 249 | this.name = types[0].toString(); 250 | }; 251 | TagType.prototype = new BaseType(); 252 | TagType.prototype.fresh = function(nonGeneric, mappings) { 253 | if(!mappings) mappings = {}; 254 | return new TagType(_.map(this.types, function(t) { 255 | return t.fresh(nonGeneric, mappings); 256 | })); 257 | }; 258 | TagType.prototype.toString = function() { 259 | return _.map(this.types, function(t) { 260 | return t.toString(); 261 | }).join(' '); 262 | }; 263 | exports.TagType = TagType; 264 | 265 | var UnitType = function() {}; 266 | UnitType.prototype = new BaseType(); 267 | UnitType.prototype.name = "Unit"; 268 | UnitType.prototype.fresh = function() { 269 | return this; 270 | }; 271 | exports.UnitType = UnitType; 272 | 273 | var NativeType = function() {}; 274 | NativeType.prototype = new BaseType(); 275 | NativeType.prototype.name = "Native"; 276 | NativeType.prototype.fresh = function() { 277 | return this; 278 | }; 279 | exports.NativeType = NativeType; 280 | 281 | var TypeClassType = function(name, type) { 282 | this.name = name; 283 | this.type = type; 284 | this.types = [type]; 285 | }; 286 | TypeClassType.prototype = new BaseType(); 287 | TypeClassType.prototype.fresh = function(nonGeneric, mappings) { 288 | if(!mappings) mappings = {}; 289 | return new TypeClassType(this.name, this.type.fresh(nonGeneric, mappings)); 290 | }; 291 | TypeClassType.prototype.toString = function() { 292 | return this.name + ' ' + this.type.toString(); 293 | }; 294 | exports.TypeClassType = TypeClassType; 295 | -------------------------------------------------------------------------------- /site/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Roy 5 | 6 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 82 | 83 | 84 | 85 |
86 |
87 |

Roy

88 |
89 |
90 |
91 |

About

92 |

Roy is an experimental programming language that targets JavaScript. It 93 | tries to meld JavaScript semantics with some features common in static 94 | functional languages:

95 | 105 |

Try the current version below. The code is on GitHub. Follow @roylangjs for news on development.

106 |

Try

107 |

Roy input

108 |
109 | 122 |
123 |
124 |

JavaScript

125 |

126 |       
127 |       

Output

128 |

129 |       

Resources

130 | 138 |

Editor Support

139 | 145 | 146 | 147 |
148 | 151 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /site/codemirror2/lib/util/formatting.js: -------------------------------------------------------------------------------- 1 | // ============== Formatting extensions ============================ 2 | // A common storage for all mode-specific formatting features 3 | if (!CodeMirror.modeExtensions) CodeMirror.modeExtensions = {}; 4 | 5 | // Returns the extension of the editor's current mode 6 | CodeMirror.defineExtension("getModeExt", function () { 7 | var mname = CodeMirror.resolveMode(this.getOption("mode")).name; 8 | var ext = CodeMirror.modeExtensions[mname]; 9 | if (!ext) throw new Error("No extensions found for mode " + mname); 10 | return ext; 11 | }); 12 | 13 | // If the current mode is 'htmlmixed', returns the extension of a mode located at 14 | // the specified position (can be htmlmixed, css or javascript). Otherwise, simply 15 | // returns the extension of the editor's current mode. 16 | CodeMirror.defineExtension("getModeExtAtPos", function (pos) { 17 | var token = this.getTokenAt(pos); 18 | if (token && token.state && token.state.mode) 19 | return CodeMirror.modeExtensions[token.state.mode == "html" ? "htmlmixed" : token.state.mode]; 20 | else 21 | return this.getModeExt(); 22 | }); 23 | 24 | // Comment/uncomment the specified range 25 | CodeMirror.defineExtension("commentRange", function (isComment, from, to) { 26 | var curMode = this.getModeExtAtPos(this.getCursor()); 27 | if (isComment) { // Comment range 28 | var commentedText = this.getRange(from, to); 29 | this.replaceRange(curMode.commentStart + this.getRange(from, to) + curMode.commentEnd 30 | , from, to); 31 | if (from.line == to.line && from.ch == to.ch) { // An empty comment inserted - put cursor inside 32 | this.setCursor(from.line, from.ch + curMode.commentStart.length); 33 | } 34 | } 35 | else { // Uncomment range 36 | var selText = this.getRange(from, to); 37 | var startIndex = selText.indexOf(curMode.commentStart); 38 | var endIndex = selText.lastIndexOf(curMode.commentEnd); 39 | if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { 40 | // Take string till comment start 41 | selText = selText.substr(0, startIndex) 42 | // From comment start till comment end 43 | + selText.substring(startIndex + curMode.commentStart.length, endIndex) 44 | // From comment end till string end 45 | + selText.substr(endIndex + curMode.commentEnd.length); 46 | } 47 | this.replaceRange(selText, from, to); 48 | } 49 | }); 50 | 51 | // Applies automatic mode-aware indentation to the specified range 52 | CodeMirror.defineExtension("autoIndentRange", function (from, to) { 53 | var cmInstance = this; 54 | this.operation(function () { 55 | for (var i = from.line; i <= to.line; i++) { 56 | cmInstance.indentLine(i, "smart"); 57 | } 58 | }); 59 | }); 60 | 61 | // Applies automatic formatting to the specified range 62 | CodeMirror.defineExtension("autoFormatRange", function (from, to) { 63 | var absStart = this.indexFromPos(from); 64 | var absEnd = this.indexFromPos(to); 65 | // Insert additional line breaks where necessary according to the 66 | // mode's syntax 67 | var res = this.getModeExt().autoFormatLineBreaks(this.getValue(), absStart, absEnd); 68 | var cmInstance = this; 69 | 70 | // Replace and auto-indent the range 71 | this.operation(function () { 72 | cmInstance.replaceRange(res, from, to); 73 | var startLine = cmInstance.posFromIndex(absStart).line; 74 | var endLine = cmInstance.posFromIndex(absStart + res.length).line; 75 | for (var i = startLine; i <= endLine; i++) { 76 | cmInstance.indentLine(i, "smart"); 77 | } 78 | }); 79 | }); 80 | 81 | // Define extensions for a few modes 82 | 83 | CodeMirror.modeExtensions["css"] = { 84 | commentStart: "/*", 85 | commentEnd: "*/", 86 | wordWrapChars: [";", "\\{", "\\}"], 87 | autoFormatLineBreaks: function (text) { 88 | return text.replace(new RegExp("(;|\\{|\\})([^\r\n])", "g"), "$1\n$2"); 89 | } 90 | }; 91 | 92 | CodeMirror.modeExtensions["javascript"] = { 93 | commentStart: "/*", 94 | commentEnd: "*/", 95 | wordWrapChars: [";", "\\{", "\\}"], 96 | 97 | getNonBreakableBlocks: function (text) { 98 | var nonBreakableRegexes = [ 99 | new RegExp("for\\s*?\\(([\\s\\S]*?)\\)"), 100 | new RegExp("'([\\s\\S]*?)('|$)"), 101 | new RegExp("\"([\\s\\S]*?)(\"|$)"), 102 | new RegExp("//.*([\r\n]|$)") 103 | ]; 104 | var nonBreakableBlocks = new Array(); 105 | for (var i = 0; i < nonBreakableRegexes.length; i++) { 106 | var curPos = 0; 107 | while (curPos < text.length) { 108 | var m = text.substr(curPos).match(nonBreakableRegexes[i]); 109 | if (m != null) { 110 | nonBreakableBlocks.push({ 111 | start: curPos + m.index, 112 | end: curPos + m.index + m[0].length 113 | }); 114 | curPos += m.index + Math.max(1, m[0].length); 115 | } 116 | else { // No more matches 117 | break; 118 | } 119 | } 120 | } 121 | nonBreakableBlocks.sort(function (a, b) { 122 | return a.start - b.start; 123 | }); 124 | 125 | return nonBreakableBlocks; 126 | }, 127 | 128 | autoFormatLineBreaks: function (text) { 129 | var curPos = 0; 130 | var reLinesSplitter = new RegExp("(;|\\{|\\})([^\r\n])", "g"); 131 | var nonBreakableBlocks = this.getNonBreakableBlocks(text); 132 | if (nonBreakableBlocks != null) { 133 | var res = ""; 134 | for (var i = 0; i < nonBreakableBlocks.length; i++) { 135 | if (nonBreakableBlocks[i].start > curPos) { // Break lines till the block 136 | res += text.substring(curPos, nonBreakableBlocks[i].start).replace(reLinesSplitter, "$1\n$2"); 137 | curPos = nonBreakableBlocks[i].start; 138 | } 139 | if (nonBreakableBlocks[i].start <= curPos 140 | && nonBreakableBlocks[i].end >= curPos) { // Skip non-breakable block 141 | res += text.substring(curPos, nonBreakableBlocks[i].end); 142 | curPos = nonBreakableBlocks[i].end; 143 | } 144 | } 145 | if (curPos < text.length - 1) { 146 | res += text.substr(curPos).replace(reLinesSplitter, "$1\n$2"); 147 | } 148 | return res; 149 | } 150 | else { 151 | return text.replace(reLinesSplitter, "$1\n$2"); 152 | } 153 | } 154 | }; 155 | 156 | CodeMirror.modeExtensions["xml"] = { 157 | commentStart: "", 159 | wordWrapChars: [">"], 160 | 161 | autoFormatLineBreaks: function (text) { 162 | var lines = text.split("\n"); 163 | var reProcessedPortion = new RegExp("(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)"); 164 | var reOpenBrackets = new RegExp("<", "g"); 165 | var reCloseBrackets = new RegExp("(>)([^\r\n])", "g"); 166 | for (var i = 0; i < lines.length; i++) { 167 | var mToProcess = lines[i].match(reProcessedPortion); 168 | if (mToProcess != null && mToProcess.length > 3) { // The line starts with whitespaces and ends with whitespaces 169 | lines[i] = mToProcess[1] 170 | + mToProcess[2].replace(reOpenBrackets, "\n$&").replace(reCloseBrackets, "$1\n$2") 171 | + mToProcess[3]; 172 | continue; 173 | } 174 | } 175 | 176 | return lines.join("\n"); 177 | } 178 | }; 179 | 180 | CodeMirror.modeExtensions["htmlmixed"] = { 181 | commentStart: "", 183 | wordWrapChars: [">", ";", "\\{", "\\}"], 184 | 185 | getModeInfos: function (text, absPos) { 186 | var modeInfos = new Array(); 187 | modeInfos[0] = 188 | { 189 | pos: 0, 190 | modeExt: CodeMirror.modeExtensions["xml"], 191 | modeName: "xml" 192 | }; 193 | 194 | var modeMatchers = new Array(); 195 | modeMatchers[0] = 196 | { 197 | regex: new RegExp("]*>([\\s\\S]*?)(]*>|$)", "i"), 198 | modeExt: CodeMirror.modeExtensions["css"], 199 | modeName: "css" 200 | }; 201 | modeMatchers[1] = 202 | { 203 | regex: new RegExp("]*>([\\s\\S]*?)(]*>|$)", "i"), 204 | modeExt: CodeMirror.modeExtensions["javascript"], 205 | modeName: "javascript" 206 | }; 207 | 208 | var lastCharPos = (typeof (absPos) !== "undefined" ? absPos : text.length - 1); 209 | // Detect modes for the entire text 210 | for (var i = 0; i < modeMatchers.length; i++) { 211 | var curPos = 0; 212 | while (curPos <= lastCharPos) { 213 | var m = text.substr(curPos).match(modeMatchers[i].regex); 214 | if (m != null) { 215 | if (m.length > 1 && m[1].length > 0) { 216 | // Push block begin pos 217 | var blockBegin = curPos + m.index + m[0].indexOf(m[1]); 218 | modeInfos.push( 219 | { 220 | pos: blockBegin, 221 | modeExt: modeMatchers[i].modeExt, 222 | modeName: modeMatchers[i].modeName 223 | }); 224 | // Push block end pos 225 | modeInfos.push( 226 | { 227 | pos: blockBegin + m[1].length, 228 | modeExt: modeInfos[0].modeExt, 229 | modeName: modeInfos[0].modeName 230 | }); 231 | curPos += m.index + m[0].length; 232 | continue; 233 | } 234 | else { 235 | curPos += m.index + Math.max(m[0].length, 1); 236 | } 237 | } 238 | else { // No more matches 239 | break; 240 | } 241 | } 242 | } 243 | // Sort mode infos 244 | modeInfos.sort(function sortModeInfo(a, b) { 245 | return a.pos - b.pos; 246 | }); 247 | 248 | return modeInfos; 249 | }, 250 | 251 | autoFormatLineBreaks: function (text, startPos, endPos) { 252 | var modeInfos = this.getModeInfos(text); 253 | var reBlockStartsWithNewline = new RegExp("^\\s*?\n"); 254 | var reBlockEndsWithNewline = new RegExp("\n\\s*?$"); 255 | var res = ""; 256 | // Use modes info to break lines correspondingly 257 | if (modeInfos.length > 1) { // Deal with multi-mode text 258 | for (var i = 1; i <= modeInfos.length; i++) { 259 | var selStart = modeInfos[i - 1].pos; 260 | var selEnd = (i < modeInfos.length ? modeInfos[i].pos : endPos); 261 | 262 | if (selStart >= endPos) { // The block starts later than the needed fragment 263 | break; 264 | } 265 | if (selStart < startPos) { 266 | if (selEnd <= startPos) { // The block starts earlier than the needed fragment 267 | continue; 268 | } 269 | selStart = startPos; 270 | } 271 | if (selEnd > endPos) { 272 | selEnd = endPos; 273 | } 274 | var textPortion = text.substring(selStart, selEnd); 275 | if (modeInfos[i - 1].modeName != "xml") { // Starting a CSS or JavaScript block 276 | if (!reBlockStartsWithNewline.test(textPortion) 277 | && selStart > 0) { // The block does not start with a line break 278 | textPortion = "\n" + textPortion; 279 | } 280 | if (!reBlockEndsWithNewline.test(textPortion) 281 | && selEnd < text.length - 1) { // The block does not end with a line break 282 | textPortion += "\n"; 283 | } 284 | } 285 | res += modeInfos[i - 1].modeExt.autoFormatLineBreaks(textPortion); 286 | } 287 | } 288 | else { // Single-mode text 289 | res = modeInfos[0].modeExt.autoFormatLineBreaks(text.substring(startPos, endPos)); 290 | } 291 | 292 | return res; 293 | } 294 | }; 295 | -------------------------------------------------------------------------------- /src/nodes.js: -------------------------------------------------------------------------------- 1 | exports.nodes = { 2 | Module: function (body) { 3 | this.body = body; 4 | 5 | this.accept = function(a) { 6 | if(a.visitModule) { 7 | return a.visitModule(this); 8 | } 9 | }; 10 | }, 11 | Arg: function(name, type) { 12 | this.name = name; 13 | 14 | // Optional 15 | this.type = type; 16 | 17 | this.accept = function(a) { 18 | if(a.visitArg) { 19 | return a.visitArg(this); 20 | } 21 | }; 22 | }, 23 | Function: function(name, args, body, type, whereDecls) { 24 | this.name = name; 25 | this.args = args; 26 | this.body = body; 27 | 28 | // Optional 29 | this.type = type; 30 | this.whereDecls = whereDecls || []; 31 | 32 | this.accept = function(a) { 33 | if(a.visitFunction) { 34 | return a.visitFunction(this); 35 | } 36 | }; 37 | }, 38 | Data: function(name, args, tags) { 39 | this.name = name; 40 | this.args = args; 41 | this.tags = tags; 42 | 43 | this.accept = function(a) { 44 | if(a.visitData) { 45 | return a.visitData(this); 46 | } 47 | }; 48 | }, 49 | Type: function(name, value) { 50 | this.name = name; 51 | this.value = value; 52 | 53 | this.accept = function(a) { 54 | if(a.visitType) { 55 | return a.visitType(this); 56 | } 57 | }; 58 | }, 59 | TypeClass: function(name, generic, types) { 60 | this.name = name; 61 | this.generic = generic; 62 | this.types = types; 63 | 64 | this.accept = function(a) { 65 | if(a.visitTypeClass) { 66 | return a.visitTypeClass(this); 67 | } 68 | }; 69 | }, 70 | Instance: function(name, typeClassName, typeName, object) { 71 | this.name = name; 72 | this.typeClassName = typeClassName; 73 | this.typeName = typeName; 74 | this.object = object; 75 | 76 | this.accept = function(a) { 77 | if(a.visitInstance) { 78 | return a.visitInstance(this); 79 | } 80 | }; 81 | }, 82 | Generic: function(value) { 83 | this.value = value; 84 | 85 | this.accept = function(a) { 86 | if(a.visitGeneric) { 87 | return a.visitGeneric(this); 88 | } 89 | }; 90 | }, 91 | TypeFunction: function(args) { 92 | this.args = args; 93 | 94 | this.accept = function(a) { 95 | if(a.visitTypeFunction) { 96 | return a.visitTypeFunction(this); 97 | } 98 | }; 99 | }, 100 | TypeName: function(value, args) { 101 | this.value = value; 102 | this.args = args; 103 | 104 | this.accept = function(a) { 105 | if(a.visitTypeName) { 106 | return a.visitTypeName(this); 107 | } 108 | }; 109 | }, 110 | TypeObject: function(values) { 111 | this.values = values; 112 | 113 | this.accept = function(a) { 114 | if(a.visitTypeObject) { 115 | return a.visitTypeObject(this); 116 | } 117 | }; 118 | }, 119 | TypeArray: function(value) { 120 | this.value = value; 121 | 122 | this.accept = function(a) { 123 | if(a.visitTypeArray) { 124 | return a.visitTypeArray(this); 125 | } 126 | }; 127 | }, 128 | Return: function(value) { 129 | this.value = value; 130 | 131 | this.accept = function(a) { 132 | if(a.visitReturn) { 133 | return a.visitReturn(this); 134 | } 135 | }; 136 | }, 137 | Bind: function(name, value) { 138 | this.name = name; 139 | this.value = value; 140 | 141 | // Set in compile stage 142 | this.rest = []; 143 | 144 | this.accept = function(a) { 145 | if(a.visitBind) { 146 | return a.visitBind(this); 147 | } 148 | }; 149 | }, 150 | Do: function(value, body) { 151 | this.value = value; 152 | this.body = body; 153 | 154 | this.accept = function(a) { 155 | if(a.visitDo) { 156 | return a.visitDo(this); 157 | } 158 | }; 159 | }, 160 | Match: function(value, cases) { 161 | this.value = value; 162 | this.cases = cases; 163 | 164 | this.accept = function(a) { 165 | if(a.visitMatch) { 166 | return a.visitMatch(this); 167 | } 168 | }; 169 | }, 170 | Case: function(pattern, value) { 171 | this.pattern = pattern; 172 | this.value = value; 173 | 174 | this.accept = function(a) { 175 | if(a.visitCase) { 176 | return a.visitCase(this); 177 | } 178 | }; 179 | }, 180 | Tag: function(name, vars) { 181 | this.name = name; 182 | this.vars = vars; 183 | 184 | this.accept = function(a) { 185 | if(a.visitTag) { 186 | return a.visitTag(this); 187 | } 188 | }; 189 | }, 190 | Pattern: function(tag, vars) { 191 | this.tag = tag; 192 | this.vars = vars; 193 | 194 | this.accept = function(a) { 195 | if(a.visitPattern) { 196 | return a.visitPattern(this); 197 | } 198 | }; 199 | }, 200 | Assignment: function(name, value) { 201 | this.name = name; 202 | this.value = value; 203 | 204 | this.accept = function(a) { 205 | if(a.visitAssignment) { 206 | return a.visitAssignment(this); 207 | } 208 | }; 209 | }, 210 | Let: function(name, value, type) { 211 | this.name = name; 212 | this.value = value; 213 | 214 | // Optional 215 | this.type = type; 216 | 217 | this.accept = function(a) { 218 | if(a.visitLet) { 219 | return a.visitLet(this); 220 | } 221 | }; 222 | }, 223 | Call: function(func, args) { 224 | this.func = func; 225 | this.args = args; 226 | 227 | this.accept = function(a) { 228 | if(a.visitCall) { 229 | return a.visitCall(this); 230 | } 231 | }; 232 | }, 233 | IfThenElse: function(condition, ifTrue, ifFalse) { 234 | this.condition = condition; 235 | this.ifTrue = ifTrue; 236 | this.ifFalse = ifFalse; 237 | 238 | this.accept = function(a) { 239 | if(a.visitIfThenElse) { 240 | return a.visitIfThenElse(this); 241 | } 242 | }; 243 | }, 244 | Comment: function(value) { 245 | this.value = value.slice(2); // Remove the slashes, because escodegen adds those back for us 246 | 247 | this.accept = function(a) { 248 | if(a.visitComment) { 249 | return a.visitComment(this); 250 | } 251 | }; 252 | }, 253 | PropertyAccess: function(value, property) { 254 | this.value = value; 255 | this.property = property; 256 | 257 | this.accept = function(a) { 258 | if(a.visitPropertyAccess) { 259 | return a.visitPropertyAccess(this); 260 | } 261 | }; 262 | }, 263 | Access: function(value, property) { 264 | this.value = value; 265 | this.property = property; 266 | 267 | this.accept = function(a) { 268 | if(a.visitAccess) { 269 | return a.visitAccess(this); 270 | } 271 | }; 272 | }, 273 | UnaryBooleanOperator: function(name, value) { 274 | this.name = name; 275 | this.value = value; 276 | 277 | this.accept = function(a) { 278 | if(a.visitUnaryBooleanOperator) { 279 | return a.visitUnaryBooleanOperator(this); 280 | } 281 | }; 282 | }, 283 | BinaryGenericOperator: function(name, left, right) { 284 | this.name = name; 285 | this.left = left; 286 | this.right = right; 287 | 288 | this.accept = function(a) { 289 | if(a.visitBinaryGenericOperator) { 290 | return a.visitBinaryGenericOperator(this); 291 | } 292 | }; 293 | }, 294 | BinaryNumberOperator: function(name, left, right) { 295 | this.name = name; 296 | this.left = left; 297 | this.right = right; 298 | 299 | this.accept = function(a) { 300 | if(a.visitBinaryNumberOperator) { 301 | return a.visitBinaryNumberOperator(this); 302 | } 303 | }; 304 | }, 305 | BinaryBooleanOperator: function(name, left, right) { 306 | this.name = name; 307 | this.left = left; 308 | this.right = right; 309 | 310 | this.accept = function(a) { 311 | if(a.visitBinaryBooleanOperator) { 312 | return a.visitBinaryBooleanOperator(this); 313 | } 314 | }; 315 | }, 316 | BinaryStringOperator: function(name, left, right) { 317 | this.name = name; 318 | this.left = left; 319 | this.right = right; 320 | 321 | this.accept = function(a) { 322 | if(a.visitBinaryStringOperator) { 323 | return a.visitBinaryStringOperator(this); 324 | } 325 | }; 326 | }, 327 | With: function(left, right) { 328 | this.left = left; 329 | this.right = right; 330 | 331 | this.accept = function(a) { 332 | if(a.visitWith) { 333 | return a.visitWith(this); 334 | } 335 | }; 336 | }, 337 | Identifier: function(value) { 338 | this.value = value; 339 | 340 | this.accept = function(a) { 341 | if(a.visitIdentifier) { 342 | return a.visitIdentifier(this); 343 | } 344 | }; 345 | }, 346 | Tuple: function(values) { 347 | this.values= values; 348 | 349 | this.accept = function(a) { 350 | if(a.visitTuple) { 351 | return a.visitTuple(this); 352 | } 353 | }; 354 | }, 355 | Number: function(value) { 356 | this.value = value; 357 | 358 | this.accept = function(a) { 359 | if(a.visitNumber) { 360 | return a.visitNumber(this); 361 | } 362 | }; 363 | }, 364 | String: function(value) { 365 | this.value = value; 366 | 367 | this.accept = function(a) { 368 | if(a.visitString) { 369 | return a.visitString(this); 370 | } 371 | }; 372 | }, 373 | Boolean: function(value) { 374 | this.value = value; 375 | 376 | this.accept = function(a) { 377 | if(a.visitBoolean) { 378 | return a.visitBoolean(this); 379 | } 380 | }; 381 | }, 382 | Array: function(values) { 383 | this.values = values; 384 | 385 | this.accept = function(a) { 386 | if(a.visitArray) { 387 | return a.visitArray(this); 388 | } 389 | }; 390 | }, 391 | Object: function(values) { 392 | this.values = values; 393 | 394 | this.accept = function(a) { 395 | if(a.visitObject) { 396 | return a.visitObject(this); 397 | } 398 | }; 399 | } 400 | }; 401 | -------------------------------------------------------------------------------- /src/freeVariables.js: -------------------------------------------------------------------------------- 1 | /*jshint expr:true*/ 2 | var _ = require('underscore'); 3 | var nodes = require('./nodes'); 4 | 5 | var fv = {}; 6 | 7 | /** @typedef {node.Function|node.Instance|node.Return|node.Bind|node.Do|node.Match|node.Case|node.Assignment|node.Let|node.Call|node.IfThenElse|node.Comment|node.PropertyAccess|node.Access|node.BinaryGenericOperator|node.BinaryNumberOperator|node.BinaryBooleanOperator|node.BinaryStringOperator|node.With|node.Identifier|node.Tuple|node.Number|node.String|node.Boolean|node.Array|node.Object} */ 8 | fv.Node; 9 | 10 | /** 11 | * Returns free variables in given node. 12 | * 13 | * @param {fv.Node} node Subject node. 14 | * @return {!Object.} A set of free variable names. 15 | */ 16 | function getFreeVariables(node) { 17 | var visitor = { 18 | visitFunction: function(node) { 19 | var bodyFreeVariables = getFreeVariablesOfBlock(node.body); 20 | 21 | _.each(node.whereDecls, function(whereDecl) { 22 | _.extend(bodyFreeVariables, whereDecl.accept(visitor)); 23 | }); 24 | 25 | delete bodyFreeVariables[node.name]; 26 | 27 | _.each(node.args, function(arg) { 28 | delete bodyFreeVariables[arg.name]; 29 | }); 30 | 31 | _.each(node.whereDecls, function(whereDecl) { 32 | _.each(getBindingVariables(whereDecl), function(value, name) { 33 | delete bodyFreeVariables[name]; 34 | }); 35 | }); 36 | 37 | return bodyFreeVariables; 38 | }, 39 | visitInstance: function(node) { 40 | return node.object.accept(visitor); 41 | }, 42 | visitReturn: function(node) { 43 | return node.value.accept(visitor); 44 | }, 45 | visitBind: function(node) { 46 | // Note: Bind is not recursive. 47 | return node.value.accept(visitor); 48 | }, 49 | visitDo: function(node) { 50 | var variables = {}; 51 | 52 | _.extend(variables, node.value.accept(visitor)); 53 | _.extend(variables, getFreeVariablesOfBlock(node.body)); 54 | 55 | return variables; 56 | }, 57 | visitMatch: function(node) { 58 | var variables = {}; 59 | 60 | _.extend(variables, node.value.accept(visitor)); 61 | 62 | _.each(node.cases, function(caseNode) { 63 | _.extend(variables, caseNode.accept(visitor)); 64 | }); 65 | 66 | return variables; 67 | }, 68 | visitCase: function(node) { 69 | var variables = {}; 70 | 71 | _.extend(variables, node.value.accept(visitor)); 72 | 73 | var variableGatherer = { 74 | visitIdentifier: function(identifier) { 75 | if (identifier.value === '_') { 76 | return []; 77 | } else { 78 | return [identifier.value]; 79 | } 80 | }, 81 | visitPattern: function(pattern) { 82 | return _.flatten(_.map(pattern.vars, function(subPattern) { 83 | return subPattern.accept(variableGatherer); 84 | })); 85 | } 86 | }; 87 | 88 | _.each(node.pattern.accept(variableGatherer), function(value, variable) { 89 | delete variables[variable]; 90 | }); 91 | 92 | return variables; 93 | }, 94 | visitAssignment: function(node) { 95 | var variables = node.value.accept(visitor); 96 | 97 | delete variables[node.name]; 98 | 99 | return variables; 100 | }, 101 | visitLet: function(node) { 102 | var variables = node.value.accept(visitor); 103 | 104 | delete variables[node.name]; 105 | 106 | return variables; 107 | }, 108 | visitCall: function(node) { 109 | var variables = {}; 110 | 111 | _.extend(variables, node.func.accept(visitor)); 112 | _.each(node.args, function(arg) { 113 | _.extend(variables, arg.accept(visitor)); 114 | }); 115 | 116 | return variables; 117 | }, 118 | visitIfThenElse: function(node) { 119 | var variables = {}; 120 | 121 | _.extend(variables, node.condition.accept(visitor)); 122 | 123 | _.each(node.ifTrue, function(line) { 124 | _.extend(variables, line.accept(visitor)); 125 | }); 126 | _.each(node.ifFalse, function(line) { 127 | _.extend(variables, line.accept(visitor)); 128 | }); 129 | 130 | return variables; 131 | }, 132 | visitComment: function(node) { 133 | return {}; 134 | }, 135 | visitPropertyAccess: function(node) { 136 | return node.value.accept(visitor); 137 | }, 138 | visitAccess: function(node) { 139 | var variables = {}; 140 | 141 | _.extend(variables, node.value.accept(visitor)); 142 | _.extend(variables, node.property.accept(visitor)); 143 | 144 | return variables; 145 | }, 146 | visitBinaryGenericOperator: function(node) { 147 | var variables = {}; 148 | 149 | _.extend(variables, node.left.accept(visitor)); 150 | _.extend(variables, node.right.accept(visitor)); 151 | 152 | return variables; 153 | }, 154 | visitBinaryNumberOperator: function(node) { 155 | var variables = {}; 156 | 157 | _.extend(variables, node.left.accept(visitor)); 158 | _.extend(variables, node.right.accept(visitor)); 159 | 160 | return variables; 161 | }, 162 | visitBinaryBooleanOperator: function(node) { 163 | var variables = {}; 164 | 165 | _.extend(variables, node.left.accept(visitor)); 166 | _.extend(variables, node.right.accept(visitor)); 167 | 168 | return variables; 169 | }, 170 | visitBinaryStringOperator: function(node) { 171 | var variables = {}; 172 | 173 | _.extend(variables, node.left.accept(visitor)); 174 | _.extend(variables, node.right.accept(visitor)); 175 | 176 | return variables; 177 | }, 178 | visitWith: function(node) { 179 | var variables = {}; 180 | 181 | _.extend(variables, node.left.accept(visitor)); 182 | _.extend(variables, node.right.accept(visitor)); 183 | 184 | return variables; 185 | }, 186 | visitIdentifier: function(node) { 187 | var variables = {}; 188 | 189 | variables[node.value] = true; 190 | 191 | return variables; 192 | }, 193 | visitTuple: function(node) { 194 | var variables = {}; 195 | 196 | _.each(node.values, function(value) { 197 | _.extend(variables, value.accept(visitor)); 198 | }); 199 | 200 | return variables; 201 | }, 202 | visitNumber: function(node) { 203 | return {}; 204 | }, 205 | visitString: function(node) { 206 | return {}; 207 | }, 208 | visitBoolean: function(node) { 209 | return {}; 210 | }, 211 | visitArray: function(node) { 212 | var variables = {}; 213 | 214 | _.each(node.values, function(value) { 215 | _.extend(variables, value.accept(visitor)); 216 | }); 217 | 218 | return variables; 219 | }, 220 | visitObject: function(node) { 221 | var variables = {}; 222 | 223 | _.each(node.values, function(value) { 224 | _.extend(variables, value.accept(visitor)); 225 | }); 226 | 227 | return variables; 228 | } 229 | }; 230 | 231 | return node.accept(visitor); 232 | } 233 | 234 | /** 235 | * Returns free variables in given block. 236 | * 237 | * @param {Array.} block Subject block. 238 | * @return {!Object.} A set of free variable names. 239 | */ 240 | function getFreeVariablesOfBlock(block) { 241 | var freeVariables = {}; 242 | var boundVariables = {}; 243 | 244 | _.each(block, function(line) { 245 | var bindingVariables = getBindingVariables(line); 246 | 247 | _.extend(boundVariables, bindingVariables); 248 | 249 | _.each(getFreeVariables(line), function(value, variable) { 250 | if (!boundVariables[variable]) { 251 | freeVariables[variable] = true; 252 | } 253 | }); 254 | }); 255 | 256 | return freeVariables; 257 | } 258 | 259 | /** 260 | * Returns variables bound in current environment by given node. 261 | * 262 | * For example, let expressions bind variables in current environment. 263 | * 264 | * @param {fv.Node} node Subject node 265 | * @return {!Object.} A set of variable names bound in current environment by given node 266 | */ 267 | function getBindingVariables(node) { 268 | var returnEmpty = function(node) { 269 | return {}; 270 | }; 271 | 272 | var singleton = function(name) { 273 | var variables = {}; 274 | 275 | variables[name] = true; 276 | 277 | return variables; 278 | }; 279 | 280 | var returnName = function(node) { 281 | return singleton(node.name); 282 | }; 283 | 284 | var visitor = { 285 | visitData: function(node) { 286 | var variables = {}; 287 | 288 | variables[node.name] = true; 289 | 290 | _.each(node.tags, function(tag) { 291 | _.extend(variables, tag.accept(visitor)); 292 | }); 293 | 294 | return variables; 295 | }, 296 | visitFunction: returnName, 297 | visitInstance: returnName, 298 | visitBind: returnName, 299 | visitTag: returnName, 300 | visitAssignment: returnName, 301 | visitLet: returnName, 302 | 303 | // all others don't bind variables in current environment; 304 | // they bind no variables or bind in their own environment. 305 | visitExpression: returnEmpty, 306 | visitType: returnEmpty, 307 | visitTypeClass: returnEmpty, 308 | visitGeneric: returnEmpty, 309 | visitReturn: returnEmpty, 310 | visitDo: returnEmpty, 311 | visitMatch: returnEmpty, 312 | visitCall: returnEmpty, 313 | visitIfThenElse: returnEmpty, 314 | visitComment: returnEmpty, 315 | visitPropertyAccess: returnEmpty, 316 | visitAccess: returnEmpty, 317 | visitBinaryGenericOperator: returnEmpty, 318 | visitBinaryNumberOperator: returnEmpty, 319 | visitBinaryBooleanOperator: returnEmpty, 320 | visitBinaryStringOperator: returnEmpty, 321 | visitWith: returnEmpty, 322 | visitIdentifier: returnEmpty, 323 | visitTuple: returnEmpty, 324 | visitNumber: returnEmpty, 325 | visitString: returnEmpty, 326 | visitBoolean: returnEmpty, 327 | visitArray: returnEmpty, 328 | visitObject: returnEmpty 329 | }; 330 | 331 | return node.accept(visitor); 332 | } 333 | 334 | exports.getFreeVariables = getFreeVariables; 335 | --------------------------------------------------------------------------------