feat(calculator): Parser um ^ (Potenz) und sqrt() erweitern

This commit is contained in:
2026-04-16 21:51:30 +02:00
parent 50319f8ba9
commit 10318008e6
+77 -25
View File
@@ -443,7 +443,8 @@ const Calculator = {
case '+': case '+':
case '-': case '-':
case '*': case '*':
case '/': { case '/':
case '^': {
// Wenn gerade ein Ergebnis angezeigt wird, damit weiterrechnen // Wenn gerade ein Ergebnis angezeigt wird, damit weiterrechnen
if (this._lastResult && this._currentExpr === '') { if (this._lastResult && this._currentExpr === '') {
this._currentExpr = this._lastResult; this._currentExpr = this._lastResult;
@@ -451,7 +452,7 @@ const Calculator = {
} }
// Doppelte Operatoren verhindern (letzten ersetzen) // Doppelte Operatoren verhindern (letzten ersetzen)
const last = this._currentExpr.slice(-1); const last = this._currentExpr.slice(-1);
if (/[+\-*/%]/.test(last)) { if (/[+\-*/%^]/.test(last)) {
this._currentExpr = this._currentExpr.slice(0, -1) + key; this._currentExpr = this._currentExpr.slice(0, -1) + key;
} else { } else {
this._currentExpr += key; this._currentExpr += key;
@@ -527,7 +528,7 @@ const Calculator = {
_evaluate(expr) { _evaluate(expr) {
try { try {
// Nur erlaubte Zeichen // Nur erlaubte Zeichen
const sanitized = expr.replace(/[^0-9+\-*/.%()]/g, ''); const sanitized = expr.replace(/[^0-9+\-*/.%()^a-z]/g, '');
if (!sanitized) return null; if (!sanitized) return null;
const tokens = this._tokenize(sanitized); const tokens = this._tokenize(sanitized);
@@ -551,6 +552,13 @@ const Calculator = {
while (i < expr.length) { while (i < expr.length) {
const ch = expr[i]; const ch = expr[i];
// Funktion: sqrt
if (expr.substring(i, i + 4) === 'sqrt') {
tokens.push({ type: 'func', value: 'sqrt' });
i += 4;
continue;
}
// Zahl (inkl. Dezimal) // Zahl (inkl. Dezimal)
if (/[0-9.]/.test(ch)) { if (/[0-9.]/.test(ch)) {
let num = ''; let num = '';
@@ -589,6 +597,13 @@ const Calculator = {
continue; continue;
} }
// Potenz-Operator
if (ch === '^') {
tokens.push({ type: 'op', value: '^' });
i++;
continue;
}
// Klammern // Klammern
if (ch === '(' || ch === ')') { if (ch === '(' || ch === ')') {
tokens.push({ type: 'paren', value: ch }); tokens.push({ type: 'paren', value: ch });
@@ -596,6 +611,11 @@ const Calculator = {
continue; continue;
} }
// Unbekannte Buchstaben
if (/[a-z]/.test(ch)) {
return null;
}
// Unbekanntes Zeichen // Unbekanntes Zeichen
return null; return null;
} }
@@ -605,6 +625,7 @@ const Calculator = {
/** /**
* Rekursiver Descent Parser mit Operator-Precedence * Rekursiver Descent Parser mit Operator-Precedence
* Hierarchie: parseExpr (+/-) → parseTerm (*\/%) → parsePower (^) → parseFactor
* @param {Array} tokens * @param {Array} tokens
* @returns {number|null} * @returns {number|null}
*/ */
@@ -614,36 +635,32 @@ const Calculator = {
function peek() { return tokens[pos]; } function peek() { return tokens[pos]; }
function consume() { return tokens[pos++]; } function consume() { return tokens[pos++]; }
// Expression: Term (('+' | '-') Term)*
function parseExpr() { function parseExpr() {
let left = parseTerm(); let left = parseTerm();
if (left === null) return null; if (left === null) return null;
while (pos < tokens.length) { while (pos < tokens.length) {
const t = peek(); const tk = peek();
if (!t || t.type !== 'op' || (t.value !== '+' && t.value !== '-')) break; if (!tk || tk.type !== 'op' || (tk.value !== '+' && tk.value !== '-')) break;
consume(); consume();
const right = parseTerm(); const right = parseTerm();
if (right === null) return null; if (right === null) return null;
left = t.value === '+' ? left + right : left - right; left = tk.value === '+' ? left + right : left - right;
} }
return left; return left;
} }
// Term: Factor (('*' | '/' | '%') Factor)*
function parseTerm() { function parseTerm() {
let left = parseFactor(); let left = parsePower();
if (left === null) return null; if (left === null) return null;
while (pos < tokens.length) { while (pos < tokens.length) {
const t = peek(); const tk = peek();
if (!t || t.type !== 'op' || (t.value !== '*' && t.value !== '/' && t.value !== '%')) break; if (!tk || tk.type !== 'op' || (tk.value !== '*' && tk.value !== '/' && tk.value !== '%')) break;
consume(); consume();
const right = parseFactor(); const right = parsePower();
if (right === null) return null; if (right === null) return null;
if (t.value === '*') { if (tk.value === '*') {
left = left * right; left = left * right;
} else if (t.value === '/') { } else if (tk.value === '/') {
if (right === 0) return null; if (right === 0) return null;
left = left / right; left = left / right;
} else { } else {
@@ -653,17 +670,51 @@ const Calculator = {
return left; return left;
} }
// Factor: Number | '(' Expression ')' // Power: Factor ('^' Power)? — rechts-assoziativ via Rekursion
function parseFactor() { function parsePower() {
const t = peek(); let base = parseFactor();
if (!t) return null; if (base === null) return null;
const tk = peek();
if (t.type === 'number') { if (tk && tk.type === 'op' && tk.value === '^') {
consume(); consume();
return t.value; const exp = parsePower(); // Rechts-assoziativ!
if (exp === null) return null;
return Math.pow(base, exp);
}
return base;
}
// Factor: func '(' Expression ')' | Number | '(' Expression ')'
function parseFactor() {
const tk = peek();
if (!tk) return null;
// Funktion: sqrt(...)
if (tk.type === 'func') {
const funcName = tk.value;
consume();
const open = peek();
if (!open || open.type !== 'paren' || open.value !== '(') return null;
consume();
const val = parseExpr();
if (val === null) return null;
const close = peek();
if (close && close.type === 'paren' && close.value === ')') {
consume();
}
if (funcName === 'sqrt') {
if (val < 0) return null; // Negativer Radikand nicht erlaubt
return Math.sqrt(val);
}
return null;
} }
if (t.type === 'paren' && t.value === '(') { if (tk.type === 'number') {
consume();
return tk.value;
}
if (tk.type === 'paren' && tk.value === '(') {
consume(); consume();
const val = parseExpr(); const val = parseExpr();
if (val === null) return null; if (val === null) return null;
@@ -708,7 +759,8 @@ const Calculator = {
_formatExpression(expr) { _formatExpression(expr) {
return expr return expr
.replace(/\*/g, '\u00D7') .replace(/\*/g, '\u00D7')
.replace(/\//g, '\u00F7'); .replace(/\//g, '\u00F7')
.replace(/sqrt\(/g, '\u221A(');
}, },
// ---- DISPLAY ---- // ---- DISPLAY ----