178 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			178 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | var use = require('use'); | ||
|  | var define = require('define-property'); | ||
|  | var debug = require('debug')('snapdragon:compiler'); | ||
|  | var utils = require('./utils'); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Create a new `Compiler` with the given `options`. | ||
|  |  * @param {Object} `options` | ||
|  |  */ | ||
|  | 
 | ||
|  | function Compiler(options, state) { | ||
|  |   debug('initializing', __filename); | ||
|  |   this.options = utils.extend({source: 'string'}, options); | ||
|  |   this.state = state || {}; | ||
|  |   this.compilers = {}; | ||
|  |   this.output = ''; | ||
|  |   this.set('eos', function(node) { | ||
|  |     return this.emit(node.val, node); | ||
|  |   }); | ||
|  |   this.set('noop', function(node) { | ||
|  |     return this.emit(node.val, node); | ||
|  |   }); | ||
|  |   this.set('bos', function(node) { | ||
|  |     return this.emit(node.val, node); | ||
|  |   }); | ||
|  |   use(this); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Prototype methods | ||
|  |  */ | ||
|  | 
 | ||
|  | Compiler.prototype = { | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Throw an error message with details including the cursor position. | ||
|  |    * @param {String} `msg` Message to use in the Error. | ||
|  |    */ | ||
|  | 
 | ||
|  |   error: function(msg, node) { | ||
|  |     var pos = node.position || {start: {column: 0}}; | ||
|  |     var message = this.options.source + ' column:' + pos.start.column + ': ' + msg; | ||
|  | 
 | ||
|  |     var err = new Error(message); | ||
|  |     err.reason = msg; | ||
|  |     err.column = pos.start.column; | ||
|  |     err.source = this.pattern; | ||
|  | 
 | ||
|  |     if (this.options.silent) { | ||
|  |       this.errors.push(err); | ||
|  |     } else { | ||
|  |       throw err; | ||
|  |     } | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Define a non-enumberable property on the `Compiler` instance. | ||
|  |    * | ||
|  |    * ```js
 | ||
|  |    * compiler.define('foo', 'bar'); | ||
|  |    * ```
 | ||
|  |    * @name .define | ||
|  |    * @param {String} `key` propery name | ||
|  |    * @param {any} `val` property value | ||
|  |    * @return {Object} Returns the Compiler instance for chaining. | ||
|  |    * @api public | ||
|  |    */ | ||
|  | 
 | ||
|  |   define: function(key, val) { | ||
|  |     define(this, key, val); | ||
|  |     return this; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Emit `node.val` | ||
|  |    */ | ||
|  | 
 | ||
|  |   emit: function(str, node) { | ||
|  |     this.output += str; | ||
|  |     return str; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Add a compiler `fn` with the given `name` | ||
|  |    */ | ||
|  | 
 | ||
|  |   set: function(name, fn) { | ||
|  |     this.compilers[name] = fn; | ||
|  |     return this; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Get compiler `name`. | ||
|  |    */ | ||
|  | 
 | ||
|  |   get: function(name) { | ||
|  |     return this.compilers[name]; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Get the previous AST node. | ||
|  |    */ | ||
|  | 
 | ||
|  |   prev: function(n) { | ||
|  |     return this.ast.nodes[this.idx - (n || 1)] || { type: 'bos', val: '' }; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Get the next AST node. | ||
|  |    */ | ||
|  | 
 | ||
|  |   next: function(n) { | ||
|  |     return this.ast.nodes[this.idx + (n || 1)] || { type: 'eos', val: '' }; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Visit `node`. | ||
|  |    */ | ||
|  | 
 | ||
|  |   visit: function(node, nodes, i) { | ||
|  |     var fn = this.compilers[node.type]; | ||
|  |     this.idx = i; | ||
|  | 
 | ||
|  |     if (typeof fn !== 'function') { | ||
|  |       throw this.error('compiler "' + node.type + '" is not registered', node); | ||
|  |     } | ||
|  |     return fn.call(this, node, nodes, i); | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Map visit over array of `nodes`. | ||
|  |    */ | ||
|  | 
 | ||
|  |   mapVisit: function(nodes) { | ||
|  |     if (!Array.isArray(nodes)) { | ||
|  |       throw new TypeError('expected an array'); | ||
|  |     } | ||
|  |     var len = nodes.length; | ||
|  |     var idx = -1; | ||
|  |     while (++idx < len) { | ||
|  |       this.visit(nodes[idx], nodes, idx); | ||
|  |     } | ||
|  |     return this; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Compile `ast`. | ||
|  |    */ | ||
|  | 
 | ||
|  |   compile: function(ast, options) { | ||
|  |     var opts = utils.extend({}, this.options, options); | ||
|  |     this.ast = ast; | ||
|  |     this.parsingErrors = this.ast.errors; | ||
|  |     this.output = ''; | ||
|  | 
 | ||
|  |     // source map support
 | ||
|  |     if (opts.sourcemap) { | ||
|  |       var sourcemaps = require('./source-maps'); | ||
|  |       sourcemaps(this); | ||
|  |       this.mapVisit(this.ast.nodes); | ||
|  |       this.applySourceMaps(); | ||
|  |       this.map = opts.sourcemap === 'generator' ? this.map : this.map.toJSON(); | ||
|  |       return this; | ||
|  |     } | ||
|  | 
 | ||
|  |     this.mapVisit(this.ast.nodes); | ||
|  |     return this; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Expose `Compiler` | ||
|  |  */ | ||
|  | 
 | ||
|  | module.exports = Compiler; |