mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Data segments are now supported in quest script assembly.
This commit is contained in:
parent
3edb861693
commit
1da64b8632
@ -9,7 +9,6 @@
|
||||
"@types/react-dom": "16.8.4",
|
||||
"@types/react-virtualized": "^9.21.2",
|
||||
"@types/react-virtualized-select": "^3.0.7",
|
||||
"@types/text-encoding": "^0.0.35",
|
||||
"antd": "^3.20.1",
|
||||
"camera-controls": "^1.12.2",
|
||||
"golden-layout": "^1.5.9",
|
||||
|
@ -6,7 +6,6 @@ import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
|
||||
import { WritableCursor } from "../../cursor/WritableCursor";
|
||||
import { ResizableBuffer } from "../../ResizableBuffer";
|
||||
import { Opcode, OPCODES, Type } from "./opcodes";
|
||||
import { number } from "prop-types";
|
||||
|
||||
export * from "./opcodes";
|
||||
|
||||
|
396
src/scripting/AssemblyLexer.ts
Normal file
396
src/scripting/AssemblyLexer.ts
Normal file
@ -0,0 +1,396 @@
|
||||
export enum TokenType {
|
||||
Int,
|
||||
Float,
|
||||
InvalidNumber,
|
||||
Register,
|
||||
Label,
|
||||
CodeSection,
|
||||
DataSection,
|
||||
InvalidSection,
|
||||
String,
|
||||
UnterminatedString,
|
||||
Ident,
|
||||
InvalidIdent,
|
||||
ArgSeperator,
|
||||
}
|
||||
|
||||
export type Token =
|
||||
| IntToken
|
||||
| FloatToken
|
||||
| InvalidNumberToken
|
||||
| RegisterToken
|
||||
| LabelToken
|
||||
| CodeSectionToken
|
||||
| DataSectionToken
|
||||
| InvalidSectionToken
|
||||
| StringToken
|
||||
| UnterminatedStringToken
|
||||
| IdentToken
|
||||
| InvalidIdentToken
|
||||
| ArgSeperatorToken;
|
||||
|
||||
export type IntToken = {
|
||||
type: TokenType.Int;
|
||||
col: number;
|
||||
len: number;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type FloatToken = {
|
||||
type: TokenType.Float;
|
||||
col: number;
|
||||
len: number;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type InvalidNumberToken = {
|
||||
type: TokenType.InvalidNumber;
|
||||
col: number;
|
||||
len: number;
|
||||
};
|
||||
|
||||
export type RegisterToken = {
|
||||
type: TokenType.Register;
|
||||
col: number;
|
||||
len: number;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type LabelToken = {
|
||||
type: TokenType.Label;
|
||||
col: number;
|
||||
len: number;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type CodeSectionToken = {
|
||||
type: TokenType.CodeSection;
|
||||
col: number;
|
||||
len: number;
|
||||
};
|
||||
|
||||
export type DataSectionToken = {
|
||||
type: TokenType.DataSection;
|
||||
col: number;
|
||||
len: number;
|
||||
};
|
||||
|
||||
export type InvalidSectionToken = {
|
||||
type: TokenType.InvalidSection;
|
||||
col: number;
|
||||
len: number;
|
||||
};
|
||||
|
||||
export type StringToken = {
|
||||
type: TokenType.String;
|
||||
col: number;
|
||||
len: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type UnterminatedStringToken = {
|
||||
type: TokenType.UnterminatedString;
|
||||
col: number;
|
||||
len: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type IdentToken = {
|
||||
type: TokenType.Ident;
|
||||
col: number;
|
||||
len: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type InvalidIdentToken = {
|
||||
type: TokenType.InvalidIdent;
|
||||
col: number;
|
||||
len: number;
|
||||
};
|
||||
|
||||
export type ArgSeperatorToken = {
|
||||
type: TokenType.ArgSeperator;
|
||||
col: number;
|
||||
len: number;
|
||||
};
|
||||
|
||||
export class AssemblyLexer {
|
||||
private line!: string;
|
||||
private index = 0;
|
||||
|
||||
private get col(): number {
|
||||
return this.index + 1;
|
||||
}
|
||||
|
||||
private _mark = 0;
|
||||
|
||||
tokenize_line(line: string): Token[] {
|
||||
this.line = line;
|
||||
this.index = 0;
|
||||
this._mark = 0;
|
||||
|
||||
const tokens: Token[] = [];
|
||||
|
||||
while (this.has_next()) {
|
||||
const char = this.peek();
|
||||
let token: Token;
|
||||
|
||||
if (/\s/.test(char)) {
|
||||
this.skip();
|
||||
continue;
|
||||
} else if (/[-\d]/.test(char)) {
|
||||
token = this.tokenize_number_or_label();
|
||||
} else if ("," === char) {
|
||||
token = { type: TokenType.ArgSeperator, col: this.col, len: 1 };
|
||||
this.skip();
|
||||
} else if ("." === char) {
|
||||
token = this.tokenize_section();
|
||||
} else if ('"' === char) {
|
||||
token = this.tokenize_string();
|
||||
} else if ("r" === char) {
|
||||
token = this.tokenize_register_or_ident();
|
||||
} else {
|
||||
token = this.tokenize_ident();
|
||||
}
|
||||
|
||||
tokens.push(token);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private has_next(): boolean {
|
||||
return this.index < this.line.length;
|
||||
}
|
||||
|
||||
private next(): string {
|
||||
return this.line.charAt(this.index++);
|
||||
}
|
||||
|
||||
private peek(): string {
|
||||
return this.line.charAt(this.index);
|
||||
}
|
||||
|
||||
private skip(): void {
|
||||
this.index++;
|
||||
}
|
||||
|
||||
private back(): void {
|
||||
this.index--;
|
||||
}
|
||||
|
||||
private mark(): void {
|
||||
this._mark = this.index;
|
||||
}
|
||||
|
||||
private marked_len(): number {
|
||||
return this.index - this._mark;
|
||||
}
|
||||
|
||||
private slice(): string {
|
||||
return this.line.slice(this._mark, this.index);
|
||||
}
|
||||
|
||||
private tokenize_number_or_label(): IntToken | FloatToken | InvalidNumberToken | LabelToken {
|
||||
this.mark();
|
||||
const col = this.col;
|
||||
this.skip();
|
||||
let is_label = false;
|
||||
let is_float = false;
|
||||
let is_hex = false;
|
||||
|
||||
while (this.has_next()) {
|
||||
const char = this.peek();
|
||||
|
||||
if (/\d/.test(char)) {
|
||||
this.skip();
|
||||
} else if ("." === char) {
|
||||
if (is_float || is_hex) {
|
||||
break;
|
||||
} else {
|
||||
is_float = true;
|
||||
this.skip();
|
||||
}
|
||||
} else if ("x" === char) {
|
||||
if (is_float || is_hex) {
|
||||
break;
|
||||
} else {
|
||||
is_hex = true;
|
||||
this.skip();
|
||||
}
|
||||
} else if (/[a-fA-F]/.test(char)) {
|
||||
if (is_hex) {
|
||||
this.skip();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (char === ":" && !is_float && !is_hex) {
|
||||
is_label = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let value: number;
|
||||
|
||||
if (is_float) {
|
||||
value = parseFloat(this.slice());
|
||||
} else if (is_hex) {
|
||||
value = parseInt(this.slice(), 16);
|
||||
} else {
|
||||
value = parseInt(this.slice(), 10);
|
||||
}
|
||||
|
||||
if (is_label) {
|
||||
this.skip();
|
||||
}
|
||||
|
||||
return {
|
||||
type: isNaN(value)
|
||||
? TokenType.InvalidNumber
|
||||
: is_label
|
||||
? TokenType.Label
|
||||
: is_float
|
||||
? TokenType.Float
|
||||
: TokenType.Int,
|
||||
col,
|
||||
len: this.marked_len(),
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
private tokenize_register_or_ident(): RegisterToken | IdentToken | InvalidIdentToken {
|
||||
const col = this.col;
|
||||
this.skip();
|
||||
this.mark();
|
||||
let is_register = false;
|
||||
|
||||
while (this.has_next()) {
|
||||
const char = this.peek();
|
||||
|
||||
if (/\d/.test(char)) {
|
||||
is_register = true;
|
||||
this.skip();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_register) {
|
||||
const value = parseInt(this.slice(), 10);
|
||||
|
||||
return {
|
||||
type: TokenType.Register,
|
||||
col,
|
||||
len: this.marked_len() + 1,
|
||||
value,
|
||||
};
|
||||
} else {
|
||||
this.back();
|
||||
return this.tokenize_ident();
|
||||
}
|
||||
}
|
||||
|
||||
private tokenize_section(): CodeSectionToken | DataSectionToken | InvalidSectionToken {
|
||||
const col = this.col;
|
||||
this.mark();
|
||||
|
||||
while (this.has_next()) {
|
||||
if (/\s/.test(this.peek())) {
|
||||
break;
|
||||
} else {
|
||||
this.skip();
|
||||
}
|
||||
}
|
||||
|
||||
switch (this.slice()) {
|
||||
case ".code":
|
||||
return { type: TokenType.CodeSection, col, len: 5 };
|
||||
case ".data":
|
||||
return { type: TokenType.DataSection, col, len: 5 };
|
||||
default:
|
||||
return { type: TokenType.InvalidSection, col, len: this.marked_len() };
|
||||
}
|
||||
}
|
||||
|
||||
private tokenize_string(): StringToken | UnterminatedStringToken {
|
||||
const col = this.col;
|
||||
this.skip();
|
||||
this.mark(); // Mark after opening quote.
|
||||
let prev_was_bs = false;
|
||||
let terminated = false;
|
||||
|
||||
outer: while (this.has_next()) {
|
||||
switch (this.next()) {
|
||||
case "\\":
|
||||
prev_was_bs = true;
|
||||
break;
|
||||
case '"':
|
||||
if (!prev_was_bs) {
|
||||
terminated = true;
|
||||
break outer;
|
||||
}
|
||||
|
||||
prev_was_bs = false;
|
||||
break;
|
||||
default:
|
||||
prev_was_bs = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let value: string;
|
||||
let len: number;
|
||||
|
||||
// Don't include quote in value.
|
||||
if (terminated) {
|
||||
this.back();
|
||||
value = this.slice();
|
||||
len = this.marked_len() + 2;
|
||||
this.skip();
|
||||
} else {
|
||||
value = this.slice();
|
||||
len = this.marked_len() + 1;
|
||||
}
|
||||
|
||||
return {
|
||||
type: terminated ? TokenType.String : TokenType.UnterminatedString,
|
||||
col,
|
||||
len,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
private tokenize_ident(): IdentToken | InvalidIdentToken {
|
||||
const col = this.col;
|
||||
this.mark();
|
||||
|
||||
while (this.has_next()) {
|
||||
if (/[\s,]/.test(this.peek())) {
|
||||
break;
|
||||
} else {
|
||||
this.skip();
|
||||
}
|
||||
}
|
||||
|
||||
const value = this.slice();
|
||||
const type = /^[a-z][a-z0-9_=<>!]*$/.test(value) ? TokenType.Ident : TokenType.InvalidIdent;
|
||||
|
||||
if (type === TokenType.Ident) {
|
||||
return {
|
||||
type,
|
||||
col,
|
||||
len: this.marked_len(),
|
||||
value,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type,
|
||||
col,
|
||||
len: this.marked_len(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import { InstructionSegment, Opcode, SegmentType } from "../data_formats/parsing
|
||||
import { assemble } from "./assembly";
|
||||
|
||||
test("", () => {
|
||||
const { object_code, errors } = assemble(
|
||||
const { object_code, warnings, errors } = assemble(
|
||||
`
|
||||
0: set_episode 0
|
||||
bb_map_designate 1, 2, 3, 4
|
||||
@ -15,6 +15,7 @@ test("", () => {
|
||||
`.split("\n")
|
||||
);
|
||||
|
||||
expect(warnings).toEqual([]);
|
||||
expect(errors).toEqual([]);
|
||||
|
||||
expect(object_code.length).toBe(3);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Logger from "js-logger";
|
||||
import {
|
||||
Arg,
|
||||
Instruction,
|
||||
@ -8,81 +9,123 @@ import {
|
||||
Segment,
|
||||
SegmentType,
|
||||
Type,
|
||||
DataSegment,
|
||||
} from "../data_formats/parsing/quest/bin";
|
||||
import {
|
||||
AssemblyLexer,
|
||||
CodeSectionToken,
|
||||
DataSectionToken,
|
||||
IdentToken,
|
||||
IntToken,
|
||||
LabelToken,
|
||||
RegisterToken,
|
||||
Token,
|
||||
TokenType,
|
||||
} from "./AssemblyLexer";
|
||||
|
||||
export type AssemblyError = {
|
||||
const logger = Logger.get("scripting/assembly");
|
||||
|
||||
export type AssemblyWarning = {
|
||||
line_no: number;
|
||||
col: number;
|
||||
length: number;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type AssemblyError = AssemblyWarning;
|
||||
|
||||
export function assemble(
|
||||
assembly: string[],
|
||||
manual_stack: boolean = false
|
||||
): {
|
||||
object_code: Segment[];
|
||||
warnings: AssemblyWarning[];
|
||||
errors: AssemblyError[];
|
||||
} {
|
||||
return new Assembler(assembly, manual_stack).assemble();
|
||||
}
|
||||
|
||||
type ArgToken = {
|
||||
col: number;
|
||||
arg: string;
|
||||
};
|
||||
|
||||
class Assembler {
|
||||
private lexer = new AssemblyLexer();
|
||||
private line_no!: number;
|
||||
private tokens!: Token[];
|
||||
private object_code!: Segment[];
|
||||
// The current segment.
|
||||
private segment?: Segment;
|
||||
private warnings!: AssemblyWarning[];
|
||||
private errors!: AssemblyError[];
|
||||
// Encountered labels.
|
||||
private labels!: Set<number>;
|
||||
// True iff we're in a code section, false iff we're in a data section.
|
||||
private code_section = true;
|
||||
|
||||
constructor(private assembly: string[], private manual_stack: boolean) {}
|
||||
|
||||
assemble(): {
|
||||
object_code: Segment[];
|
||||
warnings: AssemblyWarning[];
|
||||
errors: AssemblyError[];
|
||||
} {
|
||||
this.line_no = 1;
|
||||
this.object_code = [];
|
||||
this.warnings = [];
|
||||
this.errors = [];
|
||||
this.labels = new Set();
|
||||
this.code_section = true;
|
||||
|
||||
for (const line of this.assembly) {
|
||||
const match = line.match(
|
||||
/^(?<lbl_ws>\s*)(?<lbl>[^\s]+?:)?(?<op_ws>\s*)(?<op>[a-z][a-z0-9_=<>!]*)?(?<args>.*)$/
|
||||
);
|
||||
this.tokens = this.lexer.tokenize_line(line);
|
||||
|
||||
if (
|
||||
!match ||
|
||||
!match.groups ||
|
||||
(match.groups.lbl == undefined && match.groups.op == undefined)
|
||||
) {
|
||||
const left_trimmed = line.trimLeft();
|
||||
const trimmed = left_trimmed.trimRight();
|
||||
if (this.tokens.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.length) {
|
||||
this.add_error({
|
||||
col: 1 + line.length - left_trimmed.length,
|
||||
length: trimmed.length,
|
||||
message: "Expected label or instruction.",
|
||||
});
|
||||
const token = this.tokens.shift()!;
|
||||
|
||||
if (this.code_section) {
|
||||
switch (token.type) {
|
||||
case TokenType.Label:
|
||||
this.parse_label(token);
|
||||
break;
|
||||
case TokenType.CodeSection:
|
||||
this.parse_code_section(token);
|
||||
break;
|
||||
case TokenType.DataSection:
|
||||
this.parse_data_section(token);
|
||||
break;
|
||||
case TokenType.Ident:
|
||||
this.parse_instruction(token);
|
||||
break;
|
||||
case TokenType.InvalidSection:
|
||||
this.add_error({
|
||||
col: token.col,
|
||||
length: token.len,
|
||||
message: "Invalid section type.",
|
||||
});
|
||||
break;
|
||||
case TokenType.InvalidIdent:
|
||||
this.add_error({
|
||||
col: token.col,
|
||||
length: token.len,
|
||||
message: "Invalid identifier.",
|
||||
});
|
||||
break;
|
||||
default:
|
||||
this.add_error({
|
||||
col: token.col,
|
||||
length: token.len,
|
||||
message: "Unexpected token.",
|
||||
});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const { lbl_ws, lbl, op_ws, op, args } = match.groups;
|
||||
|
||||
if (lbl != undefined) {
|
||||
this.parse_label(lbl, lbl_ws);
|
||||
}
|
||||
|
||||
if (op != undefined) {
|
||||
this.parse_instruction(
|
||||
1 + lbl_ws.length + (lbl ? lbl.length : 0) + op_ws.length,
|
||||
op,
|
||||
args
|
||||
);
|
||||
switch (token.type) {
|
||||
case TokenType.Label:
|
||||
this.parse_label(token);
|
||||
break;
|
||||
case TokenType.Int:
|
||||
this.parse_bytes(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,16 +134,48 @@ class Assembler {
|
||||
|
||||
return {
|
||||
object_code: this.object_code,
|
||||
warnings: this.warnings,
|
||||
errors: this.errors,
|
||||
};
|
||||
}
|
||||
|
||||
private add_instruction(opcode: Opcode, args: Arg[]): void {
|
||||
const { instructions } = this.object_code[
|
||||
this.object_code.length - 1
|
||||
] as InstructionSegment;
|
||||
if (!this.segment) {
|
||||
// Unreachable code, technically valid.
|
||||
const instruction_segment: InstructionSegment = {
|
||||
label: -1,
|
||||
type: SegmentType.Instructions,
|
||||
instructions: [],
|
||||
};
|
||||
|
||||
instructions.push(new Instruction(opcode, args));
|
||||
this.segment = instruction_segment;
|
||||
this.object_code.push(instruction_segment);
|
||||
}
|
||||
|
||||
(this.segment as InstructionSegment).instructions.push(new Instruction(opcode, args));
|
||||
}
|
||||
|
||||
private add_bytes(bytes: number[]): void {
|
||||
if (!this.segment) {
|
||||
// Unadressable data, technically valid.
|
||||
const data_segment: DataSegment = {
|
||||
label: -1,
|
||||
type: SegmentType.Data,
|
||||
data: new Uint8Array(bytes).buffer,
|
||||
};
|
||||
|
||||
this.segment = data_segment;
|
||||
this.object_code.push(data_segment);
|
||||
} else {
|
||||
const d_seg = this.segment as DataSegment;
|
||||
const buf = new ArrayBuffer(d_seg.data.byteLength + bytes.length);
|
||||
const arr = new Uint8Array(buf);
|
||||
|
||||
arr.set(new Uint8Array(d_seg.data));
|
||||
arr.set(new Uint8Array(bytes));
|
||||
|
||||
d_seg.data = buf;
|
||||
}
|
||||
}
|
||||
|
||||
private add_error({
|
||||
@ -120,116 +195,211 @@ class Assembler {
|
||||
});
|
||||
}
|
||||
|
||||
private parse_label(lbl: string, lbl_ws: string): void {
|
||||
const label = parseInt(lbl.slice(0, -1), 10);
|
||||
private add_warning({
|
||||
col,
|
||||
length,
|
||||
message,
|
||||
}: {
|
||||
col: number;
|
||||
length: number;
|
||||
message: string;
|
||||
}): void {
|
||||
this.warnings.push({
|
||||
line_no: this.line_no,
|
||||
col,
|
||||
length,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isFinite(label) || !/^\d+:$/.test(lbl)) {
|
||||
private parse_label({ col, len, value: label }: LabelToken): void {
|
||||
if (this.labels.has(label)) {
|
||||
this.add_error({
|
||||
col: 1 + lbl_ws.length,
|
||||
length: lbl.length,
|
||||
message: "Invalid label name.",
|
||||
col,
|
||||
length: len,
|
||||
message: "Duplicate label.",
|
||||
});
|
||||
} else {
|
||||
if (this.labels.has(label)) {
|
||||
this.add_error({
|
||||
col: 1 + lbl_ws.length,
|
||||
length: lbl.length - 1,
|
||||
message: "Duplicate label.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.object_code.push({
|
||||
this.labels.add(label);
|
||||
|
||||
const next_token = this.tokens.shift();
|
||||
|
||||
if (this.code_section) {
|
||||
this.segment = {
|
||||
type: SegmentType.Instructions,
|
||||
label,
|
||||
instructions: [],
|
||||
};
|
||||
this.object_code.push(this.segment);
|
||||
|
||||
if (next_token) {
|
||||
if (next_token.type === TokenType.Ident) {
|
||||
this.parse_instruction(next_token);
|
||||
} else {
|
||||
this.add_error({
|
||||
col: next_token.col,
|
||||
length: next_token.len,
|
||||
message: "Expected opcode mnemonic.",
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.segment = {
|
||||
type: SegmentType.Data,
|
||||
label,
|
||||
data: new ArrayBuffer(0),
|
||||
};
|
||||
this.object_code.push(this.segment);
|
||||
|
||||
if (next_token) {
|
||||
if (next_token.type === TokenType.Int) {
|
||||
this.parse_bytes(next_token);
|
||||
} else {
|
||||
this.add_error({
|
||||
col: next_token.col,
|
||||
length: next_token.len,
|
||||
message: "Expected bytes.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private parse_code_section({ col, len }: CodeSectionToken): void {
|
||||
if (this.code_section) {
|
||||
this.add_warning({
|
||||
col,
|
||||
length: len,
|
||||
message: "Unnecessary code section marker.",
|
||||
});
|
||||
}
|
||||
|
||||
this.code_section = true;
|
||||
|
||||
const next_token = this.tokens.shift();
|
||||
|
||||
if (next_token) {
|
||||
this.add_error({
|
||||
col: next_token.col,
|
||||
length: next_token.len,
|
||||
message: "Unexpected token.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private parse_instruction(col: number, op: string, args: string): void {
|
||||
const opcode = OPCODES_BY_MNEMONIC.get(op);
|
||||
private parse_data_section({ col, len }: DataSectionToken): void {
|
||||
if (!this.code_section) {
|
||||
this.add_warning({
|
||||
col,
|
||||
length: len,
|
||||
message: "Unnecessary data section marker.",
|
||||
});
|
||||
}
|
||||
|
||||
this.code_section = false;
|
||||
|
||||
const next_token = this.tokens.shift();
|
||||
|
||||
if (next_token) {
|
||||
this.add_error({
|
||||
col: next_token.col,
|
||||
length: next_token.len,
|
||||
message: "Unexpected token.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private parse_instruction({ col, len, value }: IdentToken): void {
|
||||
const opcode = OPCODES_BY_MNEMONIC.get(value);
|
||||
|
||||
if (!opcode) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: op.length,
|
||||
length: len,
|
||||
message: "Unknown instruction.",
|
||||
});
|
||||
} else {
|
||||
const args_col = col + (op ? op.length : 0);
|
||||
const varargs =
|
||||
opcode.params.findIndex(p => p.type === Type.U8Var || p.type === Type.ILabelVar) !==
|
||||
-1;
|
||||
|
||||
const arg_tokens: ArgToken[] = [];
|
||||
const args_tokenization_ok = this.tokenize_args(args, args_col, arg_tokens);
|
||||
const param_count =
|
||||
opcode.params.length + (this.manual_stack ? 0 : opcode.stack_params.length);
|
||||
|
||||
let arg_count = 0;
|
||||
|
||||
for (const token of this.tokens) {
|
||||
if (token.type !== TokenType.ArgSeperator) {
|
||||
arg_count++;
|
||||
}
|
||||
}
|
||||
|
||||
const last_token = this.tokens[this.tokens.length - 1];
|
||||
let error_length = last_token ? last_token.col + last_token.len - col : 0;
|
||||
const ins_args: Arg[] = [];
|
||||
|
||||
if (!args_tokenization_ok) {
|
||||
const left_trimmed = args.trimLeft();
|
||||
const trimmed = args.trimRight();
|
||||
|
||||
if (!varargs && arg_count !== param_count) {
|
||||
this.add_error({
|
||||
col: args_col + args.length - left_trimmed.length,
|
||||
length: trimmed.length,
|
||||
message: "Instruction arguments expected.",
|
||||
col,
|
||||
length: error_length,
|
||||
message: `Expected ${param_count} argument${
|
||||
param_count === 1 ? "" : "s"
|
||||
}, got ${arg_count}.`,
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (varargs && arg_count < param_count) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: error_length,
|
||||
message: `Expected at least ${param_count} argument${
|
||||
param_count === 1 ? "" : "s"
|
||||
}, got ${arg_count}.`,
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (varargs || arg_count === opcode.params.length) {
|
||||
// Inline arguments.
|
||||
if (!this.parse_args(opcode.params, ins_args)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const varargs =
|
||||
opcode.params.findIndex(
|
||||
p => p.type === Type.U8Var || p.type === Type.ILabelVar
|
||||
) !== -1;
|
||||
// Stack arguments.
|
||||
const stack_args: Arg[] = [];
|
||||
|
||||
const param_count =
|
||||
opcode.params.length + (this.manual_stack ? 0 : opcode.stack_params.length);
|
||||
if (!this.parse_args(opcode.stack_params, stack_args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (varargs ? arg_tokens.length < param_count : arg_tokens.length !== param_count) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: op.length + args.trimRight().length,
|
||||
message: `Expected${varargs ? " at least" : ""} ${param_count} argument${
|
||||
param_count === 1 ? "" : "s"
|
||||
}, got ${arg_tokens.length}.`,
|
||||
});
|
||||
} else if (varargs || arg_tokens.length === opcode.params.length) {
|
||||
this.parse_args(opcode.params, arg_tokens, ins_args);
|
||||
} else {
|
||||
const stack_args: Arg[] = [];
|
||||
this.parse_args(opcode.stack_params, arg_tokens, stack_args);
|
||||
for (let i = 0; i < opcode.stack_params.length; i++) {
|
||||
const param = opcode.stack_params[i];
|
||||
const arg = stack_args[i];
|
||||
|
||||
for (let i = 0; i < opcode.stack_params.length; i++) {
|
||||
const param = opcode.stack_params[i];
|
||||
const arg = stack_args[i];
|
||||
const col = arg_tokens[i].col;
|
||||
const length = arg_tokens[i].arg.length;
|
||||
if (arg == undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (param.type) {
|
||||
case Type.U8:
|
||||
case Type.Register:
|
||||
this.add_instruction(Opcode.arg_pushb, [arg]);
|
||||
break;
|
||||
case Type.U16:
|
||||
case Type.ILabel:
|
||||
case Type.DLabel:
|
||||
this.add_instruction(Opcode.arg_pushw, [arg]);
|
||||
break;
|
||||
case Type.U32:
|
||||
case Type.I32:
|
||||
case Type.F32:
|
||||
this.add_instruction(Opcode.arg_pushl, [arg]);
|
||||
break;
|
||||
case Type.String:
|
||||
this.add_instruction(Opcode.arg_pushs, [arg]);
|
||||
break;
|
||||
default:
|
||||
this.add_error({
|
||||
col,
|
||||
length,
|
||||
message: `Type ${Type[param.type]} not implemented.`,
|
||||
});
|
||||
}
|
||||
switch (param.type) {
|
||||
case Type.U8:
|
||||
case Type.Register:
|
||||
this.add_instruction(Opcode.arg_pushb, [arg]);
|
||||
break;
|
||||
case Type.U16:
|
||||
case Type.ILabel:
|
||||
case Type.DLabel:
|
||||
this.add_instruction(Opcode.arg_pushw, [arg]);
|
||||
break;
|
||||
case Type.U32:
|
||||
case Type.I32:
|
||||
case Type.F32:
|
||||
this.add_instruction(Opcode.arg_pushl, [arg]);
|
||||
break;
|
||||
case Type.String:
|
||||
this.add_instruction(Opcode.arg_pushs, [arg]);
|
||||
break;
|
||||
default:
|
||||
logger.error(`Type ${Type[param.type]} not implemented.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,204 +408,249 @@ class Assembler {
|
||||
}
|
||||
}
|
||||
|
||||
private tokenize_args(arg_str: string, col: number, args: ArgToken[]): boolean {
|
||||
if (arg_str.trim().length === 0) {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* @returns true if arguments can be translated to object code, possibly after truncation. False otherwise.
|
||||
*/
|
||||
private parse_args(params: Param[], args: Arg[]): boolean {
|
||||
let semi_valid = true;
|
||||
let should_be_arg = true;
|
||||
let param_i = 0;
|
||||
|
||||
let match: RegExpMatchArray | null;
|
||||
for (let i = 0; i < this.tokens.length; i++) {
|
||||
const token = this.tokens[i];
|
||||
const param = params[param_i];
|
||||
|
||||
if (args.length === 0) {
|
||||
match = arg_str.match(/^(?<arg_ws>\s+)(?<arg>"([^"\\]|\\.)*"|[^\s,]+)\s*/);
|
||||
} else {
|
||||
match = arg_str.match(/^(?<arg_ws>,\s*)(?<arg>"([^"\\]|\\.)*"|[^\s,]+)\s*/);
|
||||
}
|
||||
if (token.type === TokenType.ArgSeperator) {
|
||||
if (should_be_arg) {
|
||||
this.add_error({
|
||||
col: token.col,
|
||||
length: token.len,
|
||||
message: "Argument expected.",
|
||||
});
|
||||
} else {
|
||||
if (param.type !== Type.U8Var && param.type !== Type.ILabelVar) {
|
||||
param_i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match || !match.groups) {
|
||||
return false;
|
||||
} else {
|
||||
const { arg_ws, arg } = match.groups;
|
||||
args.push({
|
||||
col: col + arg_ws.length,
|
||||
arg,
|
||||
});
|
||||
should_be_arg = true;
|
||||
} else {
|
||||
if (!should_be_arg) {
|
||||
const prev_token = this.tokens[i - 1];
|
||||
const col = prev_token.col + prev_token.len;
|
||||
|
||||
return this.tokenize_args(arg_str.slice(match[0].length), col + match[0].length, args);
|
||||
}
|
||||
}
|
||||
|
||||
private parse_args(params: Param[], arg_tokens: ArgToken[], args: Arg[]): void {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
const param = params[i];
|
||||
const arg_token = arg_tokens[i];
|
||||
const arg_str = arg_token.arg;
|
||||
const col = arg_token.col;
|
||||
const length = arg_str.length;
|
||||
|
||||
switch (param.type) {
|
||||
case Type.U8:
|
||||
this.parse_uint(arg_str, 1, args, col);
|
||||
break;
|
||||
case Type.U16:
|
||||
case Type.ILabel:
|
||||
case Type.DLabel:
|
||||
this.parse_uint(arg_str, 2, args, col);
|
||||
break;
|
||||
case Type.U32:
|
||||
this.parse_uint(arg_str, 4, args, col);
|
||||
break;
|
||||
case Type.I32:
|
||||
this.parse_sint(arg_str, 4, args, col);
|
||||
break;
|
||||
case Type.F32:
|
||||
this.parse_float(arg_str, args, col);
|
||||
break;
|
||||
case Type.Register:
|
||||
this.parse_register(arg_str, args, col);
|
||||
break;
|
||||
case Type.String:
|
||||
this.parse_string(arg_str, args, col);
|
||||
break;
|
||||
case Type.U8Var:
|
||||
this.parse_uint_varargs(arg_tokens, i, 1, args);
|
||||
return;
|
||||
case Type.ILabelVar:
|
||||
this.parse_uint_varargs(arg_tokens, i, 2, args);
|
||||
return;
|
||||
default:
|
||||
this.add_error({
|
||||
col,
|
||||
length,
|
||||
message: `Type ${Type[param.type]} not implemented.`,
|
||||
length: token.col - col,
|
||||
message: "Comma expected.",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
should_be_arg = false;
|
||||
|
||||
let match: boolean;
|
||||
|
||||
switch (token.type) {
|
||||
case TokenType.Int:
|
||||
switch (param.type) {
|
||||
case Type.U8:
|
||||
case Type.U8Var:
|
||||
match = true;
|
||||
this.verify_uint(1, token, args);
|
||||
break;
|
||||
case Type.U16:
|
||||
case Type.ILabel:
|
||||
case Type.ILabelVar:
|
||||
case Type.DLabel:
|
||||
match = true;
|
||||
this.verify_uint(2, token, args);
|
||||
break;
|
||||
case Type.U32:
|
||||
match = true;
|
||||
this.verify_uint(4, token, args);
|
||||
break;
|
||||
case Type.I32:
|
||||
match = true;
|
||||
this.verify_sint(4, token, args);
|
||||
break;
|
||||
case Type.F32:
|
||||
match = true;
|
||||
break;
|
||||
default:
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TokenType.Float:
|
||||
match = param.type === Type.F32;
|
||||
break;
|
||||
case TokenType.Register:
|
||||
match = param.type === Type.Register;
|
||||
this.verify_register(token, args);
|
||||
break;
|
||||
case TokenType.String:
|
||||
match = param.type === Type.String;
|
||||
break;
|
||||
default:
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
semi_valid = false;
|
||||
|
||||
let type_str = Type[param.type];
|
||||
|
||||
switch (param.type) {
|
||||
case Type.U8:
|
||||
type_str = "unsigned 8-bit integer";
|
||||
break;
|
||||
case Type.U16:
|
||||
type_str = "unsigned 16-bit integer";
|
||||
break;
|
||||
case Type.U32:
|
||||
type_str = "unsigned 32-bit integer";
|
||||
break;
|
||||
case Type.I32:
|
||||
type_str = "signed 32-bit integer";
|
||||
break;
|
||||
case Type.F32:
|
||||
type_str = "float";
|
||||
break;
|
||||
case Type.Register:
|
||||
type_str = "register reference";
|
||||
break;
|
||||
case Type.ILabel:
|
||||
type_str = "instruction label";
|
||||
break;
|
||||
case Type.DLabel:
|
||||
type_str = "data label";
|
||||
break;
|
||||
case Type.U8Var:
|
||||
type_str = "unsigned 8-bit integer";
|
||||
break;
|
||||
case Type.ILabelVar:
|
||||
type_str = "instruction label";
|
||||
break;
|
||||
case Type.String:
|
||||
type_str = "string";
|
||||
break;
|
||||
}
|
||||
|
||||
this.add_error({
|
||||
col: token.col,
|
||||
length: token.len,
|
||||
message: `Expected ${type_str}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.tokens = [];
|
||||
return semi_valid;
|
||||
}
|
||||
|
||||
private parse_uint(arg_str: string, size: number, args: Arg[], col: number): void {
|
||||
private verify_uint(size: number, { col, len, value }: IntToken, args: Arg[]): void {
|
||||
const bit_size = 8 * size;
|
||||
const value = parseInt(arg_str, 10);
|
||||
const max_value = Math.pow(2, bit_size) - 1;
|
||||
|
||||
if (!/^\d+$/.test(arg_str)) {
|
||||
if (value < 0) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
message: `Expected unsigned integer.`,
|
||||
length: len,
|
||||
message: `${bit_size}-Bit unsigned integer can't be less than 0.`,
|
||||
});
|
||||
} else if (value > max_value) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
length: len,
|
||||
message: `${bit_size}-Bit unsigned integer can't be greater than ${max_value}.`,
|
||||
});
|
||||
} else {
|
||||
args.push({
|
||||
value,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
args.push({
|
||||
value,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
private parse_sint(arg_str: string, size: number, args: Arg[], col: number): void {
|
||||
private verify_sint(size: number, { col, len, value }: IntToken, args: Arg[]): void {
|
||||
const bit_size = 8 * size;
|
||||
const value = parseInt(arg_str, 10);
|
||||
const min_value = -Math.pow(2, bit_size - 1);
|
||||
const max_value = Math.pow(2, bit_size - 1) - 1;
|
||||
|
||||
if (!/^-?\d+$/.test(arg_str)) {
|
||||
if (value < min_value) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
message: `Expected signed integer.`,
|
||||
});
|
||||
} else if (value < min_value) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
length: len,
|
||||
message: `${bit_size}-Bit signed integer can't be less than ${min_value}.`,
|
||||
});
|
||||
} else if (value > max_value) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
length: len,
|
||||
message: `${bit_size}-Bit signed integer can't be greater than ${max_value}.`,
|
||||
});
|
||||
} else {
|
||||
args.push({
|
||||
value,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
args.push({
|
||||
value,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
private parse_float(arg_str: string, args: Arg[], col: number): void {
|
||||
const value = parseFloat(arg_str);
|
||||
|
||||
if (!Number.isFinite(value)) {
|
||||
private verify_register({ col, len, value }: RegisterToken, args: Arg[]): void {
|
||||
if (value > 255) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
message: `Expected floating point number.`,
|
||||
});
|
||||
} else {
|
||||
args.push({
|
||||
value,
|
||||
size: 4,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private parse_register(arg_str: string, args: Arg[], col: number): void {
|
||||
const value = parseInt(arg_str.slice(1), 10);
|
||||
|
||||
if (!/^r\d+$/.test(arg_str)) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
message: `Expected register reference.`,
|
||||
});
|
||||
} else if (value > 255) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
length: len,
|
||||
message: `Invalid register reference, expected r0-r255.`,
|
||||
});
|
||||
} else {
|
||||
args.push({
|
||||
value,
|
||||
size: 1,
|
||||
});
|
||||
}
|
||||
|
||||
args.push({
|
||||
value,
|
||||
size: 1,
|
||||
});
|
||||
}
|
||||
|
||||
private parse_string(arg_str: string, args: Arg[], col: number): void {
|
||||
if (!/^"([^"\\]|\\.)*"$/.test(arg_str)) {
|
||||
private parse_bytes(first_token: IntToken): void {
|
||||
const bytes = [];
|
||||
let token: Token = first_token;
|
||||
let i = 0;
|
||||
|
||||
while (token.type === TokenType.Int) {
|
||||
if (token.value < 0) {
|
||||
this.add_error({
|
||||
col: token.col,
|
||||
length: token.len,
|
||||
message: `8-Bit unsigned integer can't be less than 0.`,
|
||||
});
|
||||
} else if (token.value > 255) {
|
||||
this.add_error({
|
||||
col: token.col,
|
||||
length: token.len,
|
||||
message: `8-Bit unsigned integer can't be greater than 255.`,
|
||||
});
|
||||
}
|
||||
|
||||
bytes.push(token.value);
|
||||
|
||||
if (i < this.tokens.length) {
|
||||
token = this.tokens[i++];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < this.tokens.length) {
|
||||
this.add_error({
|
||||
col,
|
||||
length: arg_str.length,
|
||||
message: `Expected string.`,
|
||||
});
|
||||
} else {
|
||||
const value = JSON.parse(arg_str);
|
||||
args.push({
|
||||
value,
|
||||
size: 2 + 2 * value.length,
|
||||
col: token.col,
|
||||
length: token.len,
|
||||
message: "Unexpected token.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private parse_uint_varargs(
|
||||
arg_tokens: ArgToken[],
|
||||
index: number,
|
||||
size: number,
|
||||
args: Arg[]
|
||||
): void {
|
||||
for (; index < arg_tokens.length; index++) {
|
||||
const arg_token = arg_tokens[index];
|
||||
const col = arg_token.col;
|
||||
this.parse_uint(arg_token.arg, size, args, col);
|
||||
}
|
||||
this.add_bytes(bytes);
|
||||
}
|
||||
}
|
||||
|
@ -6,39 +6,71 @@ import { Arg, Param, Segment, SegmentType, Type } from "../data_formats/parsing/
|
||||
export function disassemble(object_code: Segment[], manual_stack: boolean = false): string[] {
|
||||
const lines: string[] = [];
|
||||
const stack: Arg[] = [];
|
||||
let code_block: boolean | undefined;
|
||||
|
||||
for (const segment of object_code) {
|
||||
if (segment.type === SegmentType.Data) {
|
||||
continue;
|
||||
if (code_block !== false) {
|
||||
code_block = false;
|
||||
lines.push(".data");
|
||||
}
|
||||
} else {
|
||||
if (code_block !== true) {
|
||||
code_block = true;
|
||||
lines.push(".code");
|
||||
}
|
||||
}
|
||||
|
||||
if (segment.label !== -1) {
|
||||
lines.push(`${segment.label}:`);
|
||||
}
|
||||
|
||||
for (const instruction of segment.instructions) {
|
||||
if (!manual_stack && instruction.opcode.push_stack) {
|
||||
stack.push(...instruction.args);
|
||||
} else {
|
||||
let args = args_to_strings(instruction.opcode.params, instruction.args);
|
||||
if (segment.type === SegmentType.Data) {
|
||||
const bytes = new Uint8Array(segment.data);
|
||||
let line = " ";
|
||||
|
||||
if (!manual_stack) {
|
||||
args.push(
|
||||
...args_to_strings(
|
||||
instruction.opcode.stack_params,
|
||||
stack.splice(
|
||||
Math.max(0, stack.length - instruction.opcode.stack_params.length),
|
||||
instruction.opcode.stack_params.length
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
line += "0x" + bytes[i].toString(16).padStart(2, "0");
|
||||
|
||||
if (i % 16 === 15) {
|
||||
lines.push(line);
|
||||
line = " ";
|
||||
} else if (i < bytes.length - 1) {
|
||||
line += " ";
|
||||
}
|
||||
}
|
||||
|
||||
if (line.length > 4) {
|
||||
lines.push(line);
|
||||
}
|
||||
} else {
|
||||
for (const instruction of segment.instructions) {
|
||||
if (!manual_stack && instruction.opcode.push_stack) {
|
||||
stack.push(...instruction.args);
|
||||
} else {
|
||||
let args = args_to_strings(instruction.opcode.params, instruction.args);
|
||||
|
||||
if (!manual_stack) {
|
||||
args.push(
|
||||
...args_to_strings(
|
||||
instruction.opcode.stack_params,
|
||||
stack.splice(
|
||||
Math.max(
|
||||
0,
|
||||
stack.length - instruction.opcode.stack_params.length
|
||||
),
|
||||
instruction.opcode.stack_params.length
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
" " +
|
||||
instruction.opcode.mnemonic +
|
||||
(args.length ? " " + args.join(", ") : "")
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
" " +
|
||||
instruction.opcode.mnemonic +
|
||||
(args.length ? " " + args.join(", ") : "")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,22 +16,24 @@ const ASM_SYNTAX: languages.IMonarchLanguage = {
|
||||
// Registers.
|
||||
[/r\d+/, "predefined"],
|
||||
|
||||
// Identifiers.
|
||||
[/[a-z][a-z0-9_=<>!]*/, "identifier"],
|
||||
[/\.[^\s]+|(^|\s+)bytes($|\s+)/, "keyword"],
|
||||
|
||||
// Labels.
|
||||
[/\d+:/, "tag"],
|
||||
[/[^\s]+:/, "tag"],
|
||||
|
||||
// Numbers.
|
||||
[/-?\d+\.\d+/, "number.float"],
|
||||
[/0x[0-9a-fA-F]+/, "number.hex"],
|
||||
[/-?[0-9]+?/, "number"],
|
||||
|
||||
// Identifiers.
|
||||
[/[a-z][a-z0-9_=<>!]*/, "identifier"],
|
||||
|
||||
// Whitespace.
|
||||
[/[ \t\r\n]+/, "white"],
|
||||
// [/\/\*/, "comment", "@comment"],
|
||||
// [/\/\/.*$/, "comment"],
|
||||
|
||||
// Numbers.
|
||||
[/-?\d*\.\d+([eE][-+]?\d+)?/, "number.float"],
|
||||
// [/-?0[xX][0-9a-fA-F]+/, "number.hex"],
|
||||
[/-?\d+/, "number"],
|
||||
|
||||
// Delimiters.
|
||||
[/,/, "delimiter"],
|
||||
|
||||
@ -87,7 +89,7 @@ languages.registerCompletionItemProvider("psoasm", {
|
||||
languages.setLanguageConfiguration("psoasm", {
|
||||
indentationRules: {
|
||||
increaseIndentPattern: /^\s*\d+:/,
|
||||
decreaseIndentPattern: /^\s*\d+/,
|
||||
decreaseIndentPattern: /^\s*(\d+|\.)/,
|
||||
},
|
||||
autoClosingPairs: [{ open: '"', close: '"' }],
|
||||
surroundingPairs: [{ open: '"', close: '"' }],
|
||||
@ -99,8 +101,10 @@ editor.defineTheme("phantasmal-world", {
|
||||
rules: [
|
||||
{ token: "", foreground: "e0e0e0", background: "#181818" },
|
||||
{ token: "tag", foreground: "99bbff" },
|
||||
{ token: "keyword", foreground: "d0a0ff", fontStyle: "bold" },
|
||||
{ token: "predefined", foreground: "bbffbb" },
|
||||
{ token: "number", foreground: "ffffaa" },
|
||||
{ token: "number.hex", foreground: "ddffaa" },
|
||||
{ token: "string", foreground: "88ffff" },
|
||||
{ token: "string.escape", foreground: "8888ff" },
|
||||
],
|
||||
|
@ -517,11 +517,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
|
||||
integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==
|
||||
|
||||
"@types/text-encoding@^0.0.35":
|
||||
version "0.0.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/text-encoding/-/text-encoding-0.0.35.tgz#6f14474e0b232bc70c59677aadc65dcc5a99c3a9"
|
||||
integrity sha512-jfo/A88XIiAweUa8np+1mPbm3h2w0s425YrI8t3wk5QxhH6UI7w517MboNVnGDeMSuoFwA8Rwmklno+FicvV4g==
|
||||
|
||||
"@types/uglify-js@*":
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082"
|
||||
|
Loading…
Reference in New Issue
Block a user