├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── dist ├── ll.js └── ll.min.js ├── lib ├── core.js ├── lex.js ├── node.js ├── pattern.js ├── scope.js ├── token.js └── util.js ├── package.json ├── sample ├── console.css └── index.html └── test ├── lex.test.js ├── pattern.test.js ├── scope.test.js └── util.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | // Metadata. 7 | meta: { 8 | version: '0.1.0' 9 | }, 10 | banner: '/*! PROJECT_NAME - v<%= meta.version %> - ' + 11 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 12 | '* http://icymorn.github.io/lambda-lite-js/\n' + 13 | '* Copyright (c) <%= grunt.template.today("yyyy") %> ' + 14 | 'ICYMORN; Licensed MIT */\n', 15 | // Task configuration. 16 | concat: { 17 | options: { 18 | banner: '<%= banner %>', 19 | stripBanners: true 20 | }, 21 | dist: { 22 | src: ['lib/core.js', 'lib/*.js'], 23 | dest: 'dist/ll.js' 24 | } 25 | }, 26 | uglify: { 27 | options: { 28 | banner: '<%= banner %>' 29 | }, 30 | dist: { 31 | src: '<%= concat.dist.dest %>', 32 | dest: 'dist/ll.min.js' 33 | } 34 | }, 35 | watch: { 36 | gruntfile: { 37 | files: '<% uglify.dist.dest %>', 38 | tasks: ['jshint:gruntfile'] 39 | } 40 | } 41 | }); 42 | 43 | // These plugins provide necessary tasks. 44 | grunt.loadNpmTasks('grunt-contrib-concat'); 45 | grunt.loadNpmTasks('grunt-contrib-uglify'); 46 | grunt.loadNpmTasks('grunt-contrib-watch'); 47 | 48 | // Default task. 49 | grunt.registerTask('default', ['concat', 'uglify']); 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 icymorn 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lambda-Lite-js 2 | a tiny FUNCITONAL LANGUAGE implemented by javascript. 3 | 4 | online demo: https://moevis.github.io/lambda-lite-js/ (中文版) 5 | 6 | ## Support 7 | 8 | * Lambda function (including sugar for declearing multi-parameters function) 9 | * currying, lazy evaluation, recursive in anonymous function (z combinator) 10 | * Basic pattern matching 11 | * Point-free style: compose function together with `.` 12 | * Basic type system: bool, number, list, function and string. 13 | 14 | ## Tutorial 15 | 16 | ### Lambda function 17 | 18 | Using backsplash and arrow to declear an anyoumous function. Lambda function only accept one parameter, but you can use some magic method to break this limit. 19 | 20 | ```haskell 21 | \n -> n + 1; 22 | \n -> n * n; 23 | \n -> n + n * n; 24 | ``` 25 | 26 | Creating function which accepts two parameters. 27 | 28 | ```haskell 29 | (\n -> \m -> m + n) 1 2 --- output: 3 30 | ``` 31 | 32 | Now, declear a function with single-param or multi-params can be write as below: 33 | 34 | ```haskell 35 | let add x y = x + y 36 | let result = add 1 2 37 | ``` 38 | 39 | ### Pattern matching 40 | 41 | Pattern matching is an useful feature in some functional language. The ll language has a basic pattern matching implements. 42 | 43 | ```haskell 44 | let func a@1 = a + 1; 45 | let func a@2 = a + 2; 46 | print (func 2); 47 | 48 | let echo a@Number = print 'Number'; 49 | let echo a@String = print 'String'; 50 | let echo a@* = print 'Other'; 51 | echo 'this is string'; 52 | echo true; 53 | ``` 54 | 55 | Pattern matching has some limits in ll.js . 56 | 57 | * The all parameters should be in the same order. 58 | * The lengths of the functions which have same name also should be equal. 59 | * Every parameter should have a pattern declearation like `Number`, `String`, `Boolean`, or `*` for other types. 60 | * Matching progress is from top to bottom, from left to right. 61 | 62 | ### Various declaration 63 | 64 | The keyword `let` leads an assignment, in forms of `let ... = ... (-> ...)`. The symbol `->` is options, only if you want return a value. 65 | 66 | ```haskell 67 | let x = 5; 68 | let y = \n -> n + 1; 69 | let z = let a = 3 -> a * a; 70 | ``` 71 | 72 | ### Binary condition 73 | 74 | The binary condition is in form of `if ... then ... else ...`. 75 | 76 | ```haskell 77 | print (if true then 1 else 0) 78 | ``` 79 | 80 | ### Native function 81 | 82 | now some native functions are accessiable. As well as the basic calculation operators: `+-*/`. 83 | 84 | ```haskell 85 | print "hello"; 86 | print (length [1,2,3,4]); 87 | print (reverse [1,2,3,4]); 88 | print [1,2,3,4] !! 2; 89 | print (not true); 90 | ``` 91 | 92 | ### Recursive calling 93 | 94 | Recursive programming is an elegant programming style. 95 | 96 | ```haskell 97 | 98 | let fact = \n -> 99 | if n == 1 then 1 100 | else n * (fact n - 1); 101 | print (fact 5); 102 | ``` 103 | 104 | Lambda function can recursive by using z-combinator instead of calling itself. 105 | 106 | ```haskell 107 | let z = \f->(\x -> f (\y -> x x y)) (\x -> f (\y -> x x y)); 108 | let makeFact = \g -> \n -> if n < 2 109 | then 1 110 | else n * (g n - 1); 111 | let fact = z makeFact; 112 | print (fact 5); 113 | ``` 114 | 115 | ### Point-free programming 116 | 117 | Use `.` and `$` to pretifier your code, less `brackets` now !!! 118 | 119 | Beblow is a sample for calculating (10 + 10) ^ 2 120 | 121 | ```haskell 122 | let double = \n -> n + n; 123 | let square = \n -> n * n; 124 | print $ double $ square 10; 125 | let func = double . square; 126 | print $ func 10; 127 | ``` 128 | 129 | ### Play with Church Number 130 | 131 | ```haskell 132 | let True x y = x; 133 | let False x y = y; 134 | let Zero f x = x; 135 | let One f x = f x; 136 | let Two f x = f (f x); 137 | let Three f x = f (f (f x)); 138 | let Add a b f x = a f (b f x); 139 | let Mul a b f x = a (b f) x; 140 | 141 | print $ Two (\n -> n + 1) 0; 142 | print $ Add One Two (\n -> n + 1) 0; 143 | print $ Mul Two Three (\n -> n + 1) 0; 144 | ``` 145 | -------------------------------------------------------------------------------- /dist/ll.js: -------------------------------------------------------------------------------- 1 | /*! PROJECT_NAME - v0.1.0 - 2016-05-09 2 | * http://icymorn.github.io/lambda-lite-js/ 3 | * Copyright (c) 2016 ICYMORN; Licensed MIT */ 4 | var ll = { 5 | 'exports': {}, 6 | 'require': function(module) { 7 | return ll.exports[module]; 8 | } 9 | }; 10 | 11 | /** 12 | * Author: Icymorn 13 | * Github: https://github.com/icymorn/ 14 | * the first module to be loaded. 15 | * load the other module by dependencies order. 16 | */ 17 | var define = (function(){ 18 | var namespaces = {}, 19 | pending = {}, 20 | uid = 0; 21 | 22 | /** 23 | * load module 24 | * @param {string} ns name of the module. 25 | * @param {Array} dependsOn modules' name array than depends on. 26 | * @param {Function} func module content. 27 | * @returns {boolean} true if a module is successfully loaded. 28 | * @private 29 | * @example 30 | * _load('lexer',['core'], func); 31 | * 32 | */ 33 | function _load(ns, dependsOn, func){ 34 | if (namespaces[ns] !== undefined && namespaces[ns].load === true) { 35 | return true; 36 | } 37 | var loadNow = true; 38 | for (var i = 0, len = dependsOn.length; i < len; i++) { 39 | if (namespaces[dependsOn[i]] === undefined) { 40 | loadNow = false; 41 | } else { 42 | if (namespaces[dependsOn[i]].load === false) { 43 | if (!_load(dependsOn[i], namespaces[dependsOn[i]].depends, namespaces[dependsOn[i]].func)) { 44 | loadNow = false; 45 | } 46 | } 47 | } 48 | } 49 | if (loadNow) { 50 | var n; 51 | ll.exports[ns] = {}; 52 | func(ll.exports[ns]); 53 | namespaces[ns].load = true; 54 | delete pending[ns]; 55 | for (n in pending) { 56 | _load(n, namespaces[n].depends, namespaces[n].func); 57 | } 58 | return true; 59 | } else { 60 | pending[ns] = true; 61 | return false; 62 | } 63 | } 64 | 65 | /** 66 | * generate unique id; 67 | * @returns {number} 68 | */ 69 | function guid() { 70 | return uid++; 71 | } 72 | 73 | /** 74 | * @example 75 | * morn.define(itself_name, ['dependencies'], function(){}); // 76 | * morn.define(['dependencies'], function(){}); // anonymous function and has dependencies 77 | * morn.define(itself_name, function(){}); // no dependencies 78 | * morn.define(function(){}); // no dependencies and anonymous 79 | */ 80 | return function() { 81 | if (arguments.length === 1) { 82 | arguments[0](morn); 83 | } else if (arguments.length === 2){ 84 | var ns; 85 | if (typeof arguments[0] === 'string') { 86 | ns = arguments[0]; 87 | namespaces[ns] = { 88 | load: false, 89 | depends: [], 90 | func: arguments[1] 91 | }; 92 | _load(ns, [], arguments[1]); 93 | } else { 94 | ns = guid(); 95 | namespaces[ns] = { 96 | load: false, 97 | depends: arguments[0], 98 | func: arguments[1] 99 | }; 100 | _load(ns, arguments[0], arguments[1]); 101 | } 102 | } else if (arguments.length === 3){ 103 | var ns = arguments[0]; 104 | namespaces[ns] = { 105 | load: false, 106 | depends: arguments[1], 107 | func: arguments[2] 108 | }; 109 | _load(ns, arguments[1], arguments[2]); 110 | } 111 | 112 | }; 113 | }()); 114 | if (typeof define === 'undefined') { 115 | var define = function (ns, deps, func) { 116 | func(exports); 117 | } 118 | } 119 | 120 | if (typeof require === 'undefined') { 121 | var require = ll.require; 122 | } 123 | 124 | define('./lex', ['./util', './token', './node', './pattern'], function (exports) { 125 | 126 | var Util = require('./util').Util; 127 | var Token = require('./token').Token; 128 | var Node = require('./node').Node; 129 | var Pattern = require('./pattern').Pattern; 130 | 131 | var TOKEN = { 132 | EOF: 1, 133 | Identifier: 2, 134 | Keyword: 3, 135 | Numberic: 4, 136 | Punctuator: 5, 137 | Literal: 6, 138 | NullLiteral: 7, 139 | BooleanLiteral: 8, 140 | CommentLiteral: 9, 141 | List: 10, 142 | White: 11, 143 | Unknown: 12 144 | }; 145 | 146 | var binaryPrecedence = { 147 | '||' : 10, 148 | '&&' : 20, 149 | '|' : 30, 150 | '^' : 40, 151 | '&' : 50, 152 | '==' : 60, 153 | '!=' : 60, 154 | '===' : 60, 155 | '!==' : 60, 156 | '<' : 70, 157 | '>' : 70, 158 | '<=' : 70, 159 | '>=' : 70, 160 | '<<' : 80, 161 | '>>' : 80, 162 | '+' : 90, 163 | '-' : 90, 164 | '*' : 110, 165 | '/' : 110, 166 | '%' : 110, 167 | '++' : 110, 168 | '!!' : 110, 169 | ':' : 110 170 | }; 171 | 172 | var source; 173 | var segment; 174 | var length; 175 | var index; 176 | var currToken = null; 177 | var lookahead = null; 178 | var currentIndex = 0; 179 | 180 | function getPrecedence (ch) { 181 | var prece = binaryPrecedence[ch]; 182 | if (prece === undefined) { 183 | return -1; 184 | } else { 185 | return prece; 186 | } 187 | } 188 | 189 | function skipWhite () { 190 | while (index < length && Util.isWhite(source.charAt(index))) { 191 | index ++; 192 | } 193 | } 194 | 195 | function scanWhite () { 196 | var start = index ++; 197 | while (index < length) { 198 | if (! Util.isWhite(source.charAt(index))) { 199 | break; 200 | } 201 | index ++; 202 | } 203 | return source.slice(start, index); 204 | } 205 | 206 | function scanSingleLineComment () { 207 | var start = index ++; 208 | while (index < length) { 209 | if (source.charAt(index) === '\n') { 210 | index ++; 211 | break; 212 | } 213 | index ++; 214 | } 215 | return source.slice(start, index); 216 | } 217 | 218 | function scanMuiltiLineComment () { 219 | var start = index ++; 220 | while (index < length) { 221 | if (source.charAt(index) === '*') { 222 | index ++; 223 | if (index < length && source.charAt(index) === '/') { 224 | index ++; 225 | break; 226 | } 227 | } 228 | index ++; 229 | } 230 | return source.slice(start, index); 231 | } 232 | 233 | function scanLiteral () { 234 | var start = index; 235 | var ch = source.charAt(index++); 236 | var endSymbol = ch; 237 | while (index < length) { 238 | ch = source.charAt(index++); 239 | if (ch === endSymbol) { 240 | break; 241 | } 242 | } 243 | // when no matching quote found 244 | if (ch !== endSymbol) { 245 | throw "no matching for: " + endSymbol; 246 | } 247 | return source.slice(start + 1, index - 1); 248 | } 249 | 250 | function scanIdentifier () { 251 | var start = index++; 252 | var ch; 253 | while (index < length) { 254 | ch = source.charAt(index); 255 | if (Util.isIdentifierBody(ch)) { 256 | index ++; 257 | } else { 258 | break; 259 | } 260 | } 261 | return source.slice(start, index); 262 | } 263 | 264 | 265 | 266 | function scanNumberic () { 267 | var start = index++; 268 | var ch; 269 | var dot = false; 270 | while (index < length) { 271 | ch = source.charAt(index); 272 | if (Util.isDigit(ch)) { 273 | index ++; 274 | } else { 275 | if (dot === false && ch === '.') { 276 | dot = true; 277 | index ++; 278 | } else { 279 | break; 280 | } 281 | } 282 | } 283 | return source.slice(start, index); 284 | } 285 | 286 | 287 | 288 | function scanPunctuator () { 289 | var fstCh = source.charAt(index); 290 | var start = index++; 291 | var ch; 292 | if (Util.isSinglePunctuator(fstCh)) { 293 | return fstCh; 294 | } 295 | if (fstCh === '+') { 296 | if (index < length) { 297 | ch = source.charAt(index); 298 | if (ch === '+' || ch === '=') { 299 | index ++; 300 | } 301 | } 302 | } else if (fstCh === '-') { 303 | if (index < length) { 304 | ch = source.charAt(index); 305 | if (ch === '-' || ch === '>' || ch === '=') { 306 | index ++; 307 | } 308 | } 309 | } else if (fstCh === '*') { 310 | if (index < length) { 311 | ch = source.charAt(index); 312 | if (ch === '=') { 313 | index ++; 314 | } 315 | } 316 | } else if (fstCh === '/') { 317 | if (index < length) { 318 | ch = source.charAt(index); 319 | if (ch === '=' || ch === '*' || ch === '/') { 320 | index ++; 321 | } 322 | } 323 | } else if (fstCh === '=') { 324 | if (index < length) { 325 | ch = source.charAt(index); 326 | if (ch === '=') { 327 | index ++; 328 | if (index < length) { 329 | ch = source.charAt(index); 330 | if (ch === '=') { 331 | index ++; 332 | } 333 | } 334 | } 335 | } 336 | } else if (fstCh === '>') { 337 | if (index < length) { 338 | ch = source.charAt(index); 339 | if (ch === '=') { 340 | index ++; 341 | } else if (ch === '>') { 342 | index ++; 343 | if (index < length) { 344 | ch = source.charAt(index); 345 | if (ch === '=') { 346 | index ++; 347 | } 348 | } 349 | } 350 | } 351 | } else if (fstCh === '<') { 352 | if (index < length) { 353 | ch = source.charAt(index); 354 | if (ch === '=') { 355 | index ++; 356 | } else if (ch === '<') { 357 | index ++; 358 | if (index < length) { 359 | ch = source.charAt(index); 360 | if (ch === '=') { 361 | index ++; 362 | } 363 | } 364 | } 365 | } 366 | } else if (fstCh === '!') { 367 | if (index < length) { 368 | ch = source.charAt(index); 369 | if (ch === '=') { 370 | index ++; 371 | if (index < length) { 372 | ch = source.charAt(index); 373 | if (ch === '=') { 374 | index ++; 375 | } 376 | } 377 | } else if (ch === '!'){ 378 | index ++; 379 | } 380 | } 381 | } else if (fstCh === '&') { 382 | if (index < length) { 383 | ch = source.charAt(index); 384 | if (ch === '&') { 385 | index ++; 386 | } 387 | } 388 | } else if (fstCh === '|') { 389 | if (index < length) { 390 | ch = source.charAt(index); 391 | if (ch === '|') { 392 | index ++; 393 | } 394 | } 395 | } 396 | return source.slice(start, index); 397 | } 398 | 399 | function nextToken() { 400 | var token; 401 | currentIndex = index; 402 | currToken = lookahead; 403 | while (index < length) { 404 | var ch = source.charAt(index); 405 | if (Util.isWhite(ch)) { 406 | token = new Token(TOKEN.White, scanWhite()); 407 | } else if (Util.isDigit(ch)) { 408 | token = new Token(TOKEN.Numberic, scanNumberic()); 409 | } else if (Util.isIdentifierStart(ch)) { 410 | var value = scanIdentifier(); 411 | if (Util.isKeyword(value)) { 412 | token = new Token(TOKEN.Keyword, value); 413 | } else if (Util.isBooleanLiteral(value)) { 414 | token = new Token(TOKEN.BooleanLiteral, value); 415 | } else if (Util.isNullLiteral(value)) { 416 | token = new Token(TOKEN.NullLiteral, value); 417 | } else { 418 | token = new Token(TOKEN.Identifier, value); 419 | } 420 | } else if (Util.isPunctuator(ch)) { 421 | var value = scanPunctuator(); 422 | if (value === '//') { 423 | token = new Token(TOKEN.CommentLiteral, value + scanSingleLineComment()); 424 | } else if (value === '/*') { 425 | token = new Token(TOKEN.CommentLiteral, value + scanMuiltiLineComment()); 426 | } else { 427 | token = new Token(TOKEN.Punctuator, value); 428 | } 429 | } else if (Util.isLiteralStart(ch)) { 430 | token = new Token(TOKEN.Literal, scanLiteral()); 431 | } 432 | if (token.type !== TOKEN.EOF && token.type !== TOKEN.White) { 433 | 434 | lookahead = token; 435 | return token; 436 | } 437 | 438 | } 439 | 440 | if (currToken !== null && currToken.type !== TOKEN.EOF) { 441 | currToken = lookahead; 442 | } 443 | lookahead = new Token(TOKEN.EOF, null); 444 | return lookahead; 445 | } 446 | 447 | function match(value) { 448 | return lookahead.value === value; 449 | } 450 | 451 | function consumePunctuator (value) { 452 | if (currToken.value === value && currToken.type === TOKEN.Punctuator) { 453 | nextToken(); 454 | } else { 455 | throw "expect for: " + value; 456 | } 457 | } 458 | 459 | function consumeKeyword (value) { 460 | if (currToken.value === value && currToken.type === TOKEN.Keyword) { 461 | nextToken(); 462 | } else { 463 | throw "expect for: " + value; 464 | } 465 | } 466 | 467 | function expectKeyword (value) { 468 | if (lookahead.value === value && lookahead.type === TOKEN.Keyword) { 469 | nextToken(); 470 | nextToken(); 471 | } else { 472 | throw "expect for: " + value; 473 | } 474 | } 475 | 476 | function expectPunctuator(value) { 477 | if (lookahead.value === value && lookahead.type === TOKEN.Punctuator) { 478 | nextToken(); 479 | nextToken(); 480 | } else { 481 | throw "expect for:" + value; 482 | } 483 | } 484 | 485 | function genParenNode () { 486 | nextToken(); 487 | var v = genTopLevelNode(); 488 | //var v = genCallNode(); 489 | if (v === null) { 490 | return null; 491 | } else { 492 | if (currToken.value !== ')') { 493 | throw "expect for : )"; 494 | } 495 | nextToken(); 496 | return v; 497 | } 498 | } 499 | 500 | function genExpressionNode () { 501 | var left = genPrimaryNode(); 502 | if (left === null) { 503 | return null; 504 | } else { 505 | 506 | return genTree(0, left); 507 | } 508 | } 509 | 510 | function genList() { 511 | //consumePunctuator('['); 512 | nextToken(); 513 | var elements = []; 514 | while (currToken.type !== TOKEN.EOF) { 515 | if (currToken.type === TOKEN.Numberic){ 516 | elements.push(Number(currToken.value)); 517 | } else if (currToken.type === TOKEN.Literal || currToken.type === TOKEN.BooleanLiteral) { 518 | elements.push(currToken.value); 519 | } 520 | if (match(']')) { 521 | expectPunctuator(']'); 522 | break; 523 | } else { 524 | expectPunctuator(','); 525 | } 526 | } 527 | return new Node.listNode(elements, new Node.SourceCode(source, segment, currentIndex)); 528 | } 529 | 530 | function genCallNode () { 531 | var obj = genExpressionNode(); 532 | while (true) { 533 | if (currToken.type === TOKEN.Numberic || currToken.type === TOKEN.Identifier || currToken.type === TOKEN.Literal || currToken.type === TOKEN.BooleanLiteral) { 534 | obj = new Node.callNode(obj, genExpressionNode(), new Node.SourceCode(source, segment, currentIndex)); 535 | } else if (currToken.type === TOKEN.Punctuator) { 536 | if (currToken.value === '(') { 537 | obj = new Node.callNode(obj, genParenNode(), new Node.SourceCode(source, segment, currentIndex)); 538 | } else if (currToken.value === '$') { 539 | nextToken(); 540 | obj = new Node.callNode(obj, genCallNode(), new Node.SourceCode(source, segment, currentIndex)); 541 | } else if (currToken.value === '.') { 542 | nextToken(); 543 | var f = obj; 544 | var g = genCallNode(); 545 | obj = new Node.lambdaNode( 546 | '$1', 547 | new Node.callNode( 548 | f, 549 | new Node.callNode( 550 | g, 551 | new Node.objectNode( 552 | '$1', 553 | new Node.SourceCode(source, segment, currentIndex) 554 | ), 555 | new Node.SourceCode(source, segment, currentIndex) 556 | ) 557 | ), new Node.SourceCode(source, segment, currentIndex)); 558 | } else if (currToken.value === ',') { 559 | nextToken(); 560 | obj = new Node.consNode(obj, genExpressionNode(), new Node.SourceCode(source, segment, currentIndex)); 561 | } else { 562 | return obj; 563 | } 564 | } else { 565 | return obj; 566 | } 567 | } 568 | } 569 | 570 | function genTree (exprece, left) { 571 | // patch for calling function 572 | if (currToken.type === TOKEN.Punctuator & currToken.value === ';') { 573 | return left; 574 | } 575 | 576 | while (true) { 577 | var prece = getPrecedence(currToken.value); 578 | if (prece < exprece) { 579 | return left; 580 | } else { 581 | 582 | var currOp = currToken.value; 583 | nextToken(); 584 | var right = genPrimaryNode(); 585 | if (right === null) { 586 | return null; 587 | } 588 | var nextPrece = getPrecedence(currToken.value); 589 | if (prece < nextPrece) { 590 | right = genTree(prece + 1, right); 591 | if (right === null) { 592 | return null; 593 | } 594 | } 595 | var node = new Node.expressionNode(currOp, new Node.SourceCode(source, segment, currentIndex)); 596 | node.left = left; 597 | node.right = right; 598 | left = node; 599 | } 600 | } 601 | } 602 | 603 | function genNumbericNode () { 604 | var node = new Node.numberNode(Number(currToken.value), new Node.SourceCode(source, segment, currentIndex)); 605 | nextToken(); 606 | return node; 607 | } 608 | 609 | function genBooleanNode () { 610 | var node = new Node.booleanNode(currToken.value === 'true', new Node.SourceCode(source, segment, currentIndex)); 611 | nextToken(); 612 | return node; 613 | } 614 | 615 | function genLiteralNode () { 616 | var node = new Node.literalNode(currToken.value, new Node.SourceCode(source, segment, currentIndex)); 617 | nextToken(); 618 | return node; 619 | } 620 | 621 | function genLambdaNode () { 622 | nextToken(); 623 | var id = currToken.value; 624 | expectPunctuator('->'); 625 | var expre = genTopLevelNode(); 626 | return new Node.lambdaNode(id, expre, new Node.SourceCode(source, segment, currentIndex)); 627 | } 628 | 629 | function genIdentifierNode () { 630 | var node = new Node.objectNode(currToken.value, new Node.SourceCode(source, segment, currentIndex)); 631 | nextToken(); 632 | return node; 633 | } 634 | 635 | function genIfConditionNode () { 636 | nextToken(); 637 | var cond = genTopLevelNode(); 638 | consumeKeyword('then'); 639 | var expre1 = genTopLevelNode(); 640 | consumeKeyword('else'); 641 | var expre2 = genTopLevelNode(); 642 | return new Node.ifConditionNode(cond, expre1, expre2, new Node.SourceCode(source, segment, currentIndex)); 643 | } 644 | 645 | function genDefineNode () { 646 | nextToken(); 647 | var id = currToken.value; 648 | var node; 649 | var expre; 650 | 651 | if (match('=')) { 652 | expectPunctuator('='); 653 | expre = genTopLevelNode(); 654 | } else { 655 | nextToken(); 656 | if (match('@')) { 657 | expre = genPatternNode(); 658 | //node.id = id; 659 | //return node; 660 | } else { 661 | var params = []; 662 | while (! match('=')) { 663 | expre = genIdentifierNode(); 664 | params.push(expre); 665 | } 666 | // at least read one params 667 | expre = genIdentifierNode(); 668 | params.push(expre); 669 | 670 | consumePunctuator('='); 671 | expre = genTopLevelNode(); 672 | while (params.length > 0) { 673 | expre = new Node.lambdaNode(params.pop().id, expre, new Node.SourceCode(source, segment, currentIndex)); 674 | } 675 | } 676 | 677 | } 678 | var value = expre; 679 | if (currToken.type == TOKEN.Punctuator && currToken.value == '->') { 680 | nextToken(); 681 | var body = genTopLevelNode(); 682 | node = new Node.defineNode(id, value, body, new Node.SourceCode(source, segment, currentIndex)); 683 | } else { 684 | //nextToken(); 685 | node = new Node.defineNode(id, value, null, new Node.SourceCode(source, segment, currentIndex)); 686 | } 687 | return node; 688 | 689 | } 690 | 691 | function genPatternNode () { 692 | var params = []; 693 | var patterns = []; 694 | while (currToken.type !== TOKEN.Punctuator && currToken.value !== '=') { 695 | var id = genIdentifierNode().id; 696 | var pattern; 697 | consumePunctuator('@'); 698 | if (currToken.type === TOKEN.Keyword) { 699 | if (currToken.value === 'Boolean') { 700 | pattern = new Pattern.unit(Boolean); 701 | } else if (currToken.value === 'Number') { 702 | pattern = new Pattern.unit(Number); 703 | } else if (currToken.value === 'String') { 704 | pattern = new Pattern.unit(String); 705 | } 706 | } else if (currToken.type === TOKEN.Numberic) { 707 | pattern = new Pattern.unit(Number(currToken.value)); 708 | } else if (currToken.type === TOKEN.BooleanLiteral) { 709 | pattern = new Pattern.unit(currToken.value === 'true'); 710 | } else if (currToken.type === TOKEN.Literal) { 711 | pattern = new Pattern.unit(currToken.value); 712 | } else if (currToken.type === TOKEN.Punctuator && currToken.value === '*') { 713 | pattern = new Pattern.any(); 714 | } 715 | params.push(id); 716 | patterns.push(pattern); 717 | nextToken(); 718 | } 719 | consumePunctuator('='); 720 | 721 | var expre = genTopLevelNode(); 722 | expre = new Node.patternNode(params.concat(), patterns, expre, new Node.SourceCode(source, segment, currentIndex)); 723 | 724 | while (params.length > 0) { 725 | expre = new Node.lambdaNode(params.pop(), expre, new Node.SourceCode(source, segment, currentIndex)); 726 | } 727 | 728 | return expre; 729 | } 730 | 731 | 732 | function genTopLevelNode () { 733 | if (currToken.type === TOKEN.Punctuator && currToken.value === '\\') { 734 | return genLambdaNode(); 735 | } else if (currToken.type === TOKEN.Keyword && currToken.value === 'let') { 736 | return genDefineNode(); 737 | } else if (currToken.type === TOKEN.Punctuator && currToken.value === ';') { 738 | return null; 739 | } else if (currToken.type === TOKEN.EOF) { 740 | return null; 741 | } else { 742 | return genCallNode(); 743 | } 744 | } 745 | 746 | function genPrimaryNode () { 747 | if (currToken !== null) { 748 | if (currToken.type === TOKEN.Identifier) { 749 | return genIdentifierNode(); 750 | } else if (currToken.type === TOKEN.Numberic) { 751 | return genNumbericNode(); 752 | } else if (currToken.type === TOKEN.Literal) { 753 | return genLiteralNode(); 754 | } else if (currToken.type === TOKEN.BooleanLiteral) { 755 | return genBooleanNode(); 756 | } else if (currToken.type === TOKEN.Punctuator) { 757 | if (currToken.value === '(') { 758 | return genParenNode(); 759 | } else if (currToken.value === '"' || currToken.value === '\'') { 760 | return genLiteralNode(); 761 | } else if (currToken.value === '[') { 762 | return genList(); 763 | } 764 | } else if (currToken.type === TOKEN.Keyword && currToken.value === 'if') { 765 | return genIfConditionNode(); 766 | } else { 767 | return null; 768 | } 769 | } else { 770 | return null; 771 | } 772 | } 773 | 774 | exports.parse = function (code) { 775 | source = code; 776 | var tmp = code.split('\n'); 777 | segment = [0]; 778 | var base = 0; 779 | for (var i = 0, len = tmp.length; i < len; ++i ) { 780 | base += tmp[i].length + 1; 781 | segment.push(base); 782 | } 783 | length = source.length; 784 | currentIndex = 0; 785 | index = 0; 786 | lookahead = null; 787 | }; 788 | 789 | exports.genTree = function () { 790 | var ast = []; 791 | nextToken(); 792 | nextToken(); 793 | 794 | if (!currToken) { 795 | return ast; 796 | } 797 | var tick = 0; 798 | var max_tick = 1000; 799 | 800 | while (currToken.type !== TOKEN.EOF && tick++ < max_tick) { 801 | var node = genTopLevelNode(); 802 | while (currToken.type === TOKEN.Punctuator && currToken.value === ';' ) { 803 | nextToken(); 804 | } 805 | if (node) { 806 | ast.push(node); 807 | } else { 808 | break; 809 | } 810 | } 811 | return ast; 812 | } 813 | 814 | }); 815 | 816 | 817 | if (typeof define === 'undefined') { 818 | var define = function (ns, deps, func) { 819 | func(exports); 820 | } 821 | } 822 | 823 | if (typeof require === 'undefined') { 824 | var require = ll.require; 825 | } 826 | 827 | 828 | 829 | define('./node', [],function (exports) { 830 | var NODE = { 831 | 'Function': 1, 832 | 'Object': 2, 833 | 'Expression': 3 834 | }; 835 | 836 | function Node(type) { 837 | this.type = type; 838 | } 839 | 840 | function nativeFunction (func) { 841 | this.func = func; 842 | } 843 | 844 | nativeFunction.prototype.getValue = function (scope) { 845 | return this.func(scope); 846 | }; 847 | 848 | function callNode (callee, arg, source) { 849 | this.callee = callee; 850 | this.arg = arg; 851 | this.source = source; 852 | } 853 | 854 | callNode.prototype.getValue = function (scope) { 855 | var arg = this.arg.getValue(scope); 856 | var expre = this.callee.getValue(scope); 857 | return expre(packNode(arg)); 858 | //} 859 | }; 860 | 861 | function objectNode (id, source) { 862 | this.id = id; 863 | this.source = source; 864 | } 865 | 866 | objectNode.prototype.getValue = function (scope) { 867 | var various = scope.lookup(this.id); 868 | if (various === undefined) { 869 | console.log(this.source.index); 870 | throw "Cannot to find: " + this.id + '\n' + 'at ' + this.source.getCodeSegment(); 871 | 872 | } 873 | return various.getValue(scope); 874 | }; 875 | 876 | function numberNode (value) { 877 | this.value = value; 878 | } 879 | 880 | numberNode.prototype.getValue = function (scope) { 881 | return this.value; 882 | }; 883 | 884 | function expressionNode (operator, source) { 885 | this.operator = operator; 886 | this.left = null; 887 | this.right = null; 888 | this.source = source; 889 | } 890 | 891 | expressionNode.prototype.getValue = function (scope) { 892 | var left = this.left.getValue(scope); 893 | var right; 894 | if (this.operator === '*') { 895 | right = this.right.getValue(scope); 896 | return left * right; 897 | } else if (this.operator === '/') { 898 | right = this.right.getValue(scope); 899 | if (right === 0) { 900 | throw "divided by zero"; 901 | } 902 | return left / right; 903 | } else if (this.operator === '+') { 904 | right = this.right.getValue(scope); 905 | return left + right; 906 | } else if (this.operator === '-') { 907 | right = this.right.getValue(scope); 908 | return left - right; 909 | } else if (this.operator === '==') { 910 | right = this.right.getValue(scope); 911 | return left === right; 912 | } else if (this.operator === '!=') { 913 | right = this.right.getValue(scope); 914 | return left !== right; 915 | } else if (this.operator === '>') { 916 | right = this.right.getValue(scope); 917 | return left > right; 918 | } else if (this.operator === '<') { 919 | right = this.right.getValue(scope); 920 | return left < right; 921 | } else if (this.operator === '>=') { 922 | right = this.right.getValue(scope); 923 | return left >= right; 924 | } else if (this.operator === '<=') { 925 | right = this.right.getValue(scope); 926 | return left <= right; 927 | } else if (this.operator === '||') { 928 | if (left === true) { 929 | return true; 930 | } else { 931 | return this.right.getValue(scope); 932 | } 933 | } else if (this.operator === '&&') { 934 | if (left === false) { 935 | return false; 936 | } else { 937 | return this.right.getValue(scope); 938 | } 939 | } else if (this.operator === '++') { 940 | right = this.right.getValue(scope); 941 | var newInstance = left.concat(right); 942 | return newInstance; 943 | } else if (this.operator === ':') { 944 | right = this.right.getValue(scope); 945 | if (left.constructor === Array) { 946 | return left.concat(right); 947 | } else { 948 | return [].concat(left, right); 949 | } 950 | 951 | } else if (this.operator === '!!') { 952 | right = this.right.getValue(scope); 953 | return left[right]; 954 | } else { 955 | throw "cannot find a property."; 956 | } 957 | }; 958 | 959 | function lambdaNode (id, expre, source) { 960 | this.id = id; 961 | this.expre = expre; 962 | this.source = source; 963 | } 964 | 965 | lambdaNode.prototype.getValue = function (scope) { 966 | var subScope = new scope.constructor(scope); 967 | var id = this.id; 968 | var expre = this.expre; 969 | return function (p) { 970 | subScope.add(id, p); 971 | return expre.getValue(subScope); 972 | }; 973 | }; 974 | 975 | function booleanNode (bool, source) { 976 | this.bool = bool; 977 | this.source = source; 978 | } 979 | 980 | booleanNode.prototype.getValue = function (scope) { 981 | return this.bool; 982 | }; 983 | 984 | function literalNode (literal, source) { 985 | this.literal = literal; 986 | } 987 | 988 | literalNode.prototype.getValue = function (scope) { 989 | return this.literal; 990 | }; 991 | 992 | function defineNode (id, expre, body, source) { 993 | this.id = id; 994 | this.expre = expre; 995 | this.body = body; 996 | this.source = source; 997 | } 998 | 999 | defineNode.prototype.getValue = function (scope) { 1000 | if (scope.table[this.id] === undefined) { 1001 | scope.add(this.id, this.expre); 1002 | } else { 1003 | var tmp = scope.table[this.id]; 1004 | 1005 | while (tmp.constructor === lambdaNode) { 1006 | tmp = tmp.expre; 1007 | } 1008 | if (tmp.constructor === patternNode) { 1009 | if (scope.table[this.id] === undefined) { 1010 | scope.add(this.id, this.expre); 1011 | } else { 1012 | while (tmp.next !== null) { 1013 | tmp = tmp.next; 1014 | } 1015 | tmp.next = this.expre.expre; 1016 | } 1017 | } 1018 | } 1019 | 1020 | 1021 | if (this.body !== null) { 1022 | return this.body.getValue(scope); 1023 | } else { 1024 | return null; 1025 | } 1026 | }; 1027 | 1028 | function ifConditionNode (cond, expre1, expre2, source) { 1029 | this.cond = cond; 1030 | this.expre1 = expre1; 1031 | this.expre2 = expre2; 1032 | this.souce = source; 1033 | } 1034 | 1035 | ifConditionNode.prototype.getValue = function (scope) { 1036 | if (this.cond.getValue(scope) === false) { 1037 | return this.expre2.getValue(scope); 1038 | } else { 1039 | return this.expre1.getValue(scope); 1040 | } 1041 | }; 1042 | 1043 | function packNode (value) { 1044 | if (value.constructor === Number) { 1045 | return new numberNode(value); 1046 | } else if (value.constructor === String) { 1047 | return new literalNode(value); 1048 | } else if (value.constructor === Boolean) { 1049 | return new booleanNode(value); 1050 | } else { 1051 | return new numberNode(value); 1052 | } 1053 | } 1054 | 1055 | function consNode (expre1, expre2, source) { 1056 | this.expre1 = expre1; 1057 | this.expre2 = expre2; 1058 | this.source = source; 1059 | } 1060 | 1061 | consNode.prototype.getValue = function (scope) { 1062 | if (this.expre2 === null) { 1063 | return this.expre1.getValue(scope); 1064 | } else { 1065 | this.expre1.getValue(scope); 1066 | return this.expre2.getValue(scope); 1067 | } 1068 | }; 1069 | 1070 | function listNode (elements, source) { 1071 | this.ele = elements; 1072 | this.source = source; 1073 | } 1074 | 1075 | listNode.prototype.getValue = function (scope) { 1076 | return this.ele; 1077 | }; 1078 | 1079 | function patternNode (ids, patterns, expre, source) { 1080 | this.ids = ids; 1081 | this.patterns = patterns; 1082 | this.next = null; 1083 | this.expre = expre; 1084 | this.source = source; 1085 | } 1086 | 1087 | patternNode.prototype.getValue = function (scope) { 1088 | var ids = this.ids; 1089 | var inPattern = this.patterns.every(function(pattern, index) { 1090 | return pattern.expect(scope.lookup(ids[index]).getValue(scope)); 1091 | }); 1092 | if (inPattern) { 1093 | return this.expre.getValue(scope); 1094 | } else { 1095 | if (this.next === null) { 1096 | return null; 1097 | } else { 1098 | return this.next.getValue(scope); 1099 | } 1100 | } 1101 | }; 1102 | 1103 | function Exception (message) { 1104 | this.message = message; 1105 | } 1106 | 1107 | function SourceCode (source, segment, index) { 1108 | this.source = source; 1109 | this.segment = segment; 1110 | this.index = index; 1111 | } 1112 | 1113 | SourceCode.prototype.getCodeSegment = function () { 1114 | for (var i = 0, length = this.segment.length; i < length; ++ i) { 1115 | if (this.segment[i] >= this.index) { 1116 | var offset = this.index - this.segment[i - 1]; 1117 | for (var off = '^'; off.length < offset; off = ' ' + off) { 1118 | 1119 | } 1120 | return 'line ' + i + '\n' + this.source.slice(this.segment[i - 1], this.segment[i]) + '\n' + off; 1121 | } 1122 | } 1123 | }; 1124 | 1125 | exports.NODE = NODE; 1126 | exports.Node = {}; 1127 | 1128 | exports.Node.callNode = callNode; 1129 | exports.Node.objectNode = objectNode; 1130 | exports.Node.numberNode = numberNode; 1131 | exports.Node.booleanNode = booleanNode; 1132 | exports.Node.literalNode = literalNode; 1133 | exports.Node.defineNode = defineNode; 1134 | exports.Node.lambdaNode = lambdaNode; 1135 | exports.Node.expressionNode = expressionNode; 1136 | exports.Node.nativeFunction = nativeFunction; 1137 | exports.Node.ifConditionNode = ifConditionNode; 1138 | exports.Node.consNode = consNode; 1139 | exports.Node.listNode = listNode; 1140 | exports.Node.patternNode = patternNode; 1141 | 1142 | exports.Node.Exception = Exception; 1143 | exports.Node.SourceCode = SourceCode; 1144 | }); 1145 | 1146 | if (typeof define === 'undefined') { 1147 | var define = function (ns, deps, func) { 1148 | func(exports); 1149 | } 1150 | } 1151 | 1152 | if (typeof require === 'undefined') { 1153 | var require = ll.require; 1154 | } 1155 | 1156 | define('./pattern', [], function (exports) { 1157 | var Pattern = {}; 1158 | 1159 | Pattern.unit = function unit(id) { 1160 | var type; 1161 | var value; 1162 | if (id === null) { 1163 | this.type = null; 1164 | this.value = null; 1165 | } else if (typeof id === 'function') { 1166 | this.type = id; 1167 | this.value = null; 1168 | } else if (id === undefined) { 1169 | return new Pattern.any(); 1170 | } else { 1171 | this.type = id.constructor; 1172 | this.value = id; 1173 | } 1174 | if (id instanceof Array) { 1175 | var array = []; 1176 | id.forEach(function (ele) { 1177 | array.push(new unit(ele)); 1178 | }); 1179 | this.array = array; 1180 | } 1181 | }; 1182 | 1183 | Pattern.any = function any() { 1184 | }; 1185 | 1186 | Pattern.any.prototype.expect = function (arg) { 1187 | return true; 1188 | }; 1189 | 1190 | Pattern.unit.prototype.expect = function (arg) { 1191 | if (arg === null) { 1192 | return this.value === null; 1193 | } else { 1194 | if (this.value === null) { 1195 | return arg.constructor === this.type; 1196 | } else { 1197 | if (this.array !== undefined) { 1198 | if (this.array.length === 0 && arg.length === 0) { 1199 | return true; 1200 | } else { 1201 | if (this.array.length > arg.length) { 1202 | return false; 1203 | } else { 1204 | for (var i = 0, length = this.array.length; i < length; i ++) { 1205 | if (this.array[i].expect(arg[i]) === false) { 1206 | return false; 1207 | } 1208 | } 1209 | return true; 1210 | } 1211 | } 1212 | } else { 1213 | return this.value === arg && this.type === arg.constructor; 1214 | } 1215 | } 1216 | } 1217 | }; 1218 | 1219 | Pattern.PatternMatching = function PatternMatching(options) { 1220 | var opts = []; 1221 | options.forEach(function (rule) { 1222 | var args = []; 1223 | for (var i = 0, length = rule.length - 1; i < length; i ++) { 1224 | args.push(new Pattern.unit(rule[i])); 1225 | } 1226 | args.push(rule[rule.length - 1]); 1227 | //if (args.length > 1) { 1228 | opts.push(args); 1229 | //} 1230 | }); 1231 | //console.log(opts); 1232 | return function () { 1233 | var result; 1234 | var args = arguments; 1235 | opts.some(function (rule) { 1236 | var flag = true; 1237 | for (var i = 0, length = rule.length - 1; i < length; i ++) { 1238 | //rule.log(opt[i]); 1239 | if (rule[i].expect(args[i]) === false) { 1240 | flag = false; 1241 | } 1242 | } 1243 | if (flag === true) { 1244 | var func = rule[length]; 1245 | result = func.apply(null, args); 1246 | } 1247 | return flag; 1248 | }); 1249 | return result; 1250 | } 1251 | }; 1252 | 1253 | exports.Pattern = Pattern; 1254 | }); 1255 | if (typeof define === 'undefined') { 1256 | var define = function (ns, deps, func) { 1257 | func(exports); 1258 | } 1259 | } 1260 | 1261 | if (typeof require === 'undefined') { 1262 | var require = ll.require; 1263 | } 1264 | 1265 | 1266 | define('./scope', ['./node'], function (exports) { 1267 | var Node = require('./node').Node; 1268 | 1269 | var Scope = function Scope(root) { 1270 | this.root = (root === undefined)? null: root; 1271 | this.table = {}; 1272 | }; 1273 | 1274 | Scope.prototype.add = function (id, value) { 1275 | this.table[id] = value; 1276 | }; 1277 | 1278 | Scope.prototype.exist = function (id) { 1279 | return this.lookup(id) !== undefined; 1280 | }; 1281 | 1282 | Scope.prototype.lookup = function (id) { 1283 | var local = this.table[id]; 1284 | if (local === undefined) { 1285 | if (this.root === null) { 1286 | return local; 1287 | } else { 1288 | return this.root.lookup(id); 1289 | } 1290 | } else { 1291 | return local; 1292 | } 1293 | }; 1294 | 1295 | var root = new Scope(); 1296 | 1297 | root.add('print', new Node.lambdaNode('$1', new Node.nativeFunction(function(scope) { 1298 | console.log(scope.lookup('$1').getValue(scope)); 1299 | }))); 1300 | 1301 | root.add('length', new Node.lambdaNode('$1', new Node.nativeFunction(function(scope) { 1302 | return scope.lookup('$1').getValue(scope).length; 1303 | }))); 1304 | 1305 | root.add('reverse', new Node.lambdaNode('$1', new Node.nativeFunction(function(scope) { 1306 | return scope.lookup('$1').getValue(scope).concat().reverse(); 1307 | }))); 1308 | 1309 | root.add('not', new Node.lambdaNode('$1', new Node.nativeFunction(function(scope) { 1310 | return ! scope.lookup('$1').getValue(scope); 1311 | }))); 1312 | 1313 | 1314 | root.add('map', new Node.lambdaNode('$1', new Node.lambdaNode('$2', new Node.nativeFunction(function(scope) { 1315 | var func = scope.lookup('$1').getValue(scope); 1316 | return scope.lookup('$2') 1317 | .getValue(scope) 1318 | .map(function(el) { 1319 | return func(new Node.numberNode(el)); 1320 | }); 1321 | })))); 1322 | 1323 | exports.Scope = Scope; 1324 | exports.Root = root; 1325 | }); 1326 | 1327 | if (typeof define === 'undefined') { 1328 | var define = function (ns, deps, func) { 1329 | func(exports); 1330 | } 1331 | } 1332 | 1333 | if (typeof require === 'undefined') { 1334 | var require = ll.require; 1335 | } 1336 | 1337 | 1338 | define('./token', [], function (exports) { 1339 | function Token(type, value) { 1340 | this.type = type; 1341 | this.value = value; 1342 | } 1343 | exports.Token = Token; 1344 | }); 1345 | if (typeof define === 'undefined') { 1346 | define = function (ns, deps, func) { 1347 | func(exports); 1348 | } 1349 | } 1350 | 1351 | if (typeof require === 'undefined') { 1352 | require = ll.require; 1353 | } 1354 | 1355 | 1356 | define('./util', [], function (exports) { 1357 | var Util = {}; 1358 | 1359 | var hexDigit = '0123456789abcdefABCDEF'; 1360 | 1361 | var keywords = ['if', 'then', 'else', 'let', 'Number', 'String', 'List', 'Boolean']; 1362 | var typewords = ['Number', 'String', 'List', 'Boolean']; 1363 | 1364 | var punctuatorStart = '+-*/!=|&^~%<>'; 1365 | var singlePunctuator = '[]{}(),@:\\;$.'; 1366 | 1367 | Util.isDigit = function (ch) { 1368 | return '0' <= ch && '9' >= ch; 1369 | }; 1370 | 1371 | Util.isHexDigit = function (ch) { 1372 | return hexDigit.indexOf(ch) > -1; 1373 | }; 1374 | 1375 | Util.isOctalDigit = function (ch) { 1376 | return '0' <= ch && '7' >= 'ch'; 1377 | }; 1378 | 1379 | Util.isWhite = function (ch) { 1380 | return ch === ' ' || ch === '\t' || ch === '\n'; 1381 | }; 1382 | 1383 | Util.isAlpha = function (ch) { 1384 | return 'a' <= ch && 'z' >= ch || 'A' <= ch && 'Z' >= ch; 1385 | }; 1386 | 1387 | Util.isPunctuator = function (ch) { 1388 | return punctuatorStart.indexOf(ch) > -1 || singlePunctuator.indexOf(ch) > -1; 1389 | }; 1390 | 1391 | Util.isSinglePunctuator = function (ch) { 1392 | return singlePunctuator.indexOf(ch) > -1; 1393 | }; 1394 | 1395 | Util.isLiteralStart = function (ch) { 1396 | return ch === '\'' || ch === '"'; 1397 | }; 1398 | 1399 | Util.isBooleanLiteral = function (value) { 1400 | return value === 'true' || value === 'false'; 1401 | }; 1402 | 1403 | Util.isNullLiteral = function (value) { 1404 | return value === 'null'; 1405 | } 1406 | 1407 | Util.isPunctuatorStart = function (ch) { 1408 | return punctuatorStart.index(ch) > -1; 1409 | }; 1410 | 1411 | Util.isIdentifierStart = function (ch) { 1412 | return this.isAlpha(ch) || '_' === ch; 1413 | }; 1414 | 1415 | Util.isIdentifierBody = function (ch) { 1416 | return this.isIdentifierStart(ch) || this.isDigit(ch); 1417 | }; 1418 | 1419 | Util.isKeyword = function (id) { 1420 | return keywords.indexOf(id) > -1; 1421 | }; 1422 | 1423 | Util.isTypewords = function (id) { 1424 | return typewords.indexOf(id) > -1; 1425 | }; 1426 | 1427 | exports.Util = Util; 1428 | }); -------------------------------------------------------------------------------- /dist/ll.min.js: -------------------------------------------------------------------------------- 1 | /*! PROJECT_NAME - v0.1.0 - 2016-05-09 2 | * http://icymorn.github.io/lambda-lite-js/ 3 | * Copyright (c) 2016 ICYMORN; Licensed MIT */ 4 | var ll={exports:{},require:function(a){return ll.exports[a]}},define=function(){function a(b,e,f){if(void 0!==c[b]&&c[b].load===!0)return!0;for(var g=!0,h=0,i=e.length;i>h;h++)void 0===c[e[h]]?g=!1:c[e[h]].load===!1&&(a(e[h],c[e[h]].depends,c[e[h]].func)||(g=!1));if(g){var j;ll.exports[b]={},f(ll.exports[b]),c[b].load=!0,delete d[b];for(j in d)a(j,c[j].depends,c[j].func);return!0}return d[b]=!0,!1}function b(){return e++}var c={},d={},e=0;return function(){if(1===arguments.length)arguments[0](morn);else if(2===arguments.length){var d;"string"==typeof arguments[0]?(d=arguments[0],c[d]={load:!1,depends:[],func:arguments[1]},a(d,[],arguments[1])):(d=b(),c[d]={load:!1,depends:arguments[0],func:arguments[1]},a(d,arguments[0],arguments[1]))}else if(3===arguments.length){var d=arguments[0];c[d]={load:!1,depends:arguments[1],func:arguments[2]},a(d,arguments[1],arguments[2])}}}();if("undefined"==typeof define)var define=function(a,b,c){c(exports)};if("undefined"==typeof require)var require=ll.require;if(define("./lex",["./util","./token","./node","./pattern"],function(a){function b(a){var b=M[a];return void 0===b?-1:b}function c(){for(var a=G++;F>G&&H.isWhite(D.charAt(G));)G++;return D.slice(a,G)}function d(){for(var a=G++;F>G;){if("\n"===D.charAt(G)){G++;break}G++}return D.slice(a,G)}function e(){for(var a=G++;F>G;){if("*"===D.charAt(G)&&(G++,F>G&&"/"===D.charAt(G))){G++;break}G++}return D.slice(a,G)}function f(){for(var a=G,b=D.charAt(G++),c=b;F>G&&(b=D.charAt(G++),b!==c););if(b!==c)throw"no matching for: "+c;return D.slice(a+1,G-1)}function g(){for(var a,b=G++;F>G&&(a=D.charAt(G),H.isIdentifierBody(a));)G++;return D.slice(b,G)}function h(){for(var a,b=G++,c=!1;F>G;)if(a=D.charAt(G),H.isDigit(a))G++;else{if(c!==!1||"."!==a)break;c=!0,G++}return D.slice(b,G)}function i(){var a,b=D.charAt(G),c=G++;return H.isSinglePunctuator(b)?b:("+"===b?F>G&&(a=D.charAt(G),"+"!==a&&"="!==a||G++):"-"===b?F>G&&(a=D.charAt(G),"-"!==a&&">"!==a&&"="!==a||G++):"*"===b?F>G&&(a=D.charAt(G),"="===a&&G++):"/"===b?F>G&&(a=D.charAt(G),"="!==a&&"*"!==a&&"/"!==a||G++):"="===b?F>G&&(a=D.charAt(G),"="===a&&(G++,F>G&&(a=D.charAt(G),"="===a&&G++))):">"===b?F>G&&(a=D.charAt(G),"="===a?G++:">"===a&&(G++,F>G&&(a=D.charAt(G),"="===a&&G++))):"<"===b?F>G&&(a=D.charAt(G),"="===a?G++:"<"===a&&(G++,F>G&&(a=D.charAt(G),"="===a&&G++))):"!"===b?F>G&&(a=D.charAt(G),"="===a?(G++,F>G&&(a=D.charAt(G),"="===a&&G++)):"!"===a&&G++):"&"===b?F>G&&(a=D.charAt(G),"&"===a&&G++):"|"===b&&F>G&&(a=D.charAt(G),"|"===a&&G++),D.slice(c,G))}function j(){var a;for(P=G,N=O;F>G;){var b=D.charAt(G);if(H.isWhite(b))a=new I(L.White,c());else if(H.isDigit(b))a=new I(L.Numberic,h());else if(H.isIdentifierStart(b)){var j=g();a=H.isKeyword(j)?new I(L.Keyword,j):H.isBooleanLiteral(j)?new I(L.BooleanLiteral,j):H.isNullLiteral(j)?new I(L.NullLiteral,j):new I(L.Identifier,j)}else if(H.isPunctuator(b)){var j=i();a="//"===j?new I(L.CommentLiteral,j+d()):"/*"===j?new I(L.CommentLiteral,j+e()):new I(L.Punctuator,j)}else H.isLiteralStart(b)&&(a=new I(L.Literal,f()));if(a.type!==L.EOF&&a.type!==L.White)return O=a,a}return null!==N&&N.type!==L.EOF&&(N=O),O=new I(L.EOF,null)}function k(a){return O.value===a}function l(a){if(N.value!==a||N.type!==L.Punctuator)throw"expect for: "+a;j()}function m(a){if(N.value!==a||N.type!==L.Keyword)throw"expect for: "+a;j()}function n(a){if(O.value!==a||O.type!==L.Punctuator)throw"expect for:"+a;j(),j()}function o(){j();var a=B();if(null===a)return null;if(")"!==N.value)throw"expect for : )";return j(),a}function p(){var a=C();return null===a?null:s(0,a)}function q(){j();for(var a=[];N.type!==L.EOF;){if(N.type===L.Numberic?a.push(Number(N.value)):N.type!==L.Literal&&N.type!==L.BooleanLiteral||a.push(N.value),k("]")){n("]");break}n(",")}return new J.listNode(a,new J.SourceCode(D,E,P))}function r(){for(var a=p();;)if(N.type===L.Numberic||N.type===L.Identifier||N.type===L.Literal||N.type===L.BooleanLiteral)a=new J.callNode(a,p(),new J.SourceCode(D,E,P));else{if(N.type!==L.Punctuator)return a;if("("===N.value)a=new J.callNode(a,o(),new J.SourceCode(D,E,P));else if("$"===N.value)j(),a=new J.callNode(a,r(),new J.SourceCode(D,E,P));else if("."===N.value){j();var b=a,c=r();a=new J.lambdaNode("$1",new J.callNode(b,new J.callNode(c,new J.objectNode("$1",new J.SourceCode(D,E,P)),new J.SourceCode(D,E,P))),new J.SourceCode(D,E,P))}else{if(","!==N.value)return a;j(),a=new J.consNode(a,p(),new J.SourceCode(D,E,P))}}}function s(a,c){if(N.type===L.Punctuator&";"===N.value)return c;for(;;){var d=b(N.value);if(a>d)return c;var e=N.value;j();var f=C();if(null===f)return null;var g=b(N.value);if(g>d&&(f=s(d+1,f),null===f))return null;var h=new J.expressionNode(e,new J.SourceCode(D,E,P));h.left=c,h.right=f,c=h}}function t(){var a=new J.numberNode(Number(N.value),new J.SourceCode(D,E,P));return j(),a}function u(){var a=new J.booleanNode("true"===N.value,new J.SourceCode(D,E,P));return j(),a}function v(){var a=new J.literalNode(N.value,new J.SourceCode(D,E,P));return j(),a}function w(){j();var a=N.value;n("->");var b=B();return new J.lambdaNode(a,b,new J.SourceCode(D,E,P))}function x(){var a=new J.objectNode(N.value,new J.SourceCode(D,E,P));return j(),a}function y(){j();var a=B();m("then");var b=B();m("else");var c=B();return new J.ifConditionNode(a,b,c,new J.SourceCode(D,E,P))}function z(){j();var a,b,c=N.value;if(k("="))n("="),b=B();else if(j(),k("@"))b=A();else{for(var d=[];!k("=");)b=x(),d.push(b);for(b=x(),d.push(b),l("="),b=B();d.length>0;)b=new J.lambdaNode(d.pop().id,b,new J.SourceCode(D,E,P))}var e=b;if(N.type==L.Punctuator&&"->"==N.value){j();var f=B();a=new J.defineNode(c,e,f,new J.SourceCode(D,E,P))}else a=new J.defineNode(c,e,null,new J.SourceCode(D,E,P));return a}function A(){for(var a=[],b=[];N.type!==L.Punctuator&&"="!==N.value;){var c,d=x().id;l("@"),N.type===L.Keyword?"Boolean"===N.value?c=new K.unit(Boolean):"Number"===N.value?c=new K.unit(Number):"String"===N.value&&(c=new K.unit(String)):N.type===L.Numberic?c=new K.unit(Number(N.value)):N.type===L.BooleanLiteral?c=new K.unit("true"===N.value):N.type===L.Literal?c=new K.unit(N.value):N.type===L.Punctuator&&"*"===N.value&&(c=new K.any),a.push(d),b.push(c),j()}l("=");var e=B();for(e=new J.patternNode(a.concat(),b,e,new J.SourceCode(D,E,P));a.length>0;)e=new J.lambdaNode(a.pop(),e,new J.SourceCode(D,E,P));return e}function B(){return N.type===L.Punctuator&&"\\"===N.value?w():N.type===L.Keyword&&"let"===N.value?z():N.type===L.Punctuator&&";"===N.value?null:N.type===L.EOF?null:r()}function C(){return null===N?null:N.type===L.Identifier?x():N.type===L.Numberic?t():N.type===L.Literal?v():N.type===L.BooleanLiteral?u():N.type!==L.Punctuator?N.type===L.Keyword&&"if"===N.value?y():null:"("===N.value?o():'"'===N.value||"'"===N.value?v():"["===N.value?q():void 0}var D,E,F,G,H=require("./util").Util,I=require("./token").Token,J=require("./node").Node,K=require("./pattern").Pattern,L={EOF:1,Identifier:2,Keyword:3,Numberic:4,Punctuator:5,Literal:6,NullLiteral:7,BooleanLiteral:8,CommentLiteral:9,List:10,White:11,Unknown:12},M={"||":10,"&&":20,"|":30,"^":40,"&":50,"==":60,"!=":60,"===":60,"!==":60,"<":70,">":70,"<=":70,">=":70,"<<":80,">>":80,"+":90,"-":90,"*":110,"/":110,"%":110,"++":110,"!!":110,":":110},N=null,O=null,P=0;a.parse=function(a){D=a;var b=a.split("\n");E=[0];for(var c=0,d=0,e=b.length;e>d;++d)c+=b[d].length+1,E.push(c);F=D.length,P=0,G=0,O=null},a.genTree=function(){var a=[];if(j(),j(),!N)return a;for(var b=0,c=1e3;N.type!==L.EOF&&b++"===this.operator)return b=this.right.getValue(a),c>b;if("<"===this.operator)return b=this.right.getValue(a),b>c;if(">="===this.operator)return b=this.right.getValue(a),c>=b;if("<="===this.operator)return b=this.right.getValue(a),b>=c;if("||"===this.operator)return c===!0?!0:this.right.getValue(a);if("&&"===this.operator)return c===!1?!1:this.right.getValue(a);if("++"===this.operator){b=this.right.getValue(a);var d=c.concat(b);return d}if(":"===this.operator)return b=this.right.getValue(a),c.constructor===Array?c.concat(b):[].concat(c,b);if("!!"===this.operator)return b=this.right.getValue(a),c[b];throw"cannot find a property."},g.prototype.getValue=function(a){var b=new a.constructor(a),c=this.id,d=this.expre;return function(a){return b.add(c,a),d.getValue(b)}},h.prototype.getValue=function(a){return this.bool},i.prototype.getValue=function(a){return this.literal},j.prototype.getValue=function(a){if(void 0===a.table[this.id])a.add(this.id,this.expre);else{for(var b=a.table[this.id];b.constructor===g;)b=b.expre;if(b.constructor===o)if(void 0===a.table[this.id])a.add(this.id,this.expre);else{for(;null!==b.next;)b=b.next;b.next=this.expre.expre}}return null!==this.body?this.body.getValue(a):null},k.prototype.getValue=function(a){return this.cond.getValue(a)===!1?this.expre2.getValue(a):this.expre1.getValue(a)},m.prototype.getValue=function(a){return null===this.expre2?this.expre1.getValue(a):(this.expre1.getValue(a),this.expre2.getValue(a))},n.prototype.getValue=function(a){return this.ele},o.prototype.getValue=function(a){var b=this.ids,c=this.patterns.every(function(c,d){return c.expect(a.lookup(b[d]).getValue(a))});return c?this.expre.getValue(a):null===this.next?null:this.next.getValue(a)},q.prototype.getCodeSegment=function(){for(var a=0,b=this.segment.length;b>a;++a)if(this.segment[a]>=this.index){for(var c=this.index-this.segment[a-1],d="^";d.lengtha.length)return!1;for(var b=0,c=this.array.length;c>b;b++)if(this.array[b].expect(a[b])===!1)return!1;return!0}return this.value===a&&this.type===a.constructor},b.PatternMatching=function(a){var c=[];return a.forEach(function(a){for(var d=[],e=0,f=a.length-1;f>e;e++)d.push(new b.unit(a[e]));d.push(a[a.length-1]),c.push(d)}),function(){var a,b=arguments;return c.some(function(c){for(var d=!0,e=0,f=c.length-1;f>e;e++)c[e].expect(b[e])===!1&&(d=!1);if(d===!0){var g=c[f];a=g.apply(null,b)}return d}),a}},a.Pattern=b}),"undefined"==typeof define)var define=function(a,b,c){c(exports)};if("undefined"==typeof require)var require=ll.require;if(define("./scope",["./node"],function(a){var b=require("./node").Node,c=function(a){this.root=void 0===a?null:a,this.table={}};c.prototype.add=function(a,b){this.table[a]=b},c.prototype.exist=function(a){return void 0!==this.lookup(a)},c.prototype.lookup=function(a){var b=this.table[a];return void 0===b?null===this.root?b:this.root.lookup(a):b};var d=new c;d.add("print",new b.lambdaNode("$1",new b.nativeFunction(function(a){console.log(a.lookup("$1").getValue(a))}))),d.add("length",new b.lambdaNode("$1",new b.nativeFunction(function(a){return a.lookup("$1").getValue(a).length}))),d.add("reverse",new b.lambdaNode("$1",new b.nativeFunction(function(a){return a.lookup("$1").getValue(a).concat().reverse()}))),d.add("not",new b.lambdaNode("$1",new b.nativeFunction(function(a){return!a.lookup("$1").getValue(a)}))),d.add("map",new b.lambdaNode("$1",new b.lambdaNode("$2",new b.nativeFunction(function(a){var c=a.lookup("$1").getValue(a);return a.lookup("$2").getValue(a).map(function(a){return c(new b.numberNode(a))})})))),a.Scope=c,a.Root=d}),"undefined"==typeof define)var define=function(a,b,c){c(exports)};if("undefined"==typeof require)var require=ll.require;define("./token",[],function(a){function b(a,b){this.type=a,this.value=b}a.Token=b}),"undefined"==typeof define&&(define=function(a,b,c){c(exports)}),"undefined"==typeof require&&(require=ll.require),define("./util",[],function(a){var b={},c="0123456789abcdefABCDEF",d=["if","then","else","let","Number","String","List","Boolean"],e=["Number","String","List","Boolean"],f="+-*/!=|&^~%<>",g="[]{}(),@:\\;$.";b.isDigit=function(a){return a>="0"&&"9">=a},b.isHexDigit=function(a){return c.indexOf(a)>-1},b.isOctalDigit=function(a){return a>="0"&&!1},b.isWhite=function(a){return" "===a||" "===a||"\n"===a},b.isAlpha=function(a){return a>="a"&&"z">=a||a>="A"&&"Z">=a},b.isPunctuator=function(a){return f.indexOf(a)>-1||g.indexOf(a)>-1},b.isSinglePunctuator=function(a){return g.indexOf(a)>-1},b.isLiteralStart=function(a){return"'"===a||'"'===a},b.isBooleanLiteral=function(a){return"true"===a||"false"===a},b.isNullLiteral=function(a){return"null"===a},b.isPunctuatorStart=function(a){return f.index(a)>-1},b.isIdentifierStart=function(a){return this.isAlpha(a)||"_"===a},b.isIdentifierBody=function(a){return this.isIdentifierStart(a)||this.isDigit(a)},b.isKeyword=function(a){return d.indexOf(a)>-1},b.isTypewords=function(a){return e.indexOf(a)>-1},a.Util=b}); -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | var ll = { 2 | 'exports': {}, 3 | 'require': function(module) { 4 | return ll.exports[module]; 5 | } 6 | }; 7 | 8 | /** 9 | * Author: Icymorn 10 | * Github: https://github.com/icymorn/ 11 | * the first module to be loaded. 12 | * load the other module by dependencies order. 13 | */ 14 | var define = (function(){ 15 | var namespaces = {}, 16 | pending = {}, 17 | uid = 0; 18 | 19 | /** 20 | * load module 21 | * @param {string} ns name of the module. 22 | * @param {Array} dependsOn modules' name array than depends on. 23 | * @param {Function} func module content. 24 | * @returns {boolean} true if a module is successfully loaded. 25 | * @private 26 | * @example 27 | * _load('lexer',['core'], func); 28 | * 29 | */ 30 | function _load(ns, dependsOn, func){ 31 | if (namespaces[ns] !== undefined && namespaces[ns].load === true) { 32 | return true; 33 | } 34 | var loadNow = true; 35 | for (var i = 0, len = dependsOn.length; i < len; i++) { 36 | if (namespaces[dependsOn[i]] === undefined) { 37 | loadNow = false; 38 | } else { 39 | if (namespaces[dependsOn[i]].load === false) { 40 | if (!_load(dependsOn[i], namespaces[dependsOn[i]].depends, namespaces[dependsOn[i]].func)) { 41 | loadNow = false; 42 | } 43 | } 44 | } 45 | } 46 | if (loadNow) { 47 | var n; 48 | ll.exports[ns] = {}; 49 | func(ll.exports[ns]); 50 | namespaces[ns].load = true; 51 | delete pending[ns]; 52 | for (n in pending) { 53 | _load(n, namespaces[n].depends, namespaces[n].func); 54 | } 55 | return true; 56 | } else { 57 | pending[ns] = true; 58 | return false; 59 | } 60 | } 61 | 62 | /** 63 | * generate unique id; 64 | * @returns {number} 65 | */ 66 | function guid() { 67 | return uid++; 68 | } 69 | 70 | /** 71 | * @example 72 | * morn.define(itself_name, ['dependencies'], function(){}); // 73 | * morn.define(['dependencies'], function(){}); // anonymous function and has dependencies 74 | * morn.define(itself_name, function(){}); // no dependencies 75 | * morn.define(function(){}); // no dependencies and anonymous 76 | */ 77 | return function() { 78 | if (arguments.length === 1) { 79 | arguments[0](morn); 80 | } else if (arguments.length === 2){ 81 | var ns; 82 | if (typeof arguments[0] === 'string') { 83 | ns = arguments[0]; 84 | namespaces[ns] = { 85 | load: false, 86 | depends: [], 87 | func: arguments[1] 88 | }; 89 | _load(ns, [], arguments[1]); 90 | } else { 91 | ns = guid(); 92 | namespaces[ns] = { 93 | load: false, 94 | depends: arguments[0], 95 | func: arguments[1] 96 | }; 97 | _load(ns, arguments[0], arguments[1]); 98 | } 99 | } else if (arguments.length === 3){ 100 | var ns = arguments[0]; 101 | namespaces[ns] = { 102 | load: false, 103 | depends: arguments[1], 104 | func: arguments[2] 105 | }; 106 | _load(ns, arguments[1], arguments[2]); 107 | } 108 | 109 | }; 110 | }()); -------------------------------------------------------------------------------- /lib/lex.js: -------------------------------------------------------------------------------- 1 | if (typeof define === 'undefined') { 2 | var define = function (ns, deps, func) { 3 | func(exports); 4 | } 5 | } 6 | 7 | if (typeof require === 'undefined') { 8 | var require = ll.require; 9 | } 10 | 11 | define('./lex', ['./util', './token', './node', './pattern'], function (exports) { 12 | 13 | var Util = require('./util').Util; 14 | var Token = require('./token').Token; 15 | var Node = require('./node').Node; 16 | var Pattern = require('./pattern').Pattern; 17 | 18 | var TOKEN = { 19 | EOF: 1, 20 | Identifier: 2, 21 | Keyword: 3, 22 | Numberic: 4, 23 | Punctuator: 5, 24 | Literal: 6, 25 | NullLiteral: 7, 26 | BooleanLiteral: 8, 27 | CommentLiteral: 9, 28 | List: 10, 29 | White: 11, 30 | Unknown: 12 31 | }; 32 | 33 | var binaryPrecedence = { 34 | '||' : 10, 35 | '&&' : 20, 36 | '|' : 30, 37 | '^' : 40, 38 | '&' : 50, 39 | '==' : 60, 40 | '!=' : 60, 41 | '===' : 60, 42 | '!==' : 60, 43 | '<' : 70, 44 | '>' : 70, 45 | '<=' : 70, 46 | '>=' : 70, 47 | '<<' : 80, 48 | '>>' : 80, 49 | '+' : 90, 50 | '-' : 90, 51 | '*' : 110, 52 | '/' : 110, 53 | '%' : 110, 54 | '++' : 110, 55 | '!!' : 110, 56 | ':' : 110 57 | }; 58 | 59 | var source; 60 | var segment; 61 | var length; 62 | var index; 63 | var currToken = null; 64 | var lookahead = null; 65 | var currentIndex = 0; 66 | 67 | function getPrecedence (ch) { 68 | var prece = binaryPrecedence[ch]; 69 | if (prece === undefined) { 70 | return -1; 71 | } else { 72 | return prece; 73 | } 74 | } 75 | 76 | function skipWhite () { 77 | while (index < length && Util.isWhite(source.charAt(index))) { 78 | index ++; 79 | } 80 | } 81 | 82 | function scanWhite () { 83 | var start = index ++; 84 | while (index < length) { 85 | if (! Util.isWhite(source.charAt(index))) { 86 | break; 87 | } 88 | index ++; 89 | } 90 | return source.slice(start, index); 91 | } 92 | 93 | function scanSingleLineComment () { 94 | var start = index ++; 95 | while (index < length) { 96 | if (source.charAt(index) === '\n') { 97 | index ++; 98 | break; 99 | } 100 | index ++; 101 | } 102 | return source.slice(start, index); 103 | } 104 | 105 | function scanMuiltiLineComment () { 106 | var start = index ++; 107 | while (index < length) { 108 | if (source.charAt(index) === '*') { 109 | index ++; 110 | if (index < length && source.charAt(index) === '/') { 111 | index ++; 112 | break; 113 | } 114 | } 115 | index ++; 116 | } 117 | return source.slice(start, index); 118 | } 119 | 120 | function scanLiteral () { 121 | var start = index; 122 | var ch = source.charAt(index++); 123 | var endSymbol = ch; 124 | while (index < length) { 125 | ch = source.charAt(index++); 126 | if (ch === endSymbol) { 127 | break; 128 | } 129 | } 130 | // when no matching quote found 131 | if (ch !== endSymbol) { 132 | throw "no matching for: " + endSymbol; 133 | } 134 | return source.slice(start + 1, index - 1); 135 | } 136 | 137 | function scanIdentifier () { 138 | var start = index++; 139 | var ch; 140 | while (index < length) { 141 | ch = source.charAt(index); 142 | if (Util.isIdentifierBody(ch)) { 143 | index ++; 144 | } else { 145 | break; 146 | } 147 | } 148 | return source.slice(start, index); 149 | } 150 | 151 | 152 | 153 | function scanNumberic () { 154 | var start = index++; 155 | var ch; 156 | var dot = false; 157 | while (index < length) { 158 | ch = source.charAt(index); 159 | if (Util.isDigit(ch)) { 160 | index ++; 161 | } else { 162 | if (dot === false && ch === '.') { 163 | dot = true; 164 | index ++; 165 | } else { 166 | break; 167 | } 168 | } 169 | } 170 | return source.slice(start, index); 171 | } 172 | 173 | 174 | 175 | function scanPunctuator () { 176 | var fstCh = source.charAt(index); 177 | var start = index++; 178 | var ch; 179 | if (Util.isSinglePunctuator(fstCh)) { 180 | return fstCh; 181 | } 182 | if (fstCh === '+') { 183 | if (index < length) { 184 | ch = source.charAt(index); 185 | if (ch === '+' || ch === '=') { 186 | index ++; 187 | } 188 | } 189 | } else if (fstCh === '-') { 190 | if (index < length) { 191 | ch = source.charAt(index); 192 | if (ch === '-' || ch === '>' || ch === '=') { 193 | index ++; 194 | } 195 | } 196 | } else if (fstCh === '*') { 197 | if (index < length) { 198 | ch = source.charAt(index); 199 | if (ch === '=') { 200 | index ++; 201 | } 202 | } 203 | } else if (fstCh === '/') { 204 | if (index < length) { 205 | ch = source.charAt(index); 206 | if (ch === '=' || ch === '*' || ch === '/') { 207 | index ++; 208 | } 209 | } 210 | } else if (fstCh === '=') { 211 | if (index < length) { 212 | ch = source.charAt(index); 213 | if (ch === '=') { 214 | index ++; 215 | if (index < length) { 216 | ch = source.charAt(index); 217 | if (ch === '=') { 218 | index ++; 219 | } 220 | } 221 | } 222 | } 223 | } else if (fstCh === '>') { 224 | if (index < length) { 225 | ch = source.charAt(index); 226 | if (ch === '=') { 227 | index ++; 228 | } else if (ch === '>') { 229 | index ++; 230 | if (index < length) { 231 | ch = source.charAt(index); 232 | if (ch === '=') { 233 | index ++; 234 | } 235 | } 236 | } 237 | } 238 | } else if (fstCh === '<') { 239 | if (index < length) { 240 | ch = source.charAt(index); 241 | if (ch === '=') { 242 | index ++; 243 | } else if (ch === '<') { 244 | index ++; 245 | if (index < length) { 246 | ch = source.charAt(index); 247 | if (ch === '=') { 248 | index ++; 249 | } 250 | } 251 | } 252 | } 253 | } else if (fstCh === '!') { 254 | if (index < length) { 255 | ch = source.charAt(index); 256 | if (ch === '=') { 257 | index ++; 258 | if (index < length) { 259 | ch = source.charAt(index); 260 | if (ch === '=') { 261 | index ++; 262 | } 263 | } 264 | } else if (ch === '!'){ 265 | index ++; 266 | } 267 | } 268 | } else if (fstCh === '&') { 269 | if (index < length) { 270 | ch = source.charAt(index); 271 | if (ch === '&') { 272 | index ++; 273 | } 274 | } 275 | } else if (fstCh === '|') { 276 | if (index < length) { 277 | ch = source.charAt(index); 278 | if (ch === '|') { 279 | index ++; 280 | } 281 | } 282 | } 283 | return source.slice(start, index); 284 | } 285 | 286 | function nextToken() { 287 | var token; 288 | currentIndex = index; 289 | currToken = lookahead; 290 | while (index < length) { 291 | var ch = source.charAt(index); 292 | if (Util.isWhite(ch)) { 293 | token = new Token(TOKEN.White, scanWhite()); 294 | } else if (Util.isDigit(ch)) { 295 | token = new Token(TOKEN.Numberic, scanNumberic()); 296 | } else if (Util.isIdentifierStart(ch)) { 297 | var value = scanIdentifier(); 298 | if (Util.isKeyword(value)) { 299 | token = new Token(TOKEN.Keyword, value); 300 | } else if (Util.isBooleanLiteral(value)) { 301 | token = new Token(TOKEN.BooleanLiteral, value); 302 | } else if (Util.isNullLiteral(value)) { 303 | token = new Token(TOKEN.NullLiteral, value); 304 | } else { 305 | token = new Token(TOKEN.Identifier, value); 306 | } 307 | } else if (Util.isPunctuator(ch)) { 308 | var value = scanPunctuator(); 309 | if (value === '//') { 310 | token = new Token(TOKEN.CommentLiteral, value + scanSingleLineComment()); 311 | } else if (value === '/*') { 312 | token = new Token(TOKEN.CommentLiteral, value + scanMuiltiLineComment()); 313 | } else { 314 | token = new Token(TOKEN.Punctuator, value); 315 | } 316 | } else if (Util.isLiteralStart(ch)) { 317 | token = new Token(TOKEN.Literal, scanLiteral()); 318 | } 319 | if (token.type !== TOKEN.EOF && token.type !== TOKEN.White) { 320 | 321 | lookahead = token; 322 | return token; 323 | } 324 | 325 | } 326 | 327 | if (currToken !== null && currToken.type !== TOKEN.EOF) { 328 | currToken = lookahead; 329 | } 330 | lookahead = new Token(TOKEN.EOF, null); 331 | return lookahead; 332 | } 333 | 334 | function match(value) { 335 | return lookahead.value === value; 336 | } 337 | 338 | function consumePunctuator (value) { 339 | if (currToken.value === value && currToken.type === TOKEN.Punctuator) { 340 | nextToken(); 341 | } else { 342 | throw "expect for: " + value; 343 | } 344 | } 345 | 346 | function consumeKeyword (value) { 347 | if (currToken.value === value && currToken.type === TOKEN.Keyword) { 348 | nextToken(); 349 | } else { 350 | throw "expect for: " + value; 351 | } 352 | } 353 | 354 | function expectKeyword (value) { 355 | if (lookahead.value === value && lookahead.type === TOKEN.Keyword) { 356 | nextToken(); 357 | nextToken(); 358 | } else { 359 | throw "expect for: " + value; 360 | } 361 | } 362 | 363 | function expectPunctuator(value) { 364 | if (lookahead.value === value && lookahead.type === TOKEN.Punctuator) { 365 | nextToken(); 366 | nextToken(); 367 | } else { 368 | throw "expect for:" + value; 369 | } 370 | } 371 | 372 | function genParenNode () { 373 | nextToken(); 374 | var v = genTopLevelNode(); 375 | //var v = genCallNode(); 376 | if (v === null) { 377 | return null; 378 | } else { 379 | if (currToken.value !== ')') { 380 | throw "expect for : )"; 381 | } 382 | nextToken(); 383 | return v; 384 | } 385 | } 386 | 387 | function genExpressionNode () { 388 | var left = genPrimaryNode(); 389 | if (left === null) { 390 | return null; 391 | } else { 392 | 393 | return genTree(0, left); 394 | } 395 | } 396 | 397 | function genList() { 398 | //consumePunctuator('['); 399 | nextToken(); 400 | var elements = []; 401 | while (currToken.type !== TOKEN.EOF) { 402 | if (currToken.type === TOKEN.Numberic){ 403 | elements.push(Number(currToken.value)); 404 | } else if (currToken.type === TOKEN.Literal || currToken.type === TOKEN.BooleanLiteral) { 405 | elements.push(currToken.value); 406 | } 407 | if (match(']')) { 408 | expectPunctuator(']'); 409 | break; 410 | } else { 411 | expectPunctuator(','); 412 | } 413 | } 414 | return new Node.listNode(elements, new Node.SourceCode(source, segment, currentIndex)); 415 | } 416 | 417 | function genCallNode () { 418 | var obj = genExpressionNode(); 419 | while (true) { 420 | if (currToken.type === TOKEN.Numberic || currToken.type === TOKEN.Identifier || currToken.type === TOKEN.Literal || currToken.type === TOKEN.BooleanLiteral) { 421 | obj = new Node.callNode(obj, genExpressionNode(), new Node.SourceCode(source, segment, currentIndex)); 422 | } else if (currToken.type === TOKEN.Punctuator) { 423 | if (currToken.value === '(') { 424 | obj = new Node.callNode(obj, genParenNode(), new Node.SourceCode(source, segment, currentIndex)); 425 | } else if (currToken.value === '$') { 426 | nextToken(); 427 | obj = new Node.callNode(obj, genCallNode(), new Node.SourceCode(source, segment, currentIndex)); 428 | } else if (currToken.value === '.') { 429 | nextToken(); 430 | var f = obj; 431 | var g = genCallNode(); 432 | obj = new Node.lambdaNode( 433 | '$1', 434 | new Node.callNode( 435 | f, 436 | new Node.callNode( 437 | g, 438 | new Node.objectNode( 439 | '$1', 440 | new Node.SourceCode(source, segment, currentIndex) 441 | ), 442 | new Node.SourceCode(source, segment, currentIndex) 443 | ) 444 | ), new Node.SourceCode(source, segment, currentIndex)); 445 | } else if (currToken.value === ',') { 446 | nextToken(); 447 | obj = new Node.consNode(obj, genExpressionNode(), new Node.SourceCode(source, segment, currentIndex)); 448 | } else { 449 | return obj; 450 | } 451 | } else { 452 | return obj; 453 | } 454 | } 455 | } 456 | 457 | function genTree (exprece, left) { 458 | // patch for calling function 459 | if (currToken.type === TOKEN.Punctuator & currToken.value === ';') { 460 | return left; 461 | } 462 | 463 | while (true) { 464 | var prece = getPrecedence(currToken.value); 465 | if (prece < exprece) { 466 | return left; 467 | } else { 468 | 469 | var currOp = currToken.value; 470 | nextToken(); 471 | var right = genPrimaryNode(); 472 | if (right === null) { 473 | return null; 474 | } 475 | var nextPrece = getPrecedence(currToken.value); 476 | if (prece < nextPrece) { 477 | right = genTree(prece + 1, right); 478 | if (right === null) { 479 | return null; 480 | } 481 | } 482 | var node = new Node.expressionNode(currOp, new Node.SourceCode(source, segment, currentIndex)); 483 | node.left = left; 484 | node.right = right; 485 | left = node; 486 | } 487 | } 488 | } 489 | 490 | function genNumbericNode () { 491 | var node = new Node.numberNode(Number(currToken.value), new Node.SourceCode(source, segment, currentIndex)); 492 | nextToken(); 493 | return node; 494 | } 495 | 496 | function genBooleanNode () { 497 | var node = new Node.booleanNode(currToken.value === 'true', new Node.SourceCode(source, segment, currentIndex)); 498 | nextToken(); 499 | return node; 500 | } 501 | 502 | function genLiteralNode () { 503 | var node = new Node.literalNode(currToken.value, new Node.SourceCode(source, segment, currentIndex)); 504 | nextToken(); 505 | return node; 506 | } 507 | 508 | function genLambdaNode () { 509 | nextToken(); 510 | var id = currToken.value; 511 | expectPunctuator('->'); 512 | var expre = genTopLevelNode(); 513 | return new Node.lambdaNode(id, expre, new Node.SourceCode(source, segment, currentIndex)); 514 | } 515 | 516 | function genIdentifierNode () { 517 | var node = new Node.objectNode(currToken.value, new Node.SourceCode(source, segment, currentIndex)); 518 | nextToken(); 519 | return node; 520 | } 521 | 522 | function genIfConditionNode () { 523 | nextToken(); 524 | var cond = genTopLevelNode(); 525 | consumeKeyword('then'); 526 | var expre1 = genTopLevelNode(); 527 | consumeKeyword('else'); 528 | var expre2 = genTopLevelNode(); 529 | return new Node.ifConditionNode(cond, expre1, expre2, new Node.SourceCode(source, segment, currentIndex)); 530 | } 531 | 532 | function genDefineNode () { 533 | nextToken(); 534 | var id = currToken.value; 535 | var node; 536 | var expre; 537 | 538 | if (match('=')) { 539 | expectPunctuator('='); 540 | expre = genTopLevelNode(); 541 | } else { 542 | nextToken(); 543 | if (match('@')) { 544 | expre = genPatternNode(); 545 | //node.id = id; 546 | //return node; 547 | } else { 548 | var params = []; 549 | while (! match('=')) { 550 | expre = genIdentifierNode(); 551 | params.push(expre); 552 | } 553 | // at least read one params 554 | expre = genIdentifierNode(); 555 | params.push(expre); 556 | 557 | consumePunctuator('='); 558 | expre = genTopLevelNode(); 559 | while (params.length > 0) { 560 | expre = new Node.lambdaNode(params.pop().id, expre, new Node.SourceCode(source, segment, currentIndex)); 561 | } 562 | } 563 | 564 | } 565 | var value = expre; 566 | if (currToken.type == TOKEN.Punctuator && currToken.value == '->') { 567 | nextToken(); 568 | var body = genTopLevelNode(); 569 | node = new Node.defineNode(id, value, body, new Node.SourceCode(source, segment, currentIndex)); 570 | } else { 571 | //nextToken(); 572 | node = new Node.defineNode(id, value, null, new Node.SourceCode(source, segment, currentIndex)); 573 | } 574 | return node; 575 | 576 | } 577 | 578 | function genPatternNode () { 579 | var params = []; 580 | var patterns = []; 581 | while (currToken.type !== TOKEN.Punctuator && currToken.value !== '=') { 582 | var id = genIdentifierNode().id; 583 | var pattern; 584 | consumePunctuator('@'); 585 | if (currToken.type === TOKEN.Keyword) { 586 | if (currToken.value === 'Boolean') { 587 | pattern = new Pattern.unit(Boolean); 588 | } else if (currToken.value === 'Number') { 589 | pattern = new Pattern.unit(Number); 590 | } else if (currToken.value === 'String') { 591 | pattern = new Pattern.unit(String); 592 | } 593 | } else if (currToken.type === TOKEN.Numberic) { 594 | pattern = new Pattern.unit(Number(currToken.value)); 595 | } else if (currToken.type === TOKEN.BooleanLiteral) { 596 | pattern = new Pattern.unit(currToken.value === 'true'); 597 | } else if (currToken.type === TOKEN.Literal) { 598 | pattern = new Pattern.unit(currToken.value); 599 | } else if (currToken.type === TOKEN.Punctuator && currToken.value === '*') { 600 | pattern = new Pattern.any(); 601 | } 602 | params.push(id); 603 | patterns.push(pattern); 604 | nextToken(); 605 | } 606 | consumePunctuator('='); 607 | 608 | var expre = genTopLevelNode(); 609 | expre = new Node.patternNode(params.concat(), patterns, expre, new Node.SourceCode(source, segment, currentIndex)); 610 | 611 | while (params.length > 0) { 612 | expre = new Node.lambdaNode(params.pop(), expre, new Node.SourceCode(source, segment, currentIndex)); 613 | } 614 | 615 | return expre; 616 | } 617 | 618 | 619 | function genTopLevelNode () { 620 | if (currToken.type === TOKEN.Punctuator && currToken.value === '\\') { 621 | return genLambdaNode(); 622 | } else if (currToken.type === TOKEN.Keyword && currToken.value === 'let') { 623 | return genDefineNode(); 624 | } else if (currToken.type === TOKEN.Punctuator && currToken.value === ';') { 625 | return null; 626 | } else if (currToken.type === TOKEN.EOF) { 627 | return null; 628 | } else { 629 | return genCallNode(); 630 | } 631 | } 632 | 633 | function genPrimaryNode () { 634 | if (currToken !== null) { 635 | if (currToken.type === TOKEN.Identifier) { 636 | return genIdentifierNode(); 637 | } else if (currToken.type === TOKEN.Numberic) { 638 | return genNumbericNode(); 639 | } else if (currToken.type === TOKEN.Literal) { 640 | return genLiteralNode(); 641 | } else if (currToken.type === TOKEN.BooleanLiteral) { 642 | return genBooleanNode(); 643 | } else if (currToken.type === TOKEN.Punctuator) { 644 | if (currToken.value === '(') { 645 | return genParenNode(); 646 | } else if (currToken.value === '"' || currToken.value === '\'') { 647 | return genLiteralNode(); 648 | } else if (currToken.value === '[') { 649 | return genList(); 650 | } 651 | } else if (currToken.type === TOKEN.Keyword && currToken.value === 'if') { 652 | return genIfConditionNode(); 653 | } else { 654 | return null; 655 | } 656 | } else { 657 | return null; 658 | } 659 | } 660 | 661 | exports.parse = function (code) { 662 | source = code; 663 | var tmp = code.split('\n'); 664 | segment = [0]; 665 | var base = 0; 666 | for (var i = 0, len = tmp.length; i < len; ++i ) { 667 | base += tmp[i].length + 1; 668 | segment.push(base); 669 | } 670 | length = source.length; 671 | currentIndex = 0; 672 | index = 0; 673 | lookahead = null; 674 | }; 675 | 676 | exports.genTree = function () { 677 | var ast = []; 678 | nextToken(); 679 | nextToken(); 680 | 681 | if (!currToken) { 682 | return ast; 683 | } 684 | var tick = 0; 685 | var max_tick = 1000; 686 | 687 | while (currToken.type !== TOKEN.EOF && tick++ < max_tick) { 688 | var node = genTopLevelNode(); 689 | while (currToken.type === TOKEN.Punctuator && currToken.value === ';' ) { 690 | nextToken(); 691 | } 692 | if (node) { 693 | ast.push(node); 694 | } else { 695 | break; 696 | } 697 | } 698 | return ast; 699 | } 700 | 701 | }); 702 | 703 | -------------------------------------------------------------------------------- /lib/node.js: -------------------------------------------------------------------------------- 1 | if (typeof define === 'undefined') { 2 | var define = function (ns, deps, func) { 3 | func(exports); 4 | } 5 | } 6 | 7 | if (typeof require === 'undefined') { 8 | var require = ll.require; 9 | } 10 | 11 | 12 | 13 | define('./node', [],function (exports) { 14 | var NODE = { 15 | 'Function': 1, 16 | 'Object': 2, 17 | 'Expression': 3 18 | }; 19 | 20 | function Node(type) { 21 | this.type = type; 22 | } 23 | 24 | function nativeFunction (func) { 25 | this.func = func; 26 | } 27 | 28 | nativeFunction.prototype.getValue = function (scope) { 29 | return this.func(scope); 30 | }; 31 | 32 | function callNode (callee, arg, source) { 33 | this.callee = callee; 34 | this.arg = arg; 35 | this.source = source; 36 | } 37 | 38 | callNode.prototype.getValue = function (scope) { 39 | var arg = this.arg.getValue(scope); 40 | var expre = this.callee.getValue(scope); 41 | return expre(packNode(arg)); 42 | //} 43 | }; 44 | 45 | function objectNode (id, source) { 46 | this.id = id; 47 | this.source = source; 48 | } 49 | 50 | objectNode.prototype.getValue = function (scope) { 51 | var various = scope.lookup(this.id); 52 | if (various === undefined) { 53 | console.log(this.source.index); 54 | throw "Cannot to find: " + this.id + '\n' + 'at ' + this.source.getCodeSegment(); 55 | 56 | } 57 | return various.getValue(scope); 58 | }; 59 | 60 | function numberNode (value) { 61 | this.value = value; 62 | } 63 | 64 | numberNode.prototype.getValue = function (scope) { 65 | return this.value; 66 | }; 67 | 68 | function expressionNode (operator, source) { 69 | this.operator = operator; 70 | this.left = null; 71 | this.right = null; 72 | this.source = source; 73 | } 74 | 75 | expressionNode.prototype.getValue = function (scope) { 76 | var left = this.left.getValue(scope); 77 | var right; 78 | if (this.operator === '*') { 79 | right = this.right.getValue(scope); 80 | return left * right; 81 | } else if (this.operator === '/') { 82 | right = this.right.getValue(scope); 83 | if (right === 0) { 84 | throw "divided by zero"; 85 | } 86 | return left / right; 87 | } else if (this.operator === '+') { 88 | right = this.right.getValue(scope); 89 | return left + right; 90 | } else if (this.operator === '-') { 91 | right = this.right.getValue(scope); 92 | return left - right; 93 | } else if (this.operator === '==') { 94 | right = this.right.getValue(scope); 95 | return left === right; 96 | } else if (this.operator === '!=') { 97 | right = this.right.getValue(scope); 98 | return left !== right; 99 | } else if (this.operator === '>') { 100 | right = this.right.getValue(scope); 101 | return left > right; 102 | } else if (this.operator === '<') { 103 | right = this.right.getValue(scope); 104 | return left < right; 105 | } else if (this.operator === '>=') { 106 | right = this.right.getValue(scope); 107 | return left >= right; 108 | } else if (this.operator === '<=') { 109 | right = this.right.getValue(scope); 110 | return left <= right; 111 | } else if (this.operator === '||') { 112 | if (left === true) { 113 | return true; 114 | } else { 115 | return this.right.getValue(scope); 116 | } 117 | } else if (this.operator === '&&') { 118 | if (left === false) { 119 | return false; 120 | } else { 121 | return this.right.getValue(scope); 122 | } 123 | } else if (this.operator === '++') { 124 | right = this.right.getValue(scope); 125 | var newInstance = left.concat(right); 126 | return newInstance; 127 | } else if (this.operator === ':') { 128 | right = this.right.getValue(scope); 129 | if (left.constructor === Array) { 130 | return left.concat(right); 131 | } else { 132 | return [].concat(left, right); 133 | } 134 | 135 | } else if (this.operator === '!!') { 136 | right = this.right.getValue(scope); 137 | return left[right]; 138 | } else { 139 | throw "cannot find a property."; 140 | } 141 | }; 142 | 143 | function lambdaNode (id, expre, source) { 144 | this.id = id; 145 | this.expre = expre; 146 | this.source = source; 147 | } 148 | 149 | lambdaNode.prototype.getValue = function (scope) { 150 | var subScope = new scope.constructor(scope); 151 | var id = this.id; 152 | var expre = this.expre; 153 | return function (p) { 154 | subScope.add(id, p); 155 | return expre.getValue(subScope); 156 | }; 157 | }; 158 | 159 | function booleanNode (bool, source) { 160 | this.bool = bool; 161 | this.source = source; 162 | } 163 | 164 | booleanNode.prototype.getValue = function (scope) { 165 | return this.bool; 166 | }; 167 | 168 | function literalNode (literal, source) { 169 | this.literal = literal; 170 | } 171 | 172 | literalNode.prototype.getValue = function (scope) { 173 | return this.literal; 174 | }; 175 | 176 | function defineNode (id, expre, body, source) { 177 | this.id = id; 178 | this.expre = expre; 179 | this.body = body; 180 | this.source = source; 181 | } 182 | 183 | defineNode.prototype.getValue = function (scope) { 184 | if (scope.table[this.id] === undefined) { 185 | scope.add(this.id, this.expre); 186 | } else { 187 | var tmp = scope.table[this.id]; 188 | 189 | while (tmp.constructor === lambdaNode) { 190 | tmp = tmp.expre; 191 | } 192 | if (tmp.constructor === patternNode) { 193 | if (scope.table[this.id] === undefined) { 194 | scope.add(this.id, this.expre); 195 | } else { 196 | while (tmp.next !== null) { 197 | tmp = tmp.next; 198 | } 199 | tmp.next = this.expre.expre; 200 | } 201 | } 202 | } 203 | 204 | 205 | if (this.body !== null) { 206 | return this.body.getValue(scope); 207 | } else { 208 | return null; 209 | } 210 | }; 211 | 212 | function ifConditionNode (cond, expre1, expre2, source) { 213 | this.cond = cond; 214 | this.expre1 = expre1; 215 | this.expre2 = expre2; 216 | this.souce = source; 217 | } 218 | 219 | ifConditionNode.prototype.getValue = function (scope) { 220 | if (this.cond.getValue(scope) === false) { 221 | return this.expre2.getValue(scope); 222 | } else { 223 | return this.expre1.getValue(scope); 224 | } 225 | }; 226 | 227 | function packNode (value) { 228 | if (value.constructor === Number) { 229 | return new numberNode(value); 230 | } else if (value.constructor === String) { 231 | return new literalNode(value); 232 | } else if (value.constructor === Boolean) { 233 | return new booleanNode(value); 234 | } else { 235 | return new numberNode(value); 236 | } 237 | } 238 | 239 | function consNode (expre1, expre2, source) { 240 | this.expre1 = expre1; 241 | this.expre2 = expre2; 242 | this.source = source; 243 | } 244 | 245 | consNode.prototype.getValue = function (scope) { 246 | if (this.expre2 === null) { 247 | return this.expre1.getValue(scope); 248 | } else { 249 | this.expre1.getValue(scope); 250 | return this.expre2.getValue(scope); 251 | } 252 | }; 253 | 254 | function listNode (elements, source) { 255 | this.ele = elements; 256 | this.source = source; 257 | } 258 | 259 | listNode.prototype.getValue = function (scope) { 260 | return this.ele; 261 | }; 262 | 263 | function patternNode (ids, patterns, expre, source) { 264 | this.ids = ids; 265 | this.patterns = patterns; 266 | this.next = null; 267 | this.expre = expre; 268 | this.source = source; 269 | } 270 | 271 | patternNode.prototype.getValue = function (scope) { 272 | var ids = this.ids; 273 | var inPattern = this.patterns.every(function(pattern, index) { 274 | return pattern.expect(scope.lookup(ids[index]).getValue(scope)); 275 | }); 276 | if (inPattern) { 277 | return this.expre.getValue(scope); 278 | } else { 279 | if (this.next === null) { 280 | return null; 281 | } else { 282 | return this.next.getValue(scope); 283 | } 284 | } 285 | }; 286 | 287 | function Exception (message) { 288 | this.message = message; 289 | } 290 | 291 | function SourceCode (source, segment, index) { 292 | this.source = source; 293 | this.segment = segment; 294 | this.index = index; 295 | } 296 | 297 | SourceCode.prototype.getCodeSegment = function () { 298 | for (var i = 0, length = this.segment.length; i < length; ++ i) { 299 | if (this.segment[i] >= this.index) { 300 | var offset = this.index - this.segment[i - 1]; 301 | for (var off = '^'; off.length < offset; off = ' ' + off) { 302 | 303 | } 304 | return 'line ' + i + '\n' + this.source.slice(this.segment[i - 1], this.segment[i]) + '\n' + off; 305 | } 306 | } 307 | }; 308 | 309 | exports.NODE = NODE; 310 | exports.Node = {}; 311 | 312 | exports.Node.callNode = callNode; 313 | exports.Node.objectNode = objectNode; 314 | exports.Node.numberNode = numberNode; 315 | exports.Node.booleanNode = booleanNode; 316 | exports.Node.literalNode = literalNode; 317 | exports.Node.defineNode = defineNode; 318 | exports.Node.lambdaNode = lambdaNode; 319 | exports.Node.expressionNode = expressionNode; 320 | exports.Node.nativeFunction = nativeFunction; 321 | exports.Node.ifConditionNode = ifConditionNode; 322 | exports.Node.consNode = consNode; 323 | exports.Node.listNode = listNode; 324 | exports.Node.patternNode = patternNode; 325 | 326 | exports.Node.Exception = Exception; 327 | exports.Node.SourceCode = SourceCode; 328 | }); 329 | -------------------------------------------------------------------------------- /lib/pattern.js: -------------------------------------------------------------------------------- 1 | if (typeof define === 'undefined') { 2 | var define = function (ns, deps, func) { 3 | func(exports); 4 | } 5 | } 6 | 7 | if (typeof require === 'undefined') { 8 | var require = ll.require; 9 | } 10 | 11 | define('./pattern', [], function (exports) { 12 | var Pattern = {}; 13 | 14 | Pattern.unit = function unit(id) { 15 | var type; 16 | var value; 17 | if (id === null) { 18 | this.type = null; 19 | this.value = null; 20 | } else if (typeof id === 'function') { 21 | this.type = id; 22 | this.value = null; 23 | } else if (id === undefined) { 24 | return new Pattern.any(); 25 | } else { 26 | this.type = id.constructor; 27 | this.value = id; 28 | } 29 | if (id instanceof Array) { 30 | var array = []; 31 | id.forEach(function (ele) { 32 | array.push(new unit(ele)); 33 | }); 34 | this.array = array; 35 | } 36 | }; 37 | 38 | Pattern.any = function any() { 39 | }; 40 | 41 | Pattern.any.prototype.expect = function (arg) { 42 | return true; 43 | }; 44 | 45 | Pattern.unit.prototype.expect = function (arg) { 46 | if (arg === null) { 47 | return this.value === null; 48 | } else { 49 | if (this.value === null) { 50 | return arg.constructor === this.type; 51 | } else { 52 | if (this.array !== undefined) { 53 | if (this.array.length === 0 && arg.length === 0) { 54 | return true; 55 | } else { 56 | if (this.array.length > arg.length) { 57 | return false; 58 | } else { 59 | for (var i = 0, length = this.array.length; i < length; i ++) { 60 | if (this.array[i].expect(arg[i]) === false) { 61 | return false; 62 | } 63 | } 64 | return true; 65 | } 66 | } 67 | } else { 68 | return this.value === arg && this.type === arg.constructor; 69 | } 70 | } 71 | } 72 | }; 73 | 74 | Pattern.PatternMatching = function PatternMatching(options) { 75 | var opts = []; 76 | options.forEach(function (rule) { 77 | var args = []; 78 | for (var i = 0, length = rule.length - 1; i < length; i ++) { 79 | args.push(new Pattern.unit(rule[i])); 80 | } 81 | args.push(rule[rule.length - 1]); 82 | //if (args.length > 1) { 83 | opts.push(args); 84 | //} 85 | }); 86 | //console.log(opts); 87 | return function () { 88 | var result; 89 | var args = arguments; 90 | opts.some(function (rule) { 91 | var flag = true; 92 | for (var i = 0, length = rule.length - 1; i < length; i ++) { 93 | //rule.log(opt[i]); 94 | if (rule[i].expect(args[i]) === false) { 95 | flag = false; 96 | } 97 | } 98 | if (flag === true) { 99 | var func = rule[length]; 100 | result = func.apply(null, args); 101 | } 102 | return flag; 103 | }); 104 | return result; 105 | } 106 | }; 107 | 108 | exports.Pattern = Pattern; 109 | }); -------------------------------------------------------------------------------- /lib/scope.js: -------------------------------------------------------------------------------- 1 | if (typeof define === 'undefined') { 2 | var define = function (ns, deps, func) { 3 | func(exports); 4 | } 5 | } 6 | 7 | if (typeof require === 'undefined') { 8 | var require = ll.require; 9 | } 10 | 11 | 12 | define('./scope', ['./node'], function (exports) { 13 | var Node = require('./node').Node; 14 | 15 | var Scope = function Scope(root) { 16 | this.root = (root === undefined)? null: root; 17 | this.table = {}; 18 | }; 19 | 20 | Scope.prototype.add = function (id, value) { 21 | this.table[id] = value; 22 | }; 23 | 24 | Scope.prototype.exist = function (id) { 25 | return this.lookup(id) !== undefined; 26 | }; 27 | 28 | Scope.prototype.lookup = function (id) { 29 | var local = this.table[id]; 30 | if (local === undefined) { 31 | if (this.root === null) { 32 | return local; 33 | } else { 34 | return this.root.lookup(id); 35 | } 36 | } else { 37 | return local; 38 | } 39 | }; 40 | 41 | var root = new Scope(); 42 | 43 | root.add('print', new Node.lambdaNode('$1', new Node.nativeFunction(function(scope) { 44 | console.log(scope.lookup('$1').getValue(scope)); 45 | }))); 46 | 47 | root.add('length', new Node.lambdaNode('$1', new Node.nativeFunction(function(scope) { 48 | return scope.lookup('$1').getValue(scope).length; 49 | }))); 50 | 51 | root.add('reverse', new Node.lambdaNode('$1', new Node.nativeFunction(function(scope) { 52 | return scope.lookup('$1').getValue(scope).concat().reverse(); 53 | }))); 54 | 55 | root.add('not', new Node.lambdaNode('$1', new Node.nativeFunction(function(scope) { 56 | return ! scope.lookup('$1').getValue(scope); 57 | }))); 58 | 59 | 60 | root.add('map', new Node.lambdaNode('$1', new Node.lambdaNode('$2', new Node.nativeFunction(function(scope) { 61 | var func = scope.lookup('$1').getValue(scope); 62 | return scope.lookup('$2') 63 | .getValue(scope) 64 | .map(function(el) { 65 | return func(new Node.numberNode(el)); 66 | }); 67 | })))); 68 | 69 | exports.Scope = Scope; 70 | exports.Root = root; 71 | }); 72 | -------------------------------------------------------------------------------- /lib/token.js: -------------------------------------------------------------------------------- 1 | if (typeof define === 'undefined') { 2 | var define = function (ns, deps, func) { 3 | func(exports); 4 | } 5 | } 6 | 7 | if (typeof require === 'undefined') { 8 | var require = ll.require; 9 | } 10 | 11 | 12 | define('./token', [], function (exports) { 13 | function Token(type, value) { 14 | this.type = type; 15 | this.value = value; 16 | } 17 | exports.Token = Token; 18 | }); -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | if (typeof define === 'undefined') { 2 | define = function (ns, deps, func) { 3 | func(exports); 4 | } 5 | } 6 | 7 | if (typeof require === 'undefined') { 8 | require = ll.require; 9 | } 10 | 11 | 12 | define('./util', [], function (exports) { 13 | var Util = {}; 14 | 15 | var hexDigit = '0123456789abcdefABCDEF'; 16 | 17 | var keywords = ['if', 'then', 'else', 'let', 'Number', 'String', 'List', 'Boolean']; 18 | var typewords = ['Number', 'String', 'List', 'Boolean']; 19 | 20 | var punctuatorStart = '+-*/!=|&^~%<>'; 21 | var singlePunctuator = '[]{}(),@:\\;$.'; 22 | 23 | Util.isDigit = function (ch) { 24 | return '0' <= ch && '9' >= ch; 25 | }; 26 | 27 | Util.isHexDigit = function (ch) { 28 | return hexDigit.indexOf(ch) > -1; 29 | }; 30 | 31 | Util.isOctalDigit = function (ch) { 32 | return '0' <= ch && '7' >= 'ch'; 33 | }; 34 | 35 | Util.isWhite = function (ch) { 36 | return ch === ' ' || ch === '\t' || ch === '\n'; 37 | }; 38 | 39 | Util.isAlpha = function (ch) { 40 | return 'a' <= ch && 'z' >= ch || 'A' <= ch && 'Z' >= ch; 41 | }; 42 | 43 | Util.isPunctuator = function (ch) { 44 | return punctuatorStart.indexOf(ch) > -1 || singlePunctuator.indexOf(ch) > -1; 45 | }; 46 | 47 | Util.isSinglePunctuator = function (ch) { 48 | return singlePunctuator.indexOf(ch) > -1; 49 | }; 50 | 51 | Util.isLiteralStart = function (ch) { 52 | return ch === '\'' || ch === '"'; 53 | }; 54 | 55 | Util.isBooleanLiteral = function (value) { 56 | return value === 'true' || value === 'false'; 57 | }; 58 | 59 | Util.isNullLiteral = function (value) { 60 | return value === 'null'; 61 | } 62 | 63 | Util.isPunctuatorStart = function (ch) { 64 | return punctuatorStart.index(ch) > -1; 65 | }; 66 | 67 | Util.isIdentifierStart = function (ch) { 68 | return this.isAlpha(ch) || '_' === ch; 69 | }; 70 | 71 | Util.isIdentifierBody = function (ch) { 72 | return this.isIdentifierStart(ch) || this.isDigit(ch); 73 | }; 74 | 75 | Util.isKeyword = function (id) { 76 | return keywords.indexOf(id) > -1; 77 | }; 78 | 79 | Util.isTypewords = function (id) { 80 | return typewords.indexOf(id) > -1; 81 | }; 82 | 83 | exports.Util = Util; 84 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-lite-js", 3 | "version": "1.0.0", 4 | "description": "a tiny functional language", 5 | "main": "Gruntfile.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/icymorn/lambda-lite-js.git" 15 | }, 16 | "keywords": [ 17 | "functional", 18 | "lexer", 19 | "parser" 20 | ], 21 | "author": "icymorn", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/icymorn/lambda-lite-js/issues" 25 | }, 26 | "homepage": "https://github.com/icymorn/lambda-lite-js#readme", 27 | "dependencies": { 28 | "grunt": "^0.4.5", 29 | "grunt-contrib-concat": "^0.5.1", 30 | "grunt-contrib-uglify": "^0.9.2", 31 | "grunt-contrib-watch": "^0.6.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/console.css: -------------------------------------------------------------------------------- 1 | 2 | #run { 3 | background: #fff; 4 | padding: 0.3em 0; 5 | width: 100%; 6 | border: 1px solid #eee; 7 | font-size: 1.1em; 8 | } 9 | 10 | #run:hover { 11 | background: #d4d4d4 12 | } 13 | 14 | #source { 15 | width: 100%; 16 | font-size: 1.2em; 17 | outline-style: none; 18 | height: 10em; 19 | padding: 0.5em; 20 | box-sizing: border-box; 21 | } 22 | 23 | #console { 24 | height: 5em; 25 | margin-top: 1em; 26 | background: #212121; 27 | color: greenyellow; 28 | padding: 0.5em; 29 | box-shadow: 0px 0px 10px #4d4d4d; 30 | overflow-y: auto; 31 | } -------------------------------------------------------------------------------- /sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lambda-lite-js | Tutorial 6 | 7 | 8 | 9 | 10 | 16 | 17 |
18 |
19 | 44 | 45 | -------------------------------------------------------------------------------- /test/lex.test.js: -------------------------------------------------------------------------------- 1 | var lex = require('../lib/lex.js'); 2 | var Scope = require('../lib/scope.js').Scope; 3 | var scope = require('../lib/scope.js').Root; 4 | var log = console.log; 5 | 6 | //lex.parse('print 4+5'); 7 | //lex.parse('let x = 4+1; let y = 7; print x+y;'); 8 | //lex.parse('let x = \\n -> \\m -> n + m; print (x 1 6)'); 9 | //lex.parse('if true then print 5 else print 8'); 10 | //lex.parse("let x = \\n -> if n == 1 then 1 else x (n - 1);" + 11 | lex.parse( 12 | //"let x = \\n-> if n == 1 then 1 else n * (x n - 1);" + 13 | //";" + 14 | // "let double = \\r -> r + r;" + 15 | // "let square = \\r -> r * r;" + 16 | // "let func = double . square;" + 17 | // "print $ func 10;" 18 | "let z = \\f->(\\x -> f (\\y -> x x y)) (\\x -> f (\\y -> x x y));" + 19 | "let makeFact = \\getFact -> \\n -> if n < 2" + 20 | "then 1" + 21 | "else n * (getFact n - 1);" + 22 | "let fact = z makeFact;" + 23 | "print $ fact 5;" //"print (x 5);" 24 | ); 25 | //lex.parse('let x = \\n->n+1 print 4'); 26 | 27 | var z = lex.genTree(); 28 | z[0].getValue(scope); 29 | z[1].getValue(scope); 30 | z[2].getValue(scope); 31 | z[3].getValue(scope); 32 | // z[3].getValue(scope); 33 | //z[2].getValue(scope); 34 | //log(scope); 35 | //log(z[1].getValue(scope)); 36 | //console.log(scope); 37 | -------------------------------------------------------------------------------- /test/pattern.test.js: -------------------------------------------------------------------------------- 1 | var Pattern = require('../lib/pattern.js').Pattern; 2 | 3 | var PatternMatching = Pattern.PatternMatching; 4 | 5 | var test = PatternMatching([ 6 | [Number, function (n) { 7 | return 'number'; 8 | }], 9 | [String, function (s) { 10 | return 'string'; 11 | }], 12 | ['$', function (S) { 13 | return '$'; 14 | }], 15 | [[[1]], function (x) { 16 | return 'any'; 17 | }] 18 | ]); 19 | 20 | 21 | //console.log(test(1)); 22 | console.log(test([[]])); -------------------------------------------------------------------------------- /test/scope.test.js: -------------------------------------------------------------------------------- 1 | var Scope = require('./../lib/scope').Scope; 2 | 3 | var Root = new Scope(null); 4 | Root.add('a', 1000); 5 | Root.add('b', 2000); 6 | var child = new Scope(Root); 7 | 8 | console.log(child.lookup('a')); -------------------------------------------------------------------------------- /test/util.test.js: -------------------------------------------------------------------------------- 1 | util = require('../lib/util.js').Util; 2 | 3 | log = console.log; 4 | 5 | log('0 is digit', util.isDigit('0')); 6 | log('9 is digit', util.isDigit('9')); 7 | log('a is digit', util.isDigit('a')); 8 | 9 | log('a is hex digit', util.isHexDigit('a')); 10 | log('z is hex digit', util.isHexDigit('z')); 11 | 12 | log('a is Alpha', util.isAlpha('a')); 13 | log('1 is Alpha', util.isAlpha('1')); 14 | 15 | log('1 is indentity start', util.isIdentifierStart('1')); 16 | log('a is indentity start', util.isIdentifierStart('a')); 17 | 18 | log('1 is indentity body', util.isIdentifierBody('1')); 19 | --------------------------------------------------------------------------------