diff --git a/ulm-js/.gitignore b/ulm-js/.gitignore index 7e123ad..67fc443 100644 --- a/ulm-js/.gitignore +++ b/ulm-js/.gitignore @@ -1,2 +1,3 @@ /*.js +/*.d.ts /node_modules diff --git a/ulm-js/AST.ts b/ulm-js/AST.ts index d347b8f..b787b6b 100644 --- a/ulm-js/AST.ts +++ b/ulm-js/AST.ts @@ -24,17 +24,30 @@ type NumericOperation = | NumericAdditionOperation | NumericMultiplicationOperation; -type NaturalNumber = number; +interface NaturalNumber { + type: "NaturalNumber"; + value: number; +} -type IntegerNumber = number; +interface WholeNumber { + type: "WholeNumber"; + value: number; +} + +interface IntegerNumber { + type: "IntegerNumber"; + value: number; +} interface RationalNumber { type: "RationalNumber"; - numerator: IntegerNumber; - denominator: NaturalNumber; + numerator: number; + denominator: number; } -type NumericExpression = RationalNumber | NumericOperation; +type Number = RationalNumber | IntegerNumber | WholeNumber | NaturalNumber; + +type NumericExpression = Number | NumericOperation; type Expression = NumericExpression | NumericRelation; @@ -55,6 +68,14 @@ const isExpression = (e: AST | Expression): e is Expression => export { Expression, + RationalNumber, + IntegerNumber, + WholeNumber, + NaturalNumber, + NumericMultiplicationOperation, + NumericAdditionOperation, + NumericNotEqualRelation, + NumericEqualRelation, isExpression, isNumericExpression, isNumericOperation, diff --git a/ulm-js/errorMessage.ts b/ulm-js/errorMessage.ts new file mode 100644 index 0000000..091b4e5 --- /dev/null +++ b/ulm-js/errorMessage.ts @@ -0,0 +1,10 @@ +import { CodePoint } from "./Token"; + +const errorMessage = ( + message: string, + { line, column }: CodePoint, + file: string | null +) => + [message, `${file ? `In: ${file} at ` : "At: "}${line}:${column}`].join("\n"); + +export default errorMessage; diff --git a/ulm-js/index.ts b/ulm-js/index.ts index 5f145e8..41ccef8 100644 --- a/ulm-js/index.ts +++ b/ulm-js/index.ts @@ -2,3 +2,4 @@ export { default as parse } from "./parse"; export { default as tokenize } from "./tokenize"; export { default as compile } from "./compile"; export { default as toLatex } from "./toLatex"; +export { default as AST } from "./AST"; diff --git a/ulm-js/package.json b/ulm-js/package.json index fb89126..f899ef8 100644 --- a/ulm-js/package.json +++ b/ulm-js/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "license": "MIT", "scripts": { - "build": "tsc index.ts --downlevelIteration", + "build": "tsc index.ts --downlevelIteration -d", "test": "tsc test.ts --downlevelIteration && node test.js" }, "devDependencies": { diff --git a/ulm-js/parse.test.ts b/ulm-js/parse.test.ts index e7c0285..7b4daaa 100644 --- a/ulm-js/parse.test.ts +++ b/ulm-js/parse.test.ts @@ -212,6 +212,56 @@ const tokens: Token[] = [ line: 0, column: 0 } + }, + { + type: "OperatorName", + value: "RationalNumber", + file: null, + location: { + index: 0, + line: 0, + column: 0 + } + }, + { + type: "LeftParenthesis", + value: "(", + file: null, + location: { + index: 0, + line: 0, + column: 0 + } + }, + { + type: "IntegerNumberLiteral", + value: "-13i", + file: null, + location: { + index: 0, + line: 0, + column: 0 + } + }, + { + type: "NaturalNumberLiteral", + value: "25n", + file: null, + location: { + index: 0, + line: 0, + column: 0 + } + }, + { + type: "RightParenthesis", + value: ")", + file: null, + location: { + index: 0, + line: 0, + column: 0 + } } ]; @@ -242,11 +292,12 @@ assert.deepEqual(parse(tokens), [ { type: "RationalNumber", numerator: -2, denominator: 1 }, { type: "RationalNumber", numerator: 23, denominator: 5 }, { type: "RationalNumber", numerator: 558, denominator: 100 }, - { type: "RationalNumber", numerator: 13, denominator: 1 }, - { type: "RationalNumber", numerator: 3, denominator: 1 }, - { type: "RationalNumber", numerator: 0, denominator: 1 }, - { type: "RationalNumber", numerator: -34, denominator: 1 }, - { type: "RationalNumber", numerator: 2, denominator: 1 }, - { type: "RationalNumber", numerator: 33, denominator: 1 }, - { type: "RationalNumber", numerator: 1, denominator: 1 } + { type: "NaturalNumber", value: 13 }, + { type: "WholeNumber", value: 3 }, + { type: "WholeNumber", value: 0 }, + { type: "IntegerNumber", value: -34 }, + { type: "IntegerNumber", value: 2 }, + { type: "IntegerNumber", value: 33 }, + { type: "RationalNumber", numerator: 1, denominator: 1 }, + { type: "RationalNumber", numerator: -13, denominator: 25 } ]); diff --git a/ulm-js/parse.ts b/ulm-js/parse.ts index 5467fab..f0da311 100644 --- a/ulm-js/parse.ts +++ b/ulm-js/parse.ts @@ -1,5 +1,6 @@ import Token from "./Token"; import AST, { Expression, isNumericExpression } from "./AST"; +import errorMessage from "./errorMessage"; const parse = (tokens: Token[]): AST => { let ast = []; @@ -8,7 +9,7 @@ const parse = (tokens: Token[]): AST => { const walk = (): Expression => { if (token.type === "OperatorName") { - let operator = token.value; + let { value: operator, location, file } = token; index += 1; token = tokens[index]; @@ -22,19 +23,29 @@ const parse = (tokens: Token[]): AST => { operator === "NumericAdditionOperation" ) { if (operands.length !== 2) { - throw `Wrong number of operands. Expected 2, found ${ - operands.length - }.`; + throw errorMessage( + `Wrong number of operands. Expected 2, found ${operands.length}.`, + location, + file + ); } let [a, b] = operands; if (!isNumericExpression(a)) { - throw `Unexpected operand: ${a.type} Expected: 'NumericExpression'.`; + throw errorMessage( + `Unexpected operand: '${a.type}' Expected: 'NumericExpression'.`, + location, + file + ); } if (!isNumericExpression(b)) { - throw `Unexpected operand: ${b.type} Expected: 'NumericExpression'.`; + throw errorMessage( + `Unexpected operand: '${b.type}' Expected: 'NumericExpression'.`, + location, + file + ); } return { @@ -43,16 +54,85 @@ const parse = (tokens: Token[]): AST => { }; } - throw `Unknown operator ${operator}`; + if (operator === "RationalNumber") { + if (operands.length !== 2) { + throw errorMessage( + `Wrong number of operands. Expected 2, found ${operands.length}.`, + location, + file + ); + } + + let [numerator, denominator] = operands; + + if (numerator.type !== "IntegerNumber") { + throw errorMessage( + `Unexpected operand: '${ + numerator.type + }' Expected: 'IntegerNumber'.`, + location, + file + ); + } + + if (denominator.type !== "NaturalNumber") { + throw errorMessage( + `Unexpected operand: '${ + denominator.type + }' Expected: 'NaturalNumber'.`, + location, + file + ); + } + + return { + type: operator, + numerator: numerator.value, + denominator: denominator.value + }; + } + + throw errorMessage(`Unknown operator: '${operator}'`, location, file); + } + + if (token.type === "NaturalNumberLiteral") { + let value = Number(token.value.slice(0, -1).replace(/_/g, "")); + + index += 1; + token = tokens[index]; + + return { + type: "NaturalNumber", + value + }; + } + + if (token.type === "WholeNumberLiteral") { + let value = Number(token.value.slice(0, -1).replace(/_/g, "")); + + index += 1; + token = tokens[index]; + + return { + type: "WholeNumber", + value + }; + } + + if (token.type === "IntegerNumberLiteral") { + let value = Number(token.value.slice(0, -1).replace(/_/g, "")); + + index += 1; + token = tokens[index]; + + return { + type: "IntegerNumber", + value + }; } - if ( - token.type === "NaturalNumberLiteral" || - token.type === "WholeNumberLiteral" || - token.type === "IntegerNumberLiteral" || - token.type === "RationalNumberLiteral" - ) { - let value = token.value.replace(/_/g, "").replace(/[nwi]$/, ""); + if (token.type === "RationalNumberLiteral") { + let value = token.value.replace(/_/g, ""); let numerator, denominator; if (value.includes(".")) { const [n, d] = value.split("."); @@ -77,18 +157,29 @@ const parse = (tokens: Token[]): AST => { }; } - console.log(token); - throw `What's this?`; + throw errorMessage( + `Unexpected token: '${token.type}'`, + token.location, + token.file + ); }; const walkParentheses = (): Expression[] => { let expressions = []; + if (!token) { + throw `Unexpected end of file. Expected: 'LeftParenthesis'`; + } + if (token.type === "LeftParenthesis") { index += 1; token = tokens[index]; } else { - throw `Unexpected token: '${token.type}' Expected: 'LeftParenthesis'`; + throw errorMessage( + `Unexpected token: '${token.type}' Expected: 'LeftParenthesis'`, + token.location, + token.file + ); } while (token && token.type !== "RightParenthesis") { @@ -96,7 +187,7 @@ const parse = (tokens: Token[]): AST => { } if (!token) { - throw `Unexpected end of file. Expected 'RightParenthesis'`; + throw `Unexpected end of file. Expected: 'RightParenthesis'`; } index += 1; diff --git a/ulm-js/test.ts b/ulm-js/test.ts index b56e87d..f900b72 100644 --- a/ulm-js/test.ts +++ b/ulm-js/test.ts @@ -16,5 +16,5 @@ assert.equal( "NumericEqualRelation(NumericAdditionOperation(12_312, 0.33), 1/4), 10" ) ), - "12312 + \\frac{33}{100} = \\frac{1}{4}\\\\[16pt]10" + "(12312 + \\frac{33}{100}) = \\frac{1}{4}\\\\[16pt]10" ); diff --git a/ulm-js/toLatex.test.ts b/ulm-js/toLatex.test.ts index 5aca8f9..1b6d647 100644 --- a/ulm-js/toLatex.test.ts +++ b/ulm-js/toLatex.test.ts @@ -30,5 +30,5 @@ assert.equal( denominator: 1 } ]), - "12312 + \\frac{33}{100} = \\frac{1}{4}\\\\[16pt]10" + "(12312 + \\frac{33}{100}) = \\frac{1}{4}\\\\[16pt]10" ); diff --git a/ulm-js/toLatex.ts b/ulm-js/toLatex.ts index 2058400..dbfca0c 100644 --- a/ulm-js/toLatex.ts +++ b/ulm-js/toLatex.ts @@ -12,20 +12,22 @@ const expressionToLatex = (expression: Expression): string => { )}`; } if (expression.type === "NumericAdditionOperation") { - return `${toLatex(expression.operands[0])} + ${toLatex( + return `(${toLatex(expression.operands[0])} + ${toLatex( expression.operands[1] - )}`; + )})`; } if (expression.type === "NumericMultiplicationOperation") { - return `${toLatex(expression.operands[0])} \\times ${toLatex( + return `(${toLatex(expression.operands[0])} \\times ${toLatex( expression.operands[1] - )}`; + )})`; } if (expression.type === "RationalNumber") { return expression.denominator === 1 ? expression.numerator.toString() : `\\frac{${expression.numerator}}{${expression.denominator}}`; } + + throw `Unsupported expression: '${expression.type}'`; }; const toLatex = (x: Expression | AST): string => diff --git a/ulm-js/tokenize.ts b/ulm-js/tokenize.ts index 4fa7aee..6f59a63 100644 --- a/ulm-js/tokenize.ts +++ b/ulm-js/tokenize.ts @@ -1,4 +1,5 @@ -import Token, { CodePoint } from "./Token"; +import Token from "./Token"; +import errorMessage from "./errorMessage"; const SIGNS = new Set(["+", "-"]); @@ -18,13 +19,6 @@ const NAME = new Set([ ..."_-!*/\\%^+÷=≠|" ]); -const errorMessage = ( - message: string, - { line, column }: CodePoint, - file: string | null -) => - [message, `${file ? `In: ${file} at ` : "At: "}${line}:${column}`].join("\n"); - const format = (string?: string): string => string ? `'${string}' (${string