• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mellonis / turing-machine-js / 10975808441

21 Sep 2024 09:30PM UTC coverage: 93.296% (-0.1%) from 93.407%
10975808441

push

github

web-flow
Merge pull request #77 from mellonis/npm+ts

#15 Write code in TS

166 of 186 branches covered (89.25%)

Branch coverage included in aggregate %.

212 of 223 new or added lines in 12 files covered. (95.07%)

335 of 351 relevant lines covered (95.44%)

330.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.86
/packages/machine/src/classes/TapeBlock.ts
1
import Alphabet from './Alphabet';
2
import Command from './Command';
3
import Tape from './Tape';
4
import {ifOtherSymbol} from './State';
5
import {movements, symbolCommands} from './TapeCommand';
6
import Lock from './Lock';
7

8
const symbolToPatternListMapSymbol = Symbol('symbol for symbolToPatternListMap setter');
11✔
9
export const lockSymbol = Symbol('capture symbol');
11✔
10

11
type TapeBlockConstructorParam = { alphabets: Alphabet[] } | { tapes: Tape[] };
12
type PatternList = (string | symbol)[][];
13
type SymbolToPatternListMap = Map<symbol, PatternList>;
14

15
export default class TapeBlock {
16
  #symbolToPatternListMap: SymbolToPatternListMap = new Map();
79✔
17
  readonly #lock = new Lock();
79✔
18
  readonly #tapes: Tape[];
19

20
  private constructor(argument: TapeBlockConstructorParam) {
21
    this.#tapes = [];
79✔
22

23
    if ('alphabets' in argument) {
79✔
24
      const {alphabets} = argument;
48✔
25

26
      if (alphabets.length === 0) {
48!
NEW
27
        throw new Error('empty alphabet list');
×
28
      }
29

30
      this.#tapes = alphabets.map((alphabet) => new Tape({
84✔
31
        alphabet,
32
      }));
33
    } else if ('tapes' in argument) {
31!
34
      this.#tapes = argument.tapes;
31✔
35
    }
36

37
    if (this.#tapes.length === 0) {
79!
NEW
38
      throw new Error('empty tape list');
×
39
    }
40
  }
41

42
  get alphabets() {
43
    return [...this.#tapes.map((tape) => tape.alphabet)];
140✔
44
  }
45

46
  get currentSymbols() {
47
    return this.#tapes.map((tape) => tape.symbol);
3,055✔
48
  }
49

50
  get [lockSymbol]() {
51
    return this.#lock;
337✔
52
  }
53

54
  get symbol() {
55
    return this.#symbol.bind(this);
36✔
56
  }
57

58
  get tapes() {
59
    return [...this.#tapes];
974✔
60
  }
61

62
  set [symbolToPatternListMapSymbol](symbolToPatternListMap: SymbolToPatternListMap) {
63
    this.#symbolToPatternListMap = new Map(symbolToPatternListMap);
30✔
64
  }
65

66
  static fromAlphabets = (alphabets: Alphabet[]) => {
11✔
67
    return new TapeBlock({alphabets})
48✔
68
  }
69

70
  static fromTapes = (tapes: Tape[]) => {
11✔
71
    return new TapeBlock({tapes});
31✔
72
  }
73

74
  static #generateSymbolHint = (patternList: PatternList) => JSON.stringify(
60✔
75
    patternList
76
      .map((pattern) => pattern
94✔
77
        .map((symbol) => (symbol === ifOtherSymbol ? null : symbol))),
126✔
78
  );
79

80
  applyCommand(command: Command, executionSymbol: symbol | null = null): void {
30✔
81
    this.#lock.check(executionSymbol);
1,060✔
82

83
    if (this.#tapes.length !== command.tapesCommands.length) {
1,060✔
84
      throw new Error('invalid command');
2✔
85
    }
86

87
    this.#tapes.forEach((tape, ix) => {
1,058✔
88
      const {movement, symbol} = command.tapesCommands[ix];
1,106✔
89

90
      if (typeof symbol === 'string') {
1,106✔
91
        tape.symbol = symbol;
182✔
92
      }
93

94
      if (typeof symbol === 'symbol') {
1,104✔
95
        switch (symbol) {
924✔
96
          case symbolCommands.keep:
97
            break;
842✔
98
          case symbolCommands.erase:
99
            tape.symbol = tape.alphabet.blankSymbol;
82✔
100
            break;
82✔
101
          // no default
102
        }
103
      }
104

105
      switch (movement) {
1,104✔
106
        case movements.left:
107
          tape.left();
275✔
108
          break;
275✔
109
        case movements.stay:
110
          break;
342✔
111
        case movements.right:
112
          tape.right();
487✔
113
          break;
487✔
114
        // no default
115
      }
116
    });
117
  }
118

119
  clone(cloneTapes = false): TapeBlock {
26✔
120
    let tapeBlock;
121

122
    if (cloneTapes) {
30✔
123
      tapeBlock = TapeBlock.fromTapes(this.tapes.map((tape) => new Tape(tape)));
12✔
124
    } else {
125
      tapeBlock = TapeBlock.fromAlphabets(this.alphabets);
26✔
126
    }
127

128
    tapeBlock[symbolToPatternListMapSymbol] = this.#symbolToPatternListMap;
30✔
129

130
    return tapeBlock;
30✔
131
  }
132

133
  isMatched({
134
              currentSymbols = this.currentSymbols,
1,825✔
135
              symbol
136
            }: { currentSymbols?: string[], symbol: symbol }): boolean {
137
    if (symbol === ifOtherSymbol) {
1,845✔
138
      return true;
283✔
139
    }
140

141
    if (!this.#symbolToPatternListMap.has(symbol)) {
1,562✔
142
      throw new Error('invalid symbol');
2✔
143
    }
144

145
    const patternList = this.#symbolToPatternListMap.get(symbol);
1,560✔
146

147
    return patternList?.some((pattern) => (
1,560!
148
      pattern
1,872✔
149
        .every((everySymbol, ix) => (
150
          everySymbol === ifOtherSymbol
1,938✔
151
          || everySymbol === currentSymbols[ix]
152
        ))
153
    )) ?? false;
154
  }
155

156
  replaceTape(tape: Tape, tapeIx = 0) {
90✔
157
    if (this.#tapes[tapeIx] == null) {
112✔
158
      throw new Error('invalid tapeIx');
4✔
159
    }
160

161
    if (tape.alphabet.symbols.join('') === this.#tapes[tapeIx].alphabet.symbols.join('')) {
108✔
162
      this.#tapes[tapeIx] = tape;
102✔
163
    } else {
164
      throw new Error('invalid tape');
6✔
165
    }
166
  }
167

168
  #buildPatternList = (symbolList: (symbol | string)[]) => symbolList.reduce((result, symbol, ix) => {
172✔
169
    const row = Math.floor(ix / this.#tapes.length);
350✔
170

171
    if (!Array.isArray(result[row])) {
350✔
172
      result[row] = [];
230✔
173
    }
174

175
    result[row].push(symbol);
350✔
176

177
    return result;
350✔
178
  }, [] as PatternList)
179
    .filter((pattern, ix, patternList) => {
180
      const samePatternIx = patternList.findIndex((otherPattern) => (
230✔
181
        pattern
314✔
182
          .every((symbol, symbolIx) => symbol === otherPattern[symbolIx])
434✔
183
      ));
184

185
      return samePatternIx === ix;
230✔
186
    });
187

188
  #getSymbolForPatternList = (patternList: PatternList) => {
79✔
189
    if (patternList.some((pattern) => pattern.every((symbol) => symbol === ifOtherSymbol))) {
226✔
190
      return ifOtherSymbol;
2✔
191
    }
192

193
    const [storedPatternListSymbol] = [...this.#symbolToPatternListMap.entries()]
170✔
194
      .find(([, storedPatternList]) => {
195
        if (storedPatternList.length !== patternList.length) {
359✔
196
          return false;
84✔
197
        }
198

199
        return patternList
275✔
200
          .every((pattern, patternIx) => pattern
291✔
201
            .every((symbol, symbolIx) => symbol === storedPatternList[patternIx][symbolIx]));
373✔
202
      }) || [null, null];
203

204
    let symbol;
205

206
    if (storedPatternListSymbol) {
170✔
207
      symbol = storedPatternListSymbol;
110✔
208
    } else {
209
      symbol = Symbol(TapeBlock.#generateSymbolHint(patternList));
60✔
210

211
      this.#symbolToPatternListMap.set(symbol, patternList);
60✔
212
    }
213

214
    return symbol;
170✔
215
  };
216

217
  #symbol = (symbols: (symbol | string)[] | symbol | string) => {
79✔
218
    let localSymbols: (symbol | string)[] = [];
198✔
219

220
    if (symbols === ifOtherSymbol) {
198✔
221
      return ifOtherSymbol;
4✔
222
    }
223

224
    if (typeof symbols === 'string') {
194✔
225
      localSymbols = symbols.split('');
78✔
226
    } else if (Array.isArray(symbols)) {
116!
227
      localSymbols = [...symbols];
116✔
228
    }
229

230
    if (localSymbols.length === 0 || localSymbols.length % this.#tapes.length > 0) {
194✔
231
      throw new Error('invalid symbol parameter');
12✔
232
    }
233

234
    const invalidSymbolIndex = localSymbols.findIndex((symbol, ix) => (
182✔
235
      symbol !== ifOtherSymbol
378✔
236
      && !this.#tapes[ix % this.#tapes.length].alphabet.has(symbol as string)
237
    ));
238

239
    if (invalidSymbolIndex >= 0) {
182✔
240
      throw new Error('invalid symbol parameter');
4✔
241
    }
242

243
    if (localSymbols.every((symbol) => symbol === ifOtherSymbol)) {
202✔
244
      return ifOtherSymbol;
6✔
245
    }
246

247
    return this.#getSymbolForPatternList(this.#buildPatternList(localSymbols));
172✔
248
  };
249
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc