hyperformula
Version:
HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas
249 lines (247 loc) • 5.75 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.RomanPlugin = void 0;
var _Cell = require("../../Cell");
var _errorMessage = require("../../error-message");
var _InterpreterValue = require("../InterpreterValue");
var _FunctionPlugin = require("./FunctionPlugin");
/**
* @license
* Copyright (c) 2025 Handsoncode. All rights reserved.
*/
class RomanPlugin extends _FunctionPlugin.FunctionPlugin {
roman(ast, state) {
return this.runFunction(ast.args, state, this.metadata('ROMAN'), (val, mode) => {
val = Math.trunc(val);
if (mode === false) {
mode = 4;
} else if (mode === true) {
mode = 0;
}
mode = (0, _InterpreterValue.getRawValue)(this.coerceScalarToNumberOrError(mode));
if (mode instanceof _Cell.CellError) {
return mode;
}
mode = Math.trunc(mode);
if (mode < 0) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.ValueSmall);
}
if (mode > 4) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.ValueLarge);
}
return romanMode(val, mode);
});
}
arabic(ast, state) {
return this.runFunction(ast.args, state, this.metadata('ARABIC'), inputString => {
inputString = inputString.trim().toUpperCase();
let minusSign = false;
if (inputString.startsWith('-')) {
inputString = inputString.slice(1);
minusSign = true;
if (inputString === '') {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.InvalidRoman);
}
}
const work = {
input: inputString,
acc: 0
};
eatToken(work, {
token: 'MMM',
val: 3000
}, {
token: 'MM',
val: 2000
}, {
token: 'M',
val: 1000
});
eatToken(work, {
token: 'IM',
val: 999
}, {
token: 'VM',
val: 995
}, {
token: 'XM',
val: 990
}, {
token: 'LM',
val: 950
}, {
token: 'CM',
val: 900
});
eatToken(work, {
token: 'D',
val: 500
}, {
token: 'ID',
val: 499
}, {
token: 'VD',
val: 495
}, {
token: 'XD',
val: 490
}, {
token: 'LD',
val: 450
}, {
token: 'CD',
val: 400
});
eatToken(work, {
token: 'CCC',
val: 300
}, {
token: 'CC',
val: 200
}, {
token: 'C',
val: 100
});
eatToken(work, {
token: 'IC',
val: 99
}, {
token: 'VC',
val: 95
}, {
token: 'XC',
val: 90
});
eatToken(work, {
token: 'L',
val: 50
}, {
token: 'IL',
val: 49
}, {
token: 'VL',
val: 45
}, {
token: 'XL',
val: 40
});
eatToken(work, {
token: 'XXX',
val: 30
}, {
token: 'XX',
val: 20
}, {
token: 'X',
val: 10
});
eatToken(work, {
token: 'IX',
val: 9
});
eatToken(work, {
token: 'V',
val: 5
}, {
token: 'IV',
val: 4
});
eatToken(work, {
token: 'III',
val: 3
}, {
token: 'II',
val: 2
}, {
token: 'I',
val: 1
});
if (work.input !== '') {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.InvalidRoman);
} else {
return minusSign ? -work.acc : work.acc;
}
});
}
}
exports.RomanPlugin = RomanPlugin;
RomanPlugin.implementedFunctions = {
'ROMAN': {
method: 'roman',
parameters: [{
argumentType: _FunctionPlugin.FunctionArgumentType.NUMBER,
minValue: 1,
lessThan: 4000
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.NOERROR,
optionalArg: true,
defaultValue: 0
}]
},
'ARABIC': {
method: 'arabic',
parameters: [{
argumentType: _FunctionPlugin.FunctionArgumentType.STRING
}]
}
};
function eatToken(inputAcc, ...tokens) {
for (const token of tokens) {
if (inputAcc.input.startsWith(token.token)) {
inputAcc.input = inputAcc.input.slice(token.token.length);
inputAcc.acc += token.val;
break;
}
}
}
function romanMode(input, mode) {
const work = {
val: input % 1000,
acc: 'M'.repeat(Math.floor(input / 1000))
};
if (mode === 4) {
absorb(work, 'IM', 999, 1000);
absorb(work, 'ID', 499, 500);
}
if (mode >= 3) {
absorb(work, 'VM', 995, 1000);
absorb(work, 'VD', 495, 500);
}
if (mode >= 2) {
absorb(work, 'XM', 990, 1000);
absorb(work, 'XD', 490, 500);
}
if (mode >= 1) {
absorb(work, 'LM', 950, 1000);
absorb(work, 'LD', 450, 500);
}
absorb(work, 'CM', 900, 1000);
absorb(work, 'CD', 400, 500);
absorb(work, 'D', 500, 900);
work.acc += 'C'.repeat(Math.floor(work.val / 100));
work.val %= 100;
if (mode >= 2) {
absorb(work, 'IC', 99, 100);
absorb(work, 'IL', 49, 50);
}
if (mode >= 1) {
absorb(work, 'VC', 95, 100);
absorb(work, 'VL', 45, 50);
}
absorb(work, 'XC', 90, 100);
absorb(work, 'XL', 40, 50);
absorb(work, 'L', 50, 90);
work.acc += 'X'.repeat(Math.floor(work.val / 10));
work.val %= 10;
absorb(work, 'IX', 9, 10);
absorb(work, 'IV', 4, 5);
absorb(work, 'V', 5, 9);
work.acc += 'I'.repeat(work.val);
return work.acc;
}
function absorb(valAcc, token, lower, upper) {
if (valAcc.val >= lower && valAcc.val < upper) {
valAcc.val -= lower;
valAcc.acc += token;
}
}