mirror of
https://github.com/BorysLevytskyi/BitwiseCmd.git
synced 2026-01-26 13:44:17 +01:00
Migrate to typescript (#14)
* Move BitwiseCmd to typescript * Add serve-build http command * Add Env type * Add CNAME and sitemap.xml files * Add files via upload
This commit is contained in:
12
src/expression/BitwiseOperationExpression.ts
Normal file
12
src/expression/BitwiseOperationExpression.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ExpressionInput, ExpressionInputItem } from "./expression-interfaces";
|
||||
|
||||
export default class BitwiseOperationExpression implements ExpressionInput {
|
||||
|
||||
expressionString: string;
|
||||
expressionItems: ExpressionInputItem[];
|
||||
|
||||
constructor(expressionString: string, expressions: ExpressionInputItem[]) {
|
||||
this.expressionString = expressionString;
|
||||
this.expressionItems = expressions;
|
||||
}
|
||||
}
|
||||
19
src/expression/ExpressionOperand.test.ts
Normal file
19
src/expression/ExpressionOperand.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import NumericOperand from "./NumericOperand";
|
||||
import ExpressionOperand from './ExpressionOperand';
|
||||
|
||||
it('can apply ~ operand', () => {
|
||||
var op = new NumericOperand(10, 'dec');
|
||||
var expr = new ExpressionOperand("~10", op, "~");
|
||||
var result = expr.evaluate();
|
||||
expect(result.value).toBe(-11);
|
||||
expect(result.base).toBe('dec');
|
||||
});
|
||||
|
||||
it('can apply & operand', () => {
|
||||
var op1 = new NumericOperand(3, 'dec');
|
||||
var op2 = new NumericOperand(4, 'dec');
|
||||
var expr = new ExpressionOperand("|3", op1, "|");
|
||||
var result = expr.evaluate(op2);
|
||||
expect(result.value).toBe(7);
|
||||
expect(result.base).toBe('dec');
|
||||
});
|
||||
48
src/expression/ExpressionOperand.ts
Normal file
48
src/expression/ExpressionOperand.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import NumericOperand from './NumericOperand';
|
||||
import { ExpressionInputItem } from './expression-interfaces';
|
||||
|
||||
export default class ExpressionOperand implements ExpressionInputItem {
|
||||
expressionString: string;
|
||||
operand: ExpressionInputItem;
|
||||
sign: string;
|
||||
isExpression: boolean;
|
||||
isShiftExpression: boolean;
|
||||
isNotExpression: boolean;
|
||||
|
||||
constructor(expressionString : string, operand : ExpressionInputItem, sign : string) {
|
||||
this.expressionString = expressionString;
|
||||
this.operand = operand;
|
||||
this.sign = sign;
|
||||
this.isExpression = true;
|
||||
this.isShiftExpression = this.sign.indexOf('<') >= 0 || this.sign.indexOf('>')>= 0;
|
||||
this.isNotExpression = this.sign === '~';
|
||||
}
|
||||
|
||||
evaluate(operand?: NumericOperand) : NumericOperand {
|
||||
if (operand instanceof ExpressionOperand) {
|
||||
throw new Error('value shouldnt be expression');
|
||||
}
|
||||
|
||||
var evaluatedOperand = this.operand.evaluate();
|
||||
|
||||
var str = '';
|
||||
if(this.sign == '~'){
|
||||
str = '~' + evaluatedOperand.value;
|
||||
} else {
|
||||
if(operand == null)
|
||||
throw new Error("Other is required for expression: " + this.expressionString)
|
||||
|
||||
str = operand.value + this.sign + evaluatedOperand.value;
|
||||
}
|
||||
|
||||
return NumericOperand.create(eval(str), evaluatedOperand.base);
|
||||
}
|
||||
|
||||
getUnderlyingOperand() : NumericOperand {
|
||||
return this.operand.getUnderlyingOperand();
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.sign + this.operand.toString();
|
||||
}
|
||||
}
|
||||
7
src/expression/ListOfNumbersExpression.test.ts
Normal file
7
src/expression/ListOfNumbersExpression.test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import NumericOperand from "./NumericOperand";
|
||||
import ListOfNumbersExpression from "./ListOfNumbersExpression";
|
||||
|
||||
it('calculates max bits length', () => {
|
||||
var expr = new ListOfNumbersExpression("10 0xabef 0b01010", [NumericOperand.parse("10"), NumericOperand.parse("0xabef"), NumericOperand.parse("0b01010")])
|
||||
expect(expr.maxBitsLength).toBe(16);
|
||||
});
|
||||
18
src/expression/ListOfNumbersExpression.ts
Normal file
18
src/expression/ListOfNumbersExpression.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import NumericOperand from "./NumericOperand";
|
||||
import { ExpressionInput, ExpressionInputItem } from "./expression-interfaces";
|
||||
|
||||
export default class ListOfNumbersExpression implements ExpressionInput {
|
||||
numbers: NumericOperand[];
|
||||
expressionString: string;
|
||||
maxBitsLength: number;
|
||||
|
||||
constructor(expressionString: string, numbers: NumericOperand[]) {
|
||||
this.expressionString = expressionString;
|
||||
this.numbers = numbers;
|
||||
this.maxBitsLength = numbers.map(n => n.lengthInBits).reduce((n , c) => n >= c ? n : c, 0);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.numbers.map(n => n.value.toString()).join(' ');
|
||||
}
|
||||
}
|
||||
13
src/expression/NumericOperand.test.ts
Normal file
13
src/expression/NumericOperand.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import NumericOperand from './NumericOperand';
|
||||
|
||||
it('parsed from string', () => {
|
||||
var op = NumericOperand.parse('123');
|
||||
expect(op.base).toBe('dec');
|
||||
expect(op.value).toBe(123);
|
||||
});
|
||||
|
||||
it('can get other kind', () => {
|
||||
var op = new NumericOperand(10, 'dec');
|
||||
expect(op.getOtherBase('hex')).toBe('dec');
|
||||
expect(op.getOtherBase('bin')).toBe('hex');
|
||||
});
|
||||
116
src/expression/NumericOperand.ts
Normal file
116
src/expression/NumericOperand.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import {numberParser} from './numberParser';
|
||||
import { ExpressionInputItem, NumberBase } from './expression-interfaces';
|
||||
|
||||
var globalId : number = 1;
|
||||
|
||||
// Represents numeric value
|
||||
export default class NumericOperand implements ExpressionInputItem {
|
||||
id: number;
|
||||
value: number;
|
||||
base: NumberBase;
|
||||
lengthInBits: number;
|
||||
isExpression: boolean;
|
||||
|
||||
constructor(value : number, base?: NumberBase) {
|
||||
this.id = globalId++;
|
||||
this.value = value;
|
||||
this.base = base || "dec";
|
||||
this.lengthInBits = NumericOperand.getBitLength(this.value);
|
||||
this.isExpression = false;
|
||||
}
|
||||
|
||||
getLengthInBits() {
|
||||
if(this.value < 0) {
|
||||
return 32;
|
||||
}
|
||||
return Math.floor(Math.log(this.value) / Math.log(2)) + 1;
|
||||
};
|
||||
|
||||
getOtherBase(kind?: NumberBase) : NumberBase {
|
||||
switch(kind || this.base) {
|
||||
case 'dec':
|
||||
case 'bin':
|
||||
return 'hex';
|
||||
case 'hex': return 'dec';
|
||||
default : throw new Error(kind + " kind doesn't have opposite kind")
|
||||
}
|
||||
};
|
||||
|
||||
toString(base?: NumberBase) : string {
|
||||
return NumericOperand.toBaseString(this.value, base || this.base);
|
||||
}
|
||||
|
||||
toOtherKindString() : string {
|
||||
return this.toString(this.getOtherBase());
|
||||
}
|
||||
|
||||
toDecimalString() {
|
||||
return this.toString('dec');
|
||||
}
|
||||
|
||||
toHexString() {
|
||||
return this.toString('hex');
|
||||
}
|
||||
|
||||
toBinaryString() : string {
|
||||
return this.toString('bin');
|
||||
}
|
||||
|
||||
setValue(value : number) {
|
||||
this.value = value;
|
||||
this.lengthInBits = NumericOperand.getBitLength(value);
|
||||
}
|
||||
|
||||
evaluate() : NumericOperand {
|
||||
return this;
|
||||
}
|
||||
|
||||
getUnderlyingOperand() : NumericOperand {
|
||||
return this
|
||||
}
|
||||
|
||||
static getBitLength(num : number) {
|
||||
return Math.floor(Math.log(num) / Math.log(2)) + 1;
|
||||
}
|
||||
|
||||
static getBase(kind : string){
|
||||
switch (kind){
|
||||
case 'bin': return 2;
|
||||
case 'hex': return 16;
|
||||
case 'dec': return 10;
|
||||
}
|
||||
};
|
||||
|
||||
static create(value : number, base? : NumberBase) {
|
||||
return new NumericOperand(value, base || "dec");
|
||||
};
|
||||
|
||||
static parse(input: string) : NumericOperand {
|
||||
|
||||
var parsed = numberParser.parse(input);
|
||||
|
||||
if(!parsed) {
|
||||
throw new Error(input + " is not a valid number");
|
||||
}
|
||||
|
||||
return new NumericOperand(parsed.value, parsed.base);
|
||||
}
|
||||
|
||||
static toBaseString(value : number, base : NumberBase) : string {
|
||||
switch(base) {
|
||||
case 'hex':
|
||||
var hexVal = Math.abs(value).toString(16);
|
||||
return value >= 0 ? '0x' + hexVal : '-0x' + hexVal;
|
||||
case 'bin':
|
||||
return (value>>>0).toString(2);
|
||||
case 'dec':
|
||||
return value.toString(10);
|
||||
default:
|
||||
throw new Error("Unexpected kind: " + base)
|
||||
}
|
||||
};
|
||||
|
||||
static toHexString (hex : string) {
|
||||
return hex.indexOf('-') === 0 ? '-0x' + hex.substr(1) : '0x' + hex;
|
||||
};
|
||||
}
|
||||
17
src/expression/expression-interfaces.ts
Normal file
17
src/expression/expression-interfaces.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { NumericOperand } from "./expression";
|
||||
|
||||
export interface ExpressionInput
|
||||
{
|
||||
expressionString: string;
|
||||
}
|
||||
|
||||
export interface ExpressionInputItem
|
||||
{
|
||||
isExpression: boolean;
|
||||
getUnderlyingOperand: () => NumericOperand;
|
||||
evaluate(operand? : NumericOperand): NumericOperand;
|
||||
}
|
||||
|
||||
export type NumberBase = 'dec' | 'hex' | 'bin';
|
||||
|
||||
|
||||
35
src/expression/expression.test.ts
Normal file
35
src/expression/expression.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { parser, ListOfNumbersExpression, BitwiseOperationExpression, NumericOperand, ExpressionOperand } from "./expression";
|
||||
|
||||
describe("expression parser", () => {
|
||||
|
||||
it("pares list of number expression", () => {
|
||||
var result = parser.parse("1 2 3");
|
||||
expect(result).toBeInstanceOf(ListOfNumbersExpression);
|
||||
});
|
||||
|
||||
it("pares different operations expressions", () => {
|
||||
expect(parser.parse("~1")).toBeInstanceOf(BitwiseOperationExpression);
|
||||
expect(parser.parse("1^2")).toBeInstanceOf(BitwiseOperationExpression);
|
||||
expect(parser.parse("1|2")).toBeInstanceOf(BitwiseOperationExpression);
|
||||
});
|
||||
|
||||
it("pares multiple operand expression", () => {
|
||||
const result = parser.parse("1^2") as BitwiseOperationExpression;
|
||||
expect(result.expressionItems.length).toBe(2);
|
||||
|
||||
const first = result.expressionItems[0];
|
||||
const second = result.expressionItems[1];
|
||||
|
||||
expect(first).toBeInstanceOf(NumericOperand);
|
||||
expect(first.value).toBe(1);
|
||||
|
||||
expect(second).toBeInstanceOf(ExpressionOperand);
|
||||
expect(second.sign).toBe("^");
|
||||
expect(second.operand.value).toBe(2);
|
||||
});
|
||||
|
||||
it("bug", () => {
|
||||
var result = parser.parse("1|~2") as BitwiseOperationExpression;
|
||||
expect(result.expressionItems.length).toBe(2);
|
||||
});
|
||||
})
|
||||
143
src/expression/expression.ts
Normal file
143
src/expression/expression.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import NumericOperand from './NumericOperand';
|
||||
import ExpressionOperand from './ExpressionOperand'
|
||||
import ListOfNumbersExpression from './ListOfNumbersExpression';
|
||||
import BitwiseOperationExpression from './BitwiseOperationExpression';
|
||||
import { ExpressionInput, ExpressionInputItem, NumberBase } from './expression-interfaces';
|
||||
|
||||
export { default as NumericOperand } from './NumericOperand';
|
||||
export { default as ExpressionOperand } from './ExpressionOperand';
|
||||
export { default as ListOfNumbersExpression } from './ListOfNumbersExpression';
|
||||
export { default as BitwiseOperationExpression } from './BitwiseOperationExpression';
|
||||
|
||||
interface IExpressionParserFactory {
|
||||
canCreate: (input: string) => boolean;
|
||||
create: (input: string) => ExpressionInput;
|
||||
};
|
||||
|
||||
class ExpressionParser {
|
||||
factories: IExpressionParserFactory[];
|
||||
constructor() {
|
||||
this.factories = [];
|
||||
};
|
||||
|
||||
canParse (input: string) : boolean {
|
||||
var trimmed = input.replace(/^\s+|\s+$/, '');
|
||||
var i = this.factories.length-1;
|
||||
for(;i>=0;i--) {
|
||||
if(this.factories[i].canCreate(trimmed) === true){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
parse (input: string) : ExpressionInput | null {
|
||||
var trimmed = input.replace(/^\s+|\s+$/, '');
|
||||
var i = 0, l = this.factories.length, factory;
|
||||
|
||||
for(;i<l;i++) {
|
||||
factory = this.factories[i];
|
||||
|
||||
if(factory.canCreate(trimmed) == true){
|
||||
return factory.create(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
parseOperand (input : string) : NumericOperand {
|
||||
return NumericOperand.parse(input);
|
||||
};
|
||||
|
||||
createOperand (number : number, base : NumberBase) : NumericOperand {
|
||||
return NumericOperand.create(number, base);
|
||||
};
|
||||
|
||||
addFactory (factory: IExpressionParserFactory) {
|
||||
this.factories.push(factory);
|
||||
}
|
||||
}
|
||||
|
||||
class ListOfNumbersExpressionFactory implements IExpressionParserFactory
|
||||
{
|
||||
regex: RegExp;
|
||||
|
||||
constructor() {
|
||||
this.regex = /^(-?(?:\d+|0x[\d,a-f]+|0b[0-1])\s?)+$/;
|
||||
}
|
||||
|
||||
canCreate (input: string): boolean {
|
||||
return this.regex.test(input);
|
||||
};
|
||||
|
||||
create (input : string) : ExpressionInput {
|
||||
var matches = this.regex.exec(input) as RegExpExecArray;
|
||||
var numbers = [] as NumericOperand[];
|
||||
var input = matches.input;
|
||||
|
||||
input.split(' ').forEach((n: string) => {
|
||||
if(n.trim().length > 0) {
|
||||
numbers.push(NumericOperand.parse(n.trim()));
|
||||
}
|
||||
});
|
||||
|
||||
return new ListOfNumbersExpression(input, numbers);
|
||||
}
|
||||
}
|
||||
|
||||
class BitwiseOperationExpressionFactory implements IExpressionParserFactory {
|
||||
fullRegex: RegExp;
|
||||
regex: RegExp;
|
||||
|
||||
constructor() {
|
||||
this.fullRegex = /^((<<|>>|>>>|\||\&|\^)?(~?-?([b,x,a-f,0-9]+)))+$/;
|
||||
this.regex = /(<<|>>|>>>|\||\&|\^)?(~?-?(?:[b,x,a-f,0-9]+))/g;
|
||||
}
|
||||
|
||||
canCreate (input: string) : boolean {
|
||||
this.fullRegex.lastIndex = 0;
|
||||
return this.fullRegex.test(this.normalizeString(input));
|
||||
};
|
||||
|
||||
create (input: string) : ExpressionInput {
|
||||
var m, operands : ExpressionInputItem[] = [],
|
||||
normalizedString = this.normalizeString(input);
|
||||
|
||||
while ((m = this.regex.exec(normalizedString)) != null) {
|
||||
operands.push(this.parseMatch(m));
|
||||
}
|
||||
|
||||
return new BitwiseOperationExpression(normalizedString, operands)
|
||||
};
|
||||
|
||||
parseMatch (m:any): ExpressionInputItem {
|
||||
var input = m[0],
|
||||
sign = m[1],
|
||||
num = m[2];
|
||||
|
||||
var parsed = null;
|
||||
if(num.indexOf('~') == '0') {
|
||||
parsed = new ExpressionOperand(num, NumericOperand.parse(num.substring(1)), '~');
|
||||
}
|
||||
else {
|
||||
parsed = NumericOperand.parse(num);
|
||||
}
|
||||
|
||||
if(sign == null) {
|
||||
return parsed as ExpressionOperand;
|
||||
} else {
|
||||
return new ExpressionOperand(input, parsed as NumericOperand, sign);
|
||||
}
|
||||
};
|
||||
|
||||
normalizeString (input : string): string {
|
||||
return input.replace(/\s+/g,'');
|
||||
};
|
||||
}
|
||||
|
||||
var parser = new ExpressionParser();
|
||||
parser.addFactory(new ListOfNumbersExpressionFactory());
|
||||
parser.addFactory(new BitwiseOperationExpressionFactory());
|
||||
|
||||
export {parser};
|
||||
38
src/expression/numberParser.tests.ts
Normal file
38
src/expression/numberParser.tests.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import {numberParser, ParsedNumber} from './numberParser';
|
||||
|
||||
describe("parser", () => {
|
||||
it('parses decimal number', () => {
|
||||
const result = numberParser.parse('10');
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
var number = result as ParsedNumber;
|
||||
expect(number.value).toBe(10);
|
||||
expect(number.base).toBe('dec');
|
||||
expect(number.input).toBe('10');
|
||||
});
|
||||
|
||||
it('parses hex number', () => {
|
||||
const result = numberParser.parse('0xab');
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
var number = result as ParsedNumber;
|
||||
expect(number.value).toBe(171);
|
||||
expect(number.base).toBe('hex');
|
||||
expect(number.input).toBe('0xab');
|
||||
});
|
||||
|
||||
it('parses bin number', () => {
|
||||
var result = numberParser.parse('0b0110');
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
var number = result as ParsedNumber;
|
||||
expect(number.value).toBe(6);
|
||||
expect(number.base).toBe('bin');
|
||||
expect(number.input).toBe('0b0110');
|
||||
});
|
||||
|
||||
it('returns null on bad inputs', () => {
|
||||
expect(numberParser.parse('abc')).toBeNull();
|
||||
expect(numberParser.parse('')).toBeNull();
|
||||
});
|
||||
});
|
||||
68
src/expression/numberParser.ts
Normal file
68
src/expression/numberParser.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { NumberBase } from "./expression-interfaces";
|
||||
|
||||
const decimalRegex = /^-?\d+$/;
|
||||
const hexRegex = /^-?0x[0-9,a-f]+$/i;
|
||||
const binRegex = /^-?0b[0-1]+$/i;
|
||||
const operatorRegex = /^<<|>>|<<<|\&|\|\^|~$/;
|
||||
|
||||
interface ParserConfig {
|
||||
regex: RegExp,
|
||||
radix: number,
|
||||
base: NumberBase,
|
||||
prefix: string|RegExp
|
||||
}
|
||||
|
||||
export interface ParsedNumber {
|
||||
value: number;
|
||||
base: NumberBase;
|
||||
input: string;
|
||||
}
|
||||
|
||||
var knownParsers : ParserConfig[] = [
|
||||
{ regex: decimalRegex, radix: 10, base: 'dec', prefix: '^$' },
|
||||
{ regex: hexRegex, radix: 16, base: 'hex', prefix:/0x/i },
|
||||
{ regex: binRegex, radix: 2, base: 'bin', prefix:/0b/i }];
|
||||
|
||||
|
||||
class NumberParser {
|
||||
|
||||
parsers: ParserConfig[];
|
||||
|
||||
constructor(parsers: ParserConfig[])
|
||||
{
|
||||
this.parsers = parsers;
|
||||
}
|
||||
|
||||
parse (input : string) : ParsedNumber | null {
|
||||
return this.parsers.map(p => this.applyParser(p, input)).reduce((c, n) => c || n);
|
||||
};
|
||||
|
||||
parseOperator (input: string) : string | null {
|
||||
var m = input.match(input);
|
||||
|
||||
if(m == null || m.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return m[0];
|
||||
};
|
||||
|
||||
applyParser(parser : ParserConfig, rawInput: string) : ParsedNumber | null {
|
||||
|
||||
if(!parser.regex.test(rawInput)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = parseInt(rawInput.replace(parser.prefix, ''), parser.radix);
|
||||
|
||||
return {
|
||||
value: value,
|
||||
base: parser.base,
|
||||
input: rawInput
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const numberParser = new NumberParser(knownParsers);
|
||||
|
||||
export {numberParser};
|
||||
Reference in New Issue
Block a user