main.js (5784B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var join = require( 'path' ).join; 24 var readFile = require( '@stdlib/fs/read-file' ).sync; 25 var replace = require( '@stdlib/string/replace' ); 26 var isInteger = require( '@stdlib/assert/is-integer' ).isPrimitive; 27 28 29 // VARIABLES // 30 31 var opts = { 32 'encoding': 'utf8' 33 }; 34 var dir = join( __dirname, 'templates' ); 35 36 // Templates: 37 var COEFFICIENT_RATIO_TEMPLATE = readFile( join( dir, 'coefficient_ratio.js.txt' ), opts ); // eslint-disable-line id-length 38 var EVALRATIONAL_TEMPLATE = readFile( join( dir, 'evalrational.js.txt' ), opts ); 39 var LOOP_TEMPLATE = readFile( join( dir, 'loop.js.txt' ), opts ); 40 var NAN_TEMPLATE = readFile( join( dir, 'nan.js.txt' ), opts ); 41 var MAX_CHARS = 66; // max-len (80) - chars already in line ('2x tab': 8, 's1 = ': 5, ';': 1) 42 43 44 // FUNCTIONS // 45 46 /** 47 * Serializes a single value to a string. 48 * 49 * @private 50 * @param {number} x - value to serialize 51 * @returns {string} serialized value 52 */ 53 function value2string( x ) { 54 var str = x.toString(); 55 if ( isInteger( x ) ) { 56 str += '.0'; 57 } 58 return str; 59 } 60 61 /** 62 * Serializes an array of numbers to an indented newline separated list. 63 * 64 * @private 65 * @param {NumericArray} x - array of numbers 66 * @returns {string} serialized value 67 */ 68 function array2list( x ) { 69 var str; 70 var n; 71 var m; 72 var i; 73 74 n = x.length; 75 m = n - 1; 76 str = ''; 77 for ( i = 0; i < n; i++ ) { 78 str += '\t' + x[ i ].toString(); 79 if ( isInteger( x[ i ] ) ) { 80 str += '.0'; 81 } 82 if ( i < m ) { 83 str += ',\n'; 84 } 85 } 86 return str; 87 } 88 89 /** 90 * Serializes an array of coefficients to a string implementing Horner's method. 91 * 92 * @private 93 * @param {NumericArray} x - coefficients sorted in ascending degree 94 * @returns {string} output string 95 */ 96 function hornerAscending( x ) { 97 var str; 98 var n; 99 var m; 100 var i; 101 102 n = x.length; 103 m = n - 1; 104 str = x[ 0 ].toString(); 105 if ( isInteger( x[ 0 ] ) ) { 106 str += '.0'; 107 } 108 for ( i = 1; i < n; i++ ) { 109 str += ' + (x * '; 110 if ( i < m ) { 111 str += '('; 112 } 113 str += x[ i ].toString(); 114 if ( isInteger( x[ i ] ) ) { 115 str += '.0'; 116 } 117 } 118 // Close all the parentheses... 119 for ( i = 0; i < (2*m)-1; i++ ) { 120 str += ')'; 121 } 122 return str; 123 } 124 125 /** 126 * Serializes an array of coefficients to a string implementing Horner's method. 127 * 128 * @private 129 * @param {NumericArray} x - coefficients sorted in descending degree 130 * @returns {string} output string 131 */ 132 function hornerDescending( x ) { 133 var str; 134 var m; 135 var i; 136 137 m = x.length - 1; 138 str = x[ m ].toString(); 139 if ( isInteger( x[ m ] ) ) { 140 str += '.0'; 141 } 142 for ( i = m-1; i >= 0; i-- ) { 143 str += ' + (x * '; 144 if ( i > 0 ) { 145 str += '('; 146 } 147 str += x[ i ].toString(); 148 if ( isInteger( x[ i ] ) ) { 149 str += '.0'; 150 } 151 } 152 // Close all the parentheses... 153 for ( i = 0; i < (2*m)-1; i++ ) { 154 str += ')'; 155 } 156 return str; 157 } 158 159 /** 160 * Replaces the specified tag in a source string by the chosen replacement and adds directive to disable the maximum line length lint rule if necessary. 161 * 162 * @private 163 * @param {string} src - source string 164 * @param {string} target - target tag 165 * @param {string} str - replacement 166 * @returns {string} output string 167 */ 168 function replaceString( src, target, str ) { 169 var out = replace( src, '{{'+target+'}}', str ); 170 if ( str.length > MAX_CHARS ) { 171 out = replace( out, '{{'+target+'_ESLINT}}', ' // eslint-disable-line max-len' ); 172 } else { 173 out = replace( out, '{{'+target+'_ESLINT}}', '' ); 174 } 175 return out; 176 } 177 178 179 // MAIN // 180 181 /** 182 * Compiles a module string which exports a function for evaluating a rational function. 183 * 184 * @param {NumericArray} P - numerator polynomial coefficients sorted in ascending degree 185 * @param {NumericArray} Q - denominator polynomial coefficients sorted in ascending degree 186 * @returns {string} module string exporting a function for evaluating a rational function 187 * 188 * @example 189 * var P = [ -6.0, -5.0 ]; 190 * var Q = [ 3.0, 0.5 ]; 191 * 192 * var str = compile( P, Q ); 193 * // returns <string> 194 */ 195 function compile( P, Q ) { 196 var str; 197 var n; 198 199 n = P.length; 200 201 // If no coefficients, the function always returns NaN... 202 if ( n === 0 ) { 203 return NAN_TEMPLATE; 204 } 205 // If P and Q have different lengths, the function always returns NaN... 206 if ( n !== Q.length ) { 207 return NAN_TEMPLATE; 208 } 209 // If P and Q only have one coefficient, the function always returns the ratio of those coefficients... 210 if ( n === 1 ) { 211 return replace( COEFFICIENT_RATIO_TEMPLATE, '{{ratio}}', value2string( P[0] / Q[0] ) ); 212 } 213 // Avoid exceeding the maximum stack size on V8 by using a simple loop :(. Note that the choice of `500` was empirically determined... 214 if ( n > 500 ) { 215 str = replace( LOOP_TEMPLATE, '{{P}}', array2list( P ) ); 216 str = replace( str, '{{Q}}', array2list( Q ) ); 217 return replace( str, '{{ratio}}', value2string( P[0]/Q[0] ) ); 218 } 219 // If more than one coefficient, apply Horner's method... 220 str = replaceString( EVALRATIONAL_TEMPLATE, 'P_ASCENDING', hornerAscending( P ) ); 221 str = replaceString( str, 'Q_ASCENDING', hornerAscending( Q ) ); 222 str = replaceString( str, 'P_DESCENDING', hornerDescending( P ) ); 223 str = replaceString( str, 'Q_DESCENDING', hornerDescending( Q ) ); 224 return replace( str, '{{ratio}}', value2string( P[0]/Q[0] ) ); 225 } 226 227 228 // EXPORTS // 229 230 module.exports = compile;