@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
259 lines (201 loc) • 5.58 kB
JavaScript
import { assert } from "../assert.js";
import { string_repeat } from "../primitives/strings/string_repeat.js";
class Line {
/**
*
* @param {string} text
* @param {number} indent
*/
constructor(text, indent) {
assert.isString(text, 'text');
assert.isNonNegativeInteger(indent, 'index');
/**
*
* @type {string}
*/
this.text = text;
/**
*
* @type {number}
*/
this.indentation = indent;
}
toString() {
return `Line{ indentation=${this.indentation}, text="${this.text}" }`
}
}
const DEFAULT_INDENT_SPACES = 4;
/**
* Useful for generating formatted snippets of code.
* @example
* const b = new LineBuilder();
* b.add("function hello(){");
* b.indent();
* b.add("return \"hello world\";");
* b.dedent();
* b.add("}");
* b.build();
* // function hello(){
* // return "hello world";
* // }
*/
class LineBuilder {
/**
*
* @type {Line[]}
*/
#lines = [];
/**
* Current indent level
* @type {number}
*/
#indentation = 0;
/**
* Current indentation level
* Mainly intended for testing
* @return {number}
*/
get indentation() {
return this.#indentation;
}
/**
* TODO replace with indent string, that is tab, space or any combination or something else entirely
* @private
* @type {number}
*/
indentSpaces = DEFAULT_INDENT_SPACES;
/**
* Number of lines
* @return {number}
*/
get count() {
return this.#lines.length;
}
/**
* Substring test of per-line basis. A match can only span a single line, so multi-line matches will not be found.
* @return {boolean}
* @param {string} term
*/
containsSubstring(term) {
assert.isString(term, 'term');
const lines = this.#lines;
const n = lines.length;
for (let i = 0; i < n; i++) {
const line = lines[i];
const termIndex = line.text.indexOf(term);
if (termIndex !== -1) {
return true;
}
}
return false;
}
/**
*
* @returns {LineBuilder}
*/
indent() {
this.#indentation++;
return this;
}
/**
* NOTE: clamps indentation to 0 (can't go negative)
* @returns {LineBuilder}
*/
dedent() {
// clamp to 0
this.#indentation = Math.max(0, this.#indentation - 1);
return this;
}
/**
* Adds an entire new line with a given text.
* Inherits the current indentation level.
* @param {string} line_text
* @returns {LineBuilder}
*/
add(line_text) {
const line = new Line(line_text, this.#indentation);
this.#lines.push(line);
return this;
}
/**
* Appends text to the last line.
*
* @param {string} text
* @returns {void}
* @example
* const b = new LineBuilder();
* b.add("hello");
* b.extend(" world");
* b.build(); // "hello world"
*
*/
extend(text) {
assert.isString(text, 'text');
const lines = this.#lines;
const last_index = lines.length - 1;
if (last_index < 0) {
throw new Error("No lines to append to");
}
lines[last_index].text += text;
}
/**
*
* @param {LineBuilder} lines
*/
addLines(lines) {
assert.defined(lines, 'lines');
const other_lines = lines.#lines;
const other_line_count = other_lines.length;
for (let i = 0; i < other_line_count; i++) {
const otherLine = other_lines[i];
const line = new Line(otherLine.text, otherLine.indentation + this.#indentation);
this.#lines.push(line);
}
}
clear() {
this.#lines = [];
this.#indentation = 0;
}
/**
* Renders out an indented string of lines
* @returns {string}
*/
build() {
const result = [];
const lines = this.#lines;
const line_count = lines.length;
const indent_string = string_repeat(' ', this.indentSpaces);
for (let i = 0; i < line_count; i++) {
const line = lines[i];
let indentString = '';
const indent_count = line.indentation;
for (let j = 0; j < indent_count; j++) {
indentString += indent_string;
}
result.push(indentString + line.text);
}
return result.join('\n');
}
/**
* @param {string} text
* @param {string} [line_separator] defaults to new-line character
* @returns {LineBuilder}
*/
static fromText(text, line_separator = '\n') {
assert.isString(text, 'text');
assert.isString(line_separator, 'line_separator');
const r = new LineBuilder();
const lines = text.split(line_separator);
const n = lines.length;
// TODO detect indent
for (let i = 0; i < n; i++) {
const line_text = lines[i];
r.add(line_text);
}
return r;
}
toString() {
return this.build();
}
}
export default LineBuilder;