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:
Borys Levytskyi
2021-01-12 16:41:03 +02:00
committed by GitHub
parent 83f49d258a
commit 271c58a980
91 changed files with 15026 additions and 1919 deletions

View 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;
}
}

View 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');
});

View 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();
}
}

View 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);
});

View 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(' ');
}
}

View 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');
});

View 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;
};
}

View 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';

View 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);
});
})

View 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};

View 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();
});
});

View 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};