diff --git a/index.html b/index.html index 826dc88..8b960bc 100644 --- a/index.html +++ b/index.html @@ -107,7 +107,9 @@

RamdaScript

// Output JS code (alter outputJs (fn [] - (alter output.value (RamdaScript.compile source.value)))) + (alter output.value + (prop 'js' + (RamdaScript.compile source.value))))) // Select snippet (def setSource (fn [src] diff --git a/scripts/test.js b/scripts/test.js index d462f80..241457b 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -6,7 +6,7 @@ global.T = require('../src/nodes').type global.R = require('../vendor/ramda') global.run = function run(src, vars) { - var js = ram.compile(src, '') + var js = ram.compile(src, {filename: ''}).js var fn = new Function('vars', js) return fn.call(null, vars) } diff --git a/src/browser.js b/src/browser.js index 47d3f08..64db220 100644 --- a/src/browser.js +++ b/src/browser.js @@ -2,8 +2,8 @@ window.RamdaScript = module.exports = require('./ramdascript') window.RamdaScript.run = run function run(src) { - var js = RamdaScript.compile(src, {wrapper: 'none'}) - var fn = new Function(js) + var result = RamdaScript.compile(src, {format: 'none'}) + var fn = new Function(result.js) return fn() } diff --git a/src/chunk.js b/src/chunk.js new file mode 100644 index 0000000..c4d31d1 --- /dev/null +++ b/src/chunk.js @@ -0,0 +1,17 @@ + +module.exports = function chunk(content, loc) { + var chunk = { + content : content || '', + loc : loc + } + + chunk.toString = chunkToString(chunk) + + return chunk +} + +function chunkToString(chunk) { + return function () { + return chunk.content + } +} \ No newline at end of file diff --git a/src/cli.js b/src/cli.js index 4eab456..645ce28 100644 --- a/src/cli.js +++ b/src/cli.js @@ -21,15 +21,15 @@ var help = ['', 'Run .ram file containig RamdaScript code', ' -src Route to source file with RamdaScript code', '', -'compile [-src] [-dst] [-nowrap]', +'compile [-src] [-dst] [-format]', '', 'Compile to JavaScript and save as .js file', ' -src Route to source file or directory with RamdaScript code', ' the default value is the current directory (cwd)', ' -dst Route where the resulting JavaScript code will be saved', ' if route is "stdout" the resulting code will be sent to stdout', -' -wrap Specify module wrapper, possible values are: commonjs, closure, none.', -' commonjs is the default', +' -format Specify module format, possible values are: cjs, iife, none.', +' cjs is the default', '', 'eval [-src]', '', @@ -92,12 +92,12 @@ function run(opts) { // register `.ram` extension in nodejs modules Module._extensions[util.ext] = function(module, filename) { var content = fs.readFileSync(filename, 'utf8') - var js = ram.compile(content, { + var result = ram.compile(content, { filename: filename, - // just use commonjs export `R` will be imported from global - wrapper : 'commonjs-export', + // just use commonjs export, `R` will be imported from global + format : 'cjs-export', }) - module._compile(js, filename) + module._compile(result.js, filename) } var src = opts['src'] @@ -121,7 +121,7 @@ function _eval(opts) { } function run(src) { - var fn = new Function('R', ram.compile(src, '')) + var fn = new Function('R', ram.compile(src, '').js) fn(R) } } @@ -130,7 +130,7 @@ function _eval(opts) { function compile(opts) { var srcPath = opts['src'] || process.cwd() var dstPath = opts['dst'] - var wrapper = opts['wrap'] || 'commonjs' + var format = opts['format'] || 'cjs' var toStdout = dstPath === 'stdout' var stat = fs.statSync(srcPath) @@ -147,9 +147,9 @@ function compile(opts) { } if (stat.isFile()) { - compileFile(srcPath, dstPath, wrapper, toStdout) + compileFile(srcPath, dstPath, format, toStdout) } else if (stat.isDirectory()) { - compileDir(srcPath, dstPath, wrapper) + compileDir(srcPath, dstPath, format) } } @@ -157,14 +157,14 @@ function compile(opts) { // files and saves the resulting JavaScript code to the provided destiny // if the destiny path is omitted the JavaScript code will be saved adjacent // to the RamdaScript source file -function compileDir(srcPath, dstPath, wrapper, toStdout) { - fs.readdir(srcPath, function(err, list){ +function compileDir(srcPath, dstPath, format, toStdout) { + fs.readdir(srcPath, function(err, list) { if (err) throw err - list.forEach(function(fname){ + list.forEach(function(fname) { fname = path.join(srcPath, fname) - fs.stat(fname, function(err, stat){ + fs.stat(fname, function(err, stat) { if (err) throw err // skip files and dirs with name starting with `.` @@ -173,9 +173,9 @@ function compileDir(srcPath, dstPath, wrapper, toStdout) { } if (stat.isFile() && path.extname(fname) === util.ext) { - compileFile(fname, path.join(dstPath, path.basename(fname, util.ext) + '.js'), wrapper, toStdout) + compileFile(fname, path.join(dstPath, path.basename(fname, util.ext) + '.js'), format, toStdout) } else if (stat.isDirectory()) { - compileDir(fname, path.join(dstPath, path.basename(fname)), wrapper, toStdout) + compileDir(fname, path.join(dstPath, path.basename(fname)), format, toStdout) } }) }) @@ -185,39 +185,54 @@ function compileDir(srcPath, dstPath, wrapper, toStdout) { // compile a the provided file containig RamdaScript code and save the // resulting JavaScript code, if the destiny path is omitted the JavaScript code // will be saved adjacent to the RamdaScript source file -function compileFile(srcPath, dstPath, wrapper, toStdout) { +function compileFile(srcPath, dstPath, format, toStdout) { if (! fs.existsSync(dstPath)) { makePath(dstPath) } fs.readFile(srcPath, 'utf8', function(err, data) { - var js - if (err) throw err - js = ram.compile(data, { - filename: srcPath, - wrapper : wrapper + var cwd = process.cwd() + var smapDstPath = dstPath + '.map' + var smapSrcPath = path.relative( + path.dirname( + path.join(cwd, dstPath)), + path.join(cwd, srcPath)) + var result = ram.compile(data, { + filename : srcPath, + format : format, + sourceMap: !toStdout, + sourceMapCfg: { + source: smapSrcPath, + sourceContent: data + } }) if (toStdout) { - process.stdout.write(js + '\n') + process.stdout.write(result.js + '\n') return } - if (js) { - fs.writeFile(dstPath, js, 'utf8', function(err){ + if (result.js) { + result.js = result.js + '\n//# sourceMappingURL=' + path.basename(dstPath) + '.map' + fs.writeFile(dstPath, result.js, 'utf8', function(err) { + if (err) throw err + }) + } + + if (result.sourceMap) { + fs.writeFile(smapDstPath, result.sourceMap, 'utf8', function(err) { if (err) throw err }) } }) } -// makes a directory path in the filesysytem - +// makes a directory path in the filesystem function makePath(dirPath) { dirPath = path.dirname(dirPath).split(path.sep) - dirPath.reduce(function(dirPath, p){ + dirPath.reduce(function(dirPath, p) { dirPath = path.join(dirPath, p) if (! fs.existsSync(dirPath)) { try { diff --git a/src/compiler.js b/src/compiler.js index 502a768..35c1c3c 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -1,8 +1,8 @@ -var nodes = require('./nodes') -var util = require('./util') -var T = nodes.type -var newNode = nodes.node +var nodes = require('./nodes') +var util = require('./util') +var T = nodes.type +var newNode = nodes.node // walk through nodes function walk(node, parent, ctx) { @@ -38,7 +38,7 @@ function walk(node, parent, ctx) { visitObject(node, parent, ctx) break case T.JSBLOCK : - visitJsBlock(node, parent, ctx) + visitJSBlock(node, parent, ctx) break case T.NIL : visitNil(node, parent, ctx) @@ -58,17 +58,17 @@ function walk(node, parent, ctx) { } function visitLiteral(node, parent, ctx) { - ctx.write(node.content) + ctx.write(node.content, node.loc) } function visitNil(node, parent, ctx) { - ctx.write('null') + ctx.write('null', node.loc) } function visitString(node, parent, ctx) { var str = node.content node.content = str.replace(/\n|\r\n/g, '\\\n') - ctx.write(node.content) + ctx.write(node.content, node.loc) } function visitIdent(node, parent, ctx) { @@ -91,12 +91,12 @@ function visitIdent(node, parent, ctx) { cont = 'R.' + cont } } - ctx.write(cont) + ctx.write(cont, node.loc) } function visitSpecialPlaceholder(node, parent, ctx) { ctx.addUsedRamdaFn('__') - ctx.write('R.__') + ctx.write('R.__', node.loc) } function visitModule(node, parent, ctx) { @@ -121,13 +121,13 @@ function visitSexpr(node, parent, ctx) { case 'new' : operator = data[0] data = data.slice(1) - ctx.write('new ') + ctx.write('new ', node.loc) break case 'def' : var varName = data[0] var value = data[1] - ctx.write('var ') + ctx.write('var ', node.loc) walk(varName, node, ctx) ctx.write(' = ') if (value) { @@ -142,10 +142,10 @@ function visitSexpr(node, parent, ctx) { // curry function if args number is greater than 0 if (args.length === 0) { - ctx.write('function (') + ctx.write('function (', node.loc) } else { ctx.addUsedRamdaFn('curry') - ctx.write('R.curry(function (') + ctx.write('R.curry(function (', node.loc) } // write arguments @@ -226,7 +226,7 @@ function visitSexpr(node, parent, ctx) { // write the name the imported module // will be recognized with if (refer.type == T.IDENT) { - ctx.write('var ') + ctx.write('var ', node.loc) walk(refer, node, ctx) ctx.write(' = require(') walk(modPath, node, ctx) @@ -327,7 +327,7 @@ function visitObject(node, parent, ctx) { ctx.write('}') } -function visitJsBlock(node, parent, ctx) { +function visitJSBlock(node, parent, ctx) { var str = node.content node.content = util.trim(str.substring(2, str.length - 2)) visitLiteral(node, parent, ctx) @@ -338,17 +338,9 @@ function isIndentableNode(node) { return node.type === T.SEXPR } -// write CommonJS wrapper -function writeCommonJSWrapper(requireRamda, ctx) { - /* - if (Object.keys(ctx.usedRamdaFns).length > 0) { - ctx.writeTop('var R = require(\'ramda\')\n\n') - } - */ - - /* - Granular require for each Ramda function, for minimal bundle size - */ +// write CommonJS stub +function writeCommonJSStub(ast, ctx, requireRamda) { + // Granular require for each Ramda function, for minimal bundle size if (requireRamda) { var usedFns = [] @@ -357,7 +349,21 @@ function writeCommonJSWrapper(requireRamda, ctx) { }) if (usedFns.length > 0) { - ctx.writeTop('var R = {\n' + usedFns.join(',\n') + '\n}\n\n') + ctx.newLineTop() + ctx.newLineTop() + ctx.writeTop('}') + ctx.newLineTop() + + usedFns.forEach(function(fnName, idx) { + if (idx != 0) { + ctx.newLineTop() + ctx.writeTop(',') + } + ctx.writeTop(fnName) + }) + + ctx.newLineTop() + ctx.writeTop('var R = {') } } @@ -372,46 +378,49 @@ function writeCommonJSWrapper(requireRamda, ctx) { }) } -// write Closure wrapper -function writeClosureWrapper(ctx) { - ctx.writeTop(';(function () {\n\n') +// write IIFE stub +function writeIIFEStub(ctx) { + ctx.newLineTop() + ctx.newLineTop() + ctx.writeTop(';(function () {') + ctx.newLine() ctx.newLine() ctx.write('})()') } -function writeWaterMark(ctx) { +function writeCompilerInfo(ctx) { var version = require('../package.json').version - ctx.writeTop('// Generated by RamdaScript ' + version + '\n\n') + ctx.newLineTop() + ctx.newLineTop() + ctx.writeTop('// Generated by RamdaScript ' + version) } // convert the AST in JS code // params -// ast AST -// ctx shared context -// wrapper module wrapper (commonjs, closure, none) +// ast AST +// ctx Shared context +// format Module format (cjs, iife, none) // -exports.compileAst = function compileAst(ast, ctx, wrapper) { +exports.astToChunks = function astToChunks(ast, ctx, format) { walk(ast, null, ctx) + format = format || 'none' - wrapper = wrapper || 'none' - - switch (wrapper) { - case 'commonjs' : - writeCommonJSWrapper(true, ctx) + switch (format) { + case 'cjs' : + writeCommonJSStub(ast, ctx, true) break - case 'commonjs-export' : - writeCommonJSWrapper(false, ctx) + case 'cjs-export' : + writeCommonJSStub(ast, ctx, false) break - case 'closure' : - writeClosureWrapper(ctx) + case 'iife' : + writeIIFEStub(ctx) break case 'none' : break default : - throw '`' + wrapper + '` is not a valid wrapper' + throw '`' + format + '` is not a valid format' } - - writeWaterMark(ctx) - return ctx.out() + writeCompilerInfo(ctx) + return ctx.chunks } \ No newline at end of file diff --git a/src/context.js b/src/context.js index 07e3431..d1c1dfa 100644 --- a/src/context.js +++ b/src/context.js @@ -1,4 +1,6 @@ +var chunk = require('./chunk') + // Returns a new compilation context object to be shared // between compilation steps (semantic analizing and compilation) // it keeps track of defined variables, used ramda functions, @@ -10,7 +12,7 @@ exports.newContext = function newContext(filename) { filename: filename || '', // the compiled JS - buffer: [], + chunks: [], indentLevel: 0, @@ -29,22 +31,35 @@ exports.newContext = function newContext(filename) { // returns the compiled JS out: function out() { - return this.buffer.join('') + return this.chunks.join('') }, - // write to the buffer - write: function(d) { - this.buffer.push(d) + // write a new chunk + write: function(d, loc) { + if (d.indexOf('\n') != -1) { + throw 'Can not write content having \\n' + } + this.chunks.push(chunk(d, loc)) }, - // write at top of the buffer - writeTop: function(d) { - this.buffer.unshift(d) + // unshift a new chunk + writeTop: function writeTop(d, loc) { + if (d.indexOf('\n') != -1) { + throw 'Can not write content having \\n' + } + this.chunks.unshift(chunk(d, loc)) }, // write a new line and indentation newLine: function newLine() { - this.buffer.push('\n' + this.currentIndent) + this.chunks.push(chunk('\n')) + this.chunks.push(chunk(this.currentIndent)) + }, + + // write a new line and indentation + newLineTop: function newLineTop() { + this.chunks.unshift(chunk(this.currentIndent)) + this.chunks.unshift(chunk('\n')) }, // updates current indentation spaces depending diff --git a/src/ramdascript.js b/src/ramdascript.js index 1a5237b..62c6060 100644 --- a/src/ramdascript.js +++ b/src/ramdascript.js @@ -1,40 +1,94 @@ -var Parser = require('./parser').Parser -var context = require('./context') -var compiler = require('./compiler') -var semantic = require('./semantic') -var util = require('./util') +var Parser = require('./parser').Parser +var context = require('./context') +var compiler = require('./compiler') +var semantic = require('./semantic') +var sourcemap = require('./sourcemap') +var util = require('./util') // Compiles RamdaScript source code to JS, if returnCtx is true it // return the context intead of compiled JS // otps: -// filename The name of the source file -// wrapper module wrapper (commonjs, closure, none) +// filename The name of the source file +// format Module format (cjs, iife, none) +// printErrors Whether to print errors to console +// sourceMap Whether to build sourcemaps +// sourceMapCfg Sourcemap configuration object // -exports.compile = function compile(src, opts, returnCtx) { - opts = opts || {} - var ctx = context.newContext(opts.filename) - var ast = parse(src, opts) +exports.compile = function compile(src, opts) { + opts = opts || {} + var js = '' + var smap = '' + var ctx = context.newContext(opts.filename) + var chunks = srcToChunks(src, opts, ctx) + if (chunks) { + js = chunks.join('') + if (opts.sourceMap) { + smap = chunksToSourceMap(chunks, opts.sourceMapCfg) + } + } + return { + js : js, + ctx : ctx, + sourceMap: smap + } +} +// transform RamdaScript source to chunks +function srcToChunks(src, opts, ctx) { + opts = opts || {} + var ast = parse(src, opts) // check semantic var errors = semantic.checkAst(ast, ctx) - - if (returnCtx) { - return ctx - } - // there is semantic errors? if (errors) { - errors.forEach(function(e){ - console.error(e) - }) + if (opts.printErrors !== false) { + errors.forEach(function(e){ + console.error(e) + }) + } return // everything ok } else { - return compiler.compileAst(ast, ctx, opts.wrapper) + return compiler.astToChunks(ast, ctx, opts.format) } } +// convert chunks to source map +// chunks array of chunks +// cfg sourcemap config +// +function chunksToSourceMap(chunks, cfg) { + var loc + var outLine = 0 + var outColumn = 0 + var smap = sourcemap.newSourceMap() + var acc = '' + + chunks.forEach(function(chunk) { + if (chunk.content === '\n') { + outLine = outLine + 1 + outColumn = 0 + } + loc = chunk.loc + if (loc) { + smap.add( + // jison line tracking is 1 based, + // source maps reads it as 0 based + loc.firstLine - 1, + loc.firstColumn, + outLine, + outColumn + ) + } + if (chunk.content !== '\n') { + outColumn = outColumn + chunk.content.length + } + }) + + return smap.generate(cfg) +} + // parses the RamdaScript code function parse(src, opts) { var parser = new Parser() @@ -42,4 +96,6 @@ function parse(src, opts) { return parser.parse(src) } +exports.srcToChunks = srcToChunks +exports.chunksToSourceMap = chunksToSourceMap exports.parse = parse \ No newline at end of file diff --git a/src/repl.js b/src/repl.js index 0f2772c..50116d7 100644 --- a/src/repl.js +++ b/src/repl.js @@ -15,14 +15,16 @@ exports.launch = function launch(extCtx) { repl.start('ram> ', null, _eval) function _eval(code, ctx, file, cb) { - var js, err, result + var js + var err + var result try { // import passed context Object.assign(ctx, extCtx) - js = ram.compile(code, { + result = ram.compile(code, { filename: '' }) - result = vm.runInContext(js, ctx, file) + result = vm.runInContext(result.js, ctx, file) } catch (e) { err = e } diff --git a/src/sourcemap.js b/src/sourcemap.js new file mode 100644 index 0000000..5bd976e --- /dev/null +++ b/src/sourcemap.js @@ -0,0 +1,151 @@ + +// This is a version, +// the original code can be found at +// https://github.com/jashkenas/coffeescript/tree/master/src/sourcemap.litcoffee + +var VLQ_SHIFT = 5 +var VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT +var VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - 1 +var BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +function toVlq(value) { + var nextChunk + var vlq = '' + var signBit = value < 0 ? 1 : 0 + var valueToEncode = (Math.abs(value) << 1) + signBit + + while (valueToEncode || !vlq) { + nextChunk = valueToEncode & VLQ_VALUE_MASK + valueToEncode = valueToEncode >> VLQ_SHIFT + + if (valueToEncode) { + nextChunk |= VLQ_CONTINUATION_BIT + } + + vlq += toBase64(nextChunk) + } + return vlq +} + +function toBase64(value) { + var b64 = BASE64_CHARS[value] + + if (!b64) { + throw 'Can not encode ' + value + ' to base-64' + } + return b64 +} + +var newLineMap = function newLineMap(l) { + return { + line : l, + segments: [], + + add: function(generatedColumn, sourceLine, sourceColumn) { + this.segments[generatedColumn] = { + line : this.line, + column : generatedColumn, + sourceLine : sourceLine, + sourceColumn: sourceColumn + } + + return this.segments + } + } +} + +exports.newSourceMap = function newSourceMap() { + return { + lines: [], + names: null, + + add: function(sourceLine, sourceColumn, generatedLine, generatedColumn) { + var line = this.lines[generatedLine] + + if (!line) { + line = this.lines[generatedLine] = newLineMap(generatedLine) + } + + line.add(generatedColumn, sourceLine, sourceColumn) + }, + + generate: function(cfg) { + cfg = cfg || {} + + var i + var j + var line + var sm + var segment + var segmentsLen + var currentLine = 0 + var lastSourceLine = 0 + var lastSourceColumn = 0 + var lastColumn = 0 + var linesLen = this.lines.length + var mapping = '' + var segmentSep = '' + + for (i = 0; i < linesLen; i++) { + line = this.lines[i] + + if (!line) continue + + segmentsLen = line.segments.length + + for (j = 0; j < segmentsLen; j++) { + segment = line.segments[j] + + if (!segment) continue + + while (currentLine < segment.line) { + segmentSep = '' + lastColumn = 0 + mapping += ';' + currentLine++ + } + + mapping += segmentSep + mapping += toVlq(segment.column - lastColumn) + mapping += toVlq(0) + mapping += toVlq(segment.sourceLine - lastSourceLine) + mapping += toVlq(segment.sourceColumn - lastSourceColumn) + + lastColumn = segment.column + lastSourceLine = segment.sourceLine + lastSourceColumn = segment.sourceColumn + + segmentSep = ',' + } + } + + sm = { + version : 3, + file : '', + sourceRoot : '', + sources : [''], + sourcesContent: [null], + names : [], + mappings : mapping + } + + if (cfg.file) { + sm.file = cfg.file + } + + if (cfg.sourceRoot) { + sm.sourceRoot = cfg.sourceRoot + } + + if (cfg.source) { + sm.sources = [cfg.source] + } + + if (cfg.sourceContent) { + sm.sourcesContent = [cfg.sourceContent] + } + + return JSON.stringify(sm) + } + } +} \ No newline at end of file diff --git a/test/behavior.js b/test/behavior.js index 5ca37f3..f8d7eca 100644 --- a/test/behavior.js +++ b/test/behavior.js @@ -130,13 +130,13 @@ describe('Identifiers', function() { expect(vars.val).not.toBe(R.T) }) - it('are not identified as Ramda function if is a property', function() { + it('are not identified as Ramda function if it is a property', function() { var vars = {} run('(def obj {:T 0}) (alter vars.val obj.T)', vars) expect(vars.val).not.toBe(R.T) }) - it('are not identified as Ramda function if is previously defined', function() { + it('are not identified as Ramda function if it is previously defined', function() { var vars = {} run('(def T F) (alter vars.val (T))', vars) expect(vars.val).toBe(false) diff --git a/test/semantic.js b/test/semantic.js index 69c8008..a3fbfb3 100644 --- a/test/semantic.js +++ b/test/semantic.js @@ -1,8 +1,12 @@ describe('Semantic analizer', function() { + var getCtx = function(src) { + return ram.compile(src, {printErrors: false}).ctx + } + it('adds error if other that S-Expression or identifier are used as operations', function() { - var ctx = ram.compile('([]) ({}) (\'\') (nil) (0) (/x/)', null, true) + var ctx = getCtx('([]) ({}) (\'\') (nil) (0) (/x/)') expect(contains(ctx.errors[0], T.ARRAY)).toBe(true) expect(contains(ctx.errors[1], T.OBJECT)).toBe(true) expect(contains(ctx.errors[2], T.STRING)).toBe(true) @@ -14,103 +18,103 @@ describe('Semantic analizer', function() { describe('`def`', function() { it('can be used only in module scope', function() { - var ctx = ram.compile('((def x))', null, true) + var ctx = getCtx('((def x))') expect(contains(ctx.errors[0], 'def')).toBe(true) }) it('must have arguments', function() { - var ctx = ram.compile('(def)', null, true) + var ctx = getCtx('(def)') expect(contains(ctx.errors[0], 'def')).toBe(true) }) it('must have no more than two arguments', function() { - var ctx = ram.compile('(def x y z)', null, true) + var ctx = getCtx('(def x y z)') expect(contains(ctx.errors[0], 'def')).toBe(true) }) it('should add error when define a var more than once', function() { - var ctx = ram.compile('(def x) (def x)', null, true) + var ctx = getCtx('(def x) (def x)') expect(contains(ctx.errors[0], 'x')).toBe(true) }) it('should add error when define a qualified identifier', function() { - var ctx = ram.compile('(def x.y)', null, true) + var ctx = getCtx('(def x.y)') expect(contains(ctx.errors[0], 'x.y')).toBe(true) }) }) describe('`import`', function() { it('can be used only in module scope', function() { - var ctx = ram.compile('((import x y))', null, true) + var ctx = getCtx('((import x y))') expect(contains(ctx.errors[0], 'import')).toBe(true) }) it('must have arguments', function() { - var ctx = ram.compile('(import)', null, true) + var ctx = getCtx('(import)') expect(contains(ctx.errors[0], 'import')).toBe(true) }) it('must have exactly two arguments', function() { - var ctx = ram.compile('(import x y z)', null, true) + var ctx = getCtx('(import x y z)') expect(contains(ctx.errors[0], 'import')).toBe(true) }) }) describe('`new`', function() { it('can be used only in module scope', function() { - var ctx = ram.compile('(new)', null, true) + var ctx = getCtx('(new)') expect(contains(ctx.errors[0], 'new')).toBe(true) }) }) describe('`fn`', function() { it('must have a list as the first argument', function() { - var ctx = ram.compile('(fn x)', null, true) + var ctx = getCtx('(fn x)') expect(contains(ctx.errors[0], 'fn')).toBe(true) }) it('must have a list as the first argument', function() { - var ctx = ram.compile('(fn [3])', null, true) + var ctx = getCtx('(fn [3])') expect(contains(ctx.errors[0], T.NUMBER)).toBe(true) }) }) describe('`alter`', function() { it('must have arguments', function() { - var ctx = ram.compile('(alter)', null, true) + var ctx = getCtx('(alter)') expect(contains(ctx.errors[0], 'alter')).toBe(true) }) it('must have no more than two arguments', function() { - var ctx = ram.compile('(alter x y z)', null, true) + var ctx = getCtx('(alter x y z)') expect(contains(ctx.errors[0], 'alter')).toBe(true) }) }) describe('`R` identifier', function() { it('adds error if `R` is used as operator', function() { - var ctx = ram.compile('(R)', null, true) + var ctx = getCtx('(R)') expect(contains(ctx.errors[0], 'R')).toBe(true) }) it('adds error if `R` is used in S-Expression', function() { - var ctx = ram.compile('(x R)', null, true) + var ctx = getCtx('(x R)') expect(contains(ctx.errors[0], 'R')).toBe(true) }) it('adds error if `R` is used in Array', function() { - var ctx = ram.compile('(x [R])', null, true) + var ctx = getCtx('(x [R])') expect(contains(ctx.errors[0], 'R')).toBe(true) }) it('adds error if `R` is used in Object', function() { - var ctx = ram.compile('(x {:x R})', null, true) + var ctx = getCtx('(x {:x R})') expect(contains(ctx.errors[0], 'R')).toBe(true) }) }) it('adds error if ES keywords are used as identifier', function() { - var ctx = ram.compile('(if let for const)', null, true) + var ctx = getCtx('(if let for const)') expect(contains(ctx.errors[0], 'if')).toBe(true) expect(contains(ctx.errors[1], 'let')).toBe(true) expect(contains(ctx.errors[2], 'for')).toBe(true) @@ -118,7 +122,7 @@ describe('Semantic analizer', function() { }) it('adds error a builtin function is used as data in S-Expression', function() { - var ctx = ram.compile('(x fn alter new)', null, true) + var ctx = getCtx('(x fn alter new)') expect(contains(ctx.errors[0], 'fn')).toBe(true) expect(contains(ctx.errors[1], 'alter')).toBe(true) expect(contains(ctx.errors[2], 'new')).toBe(true)