@andrivet/z80-assembler 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +13 -0
- package/.eslintignore +3 -0
- package/.eslintrc.json +35 -0
- package/.github/workflows/node.js.yml +47 -0
- package/.prettierignore +3 -0
- package/.prettierrc +3 -0
- package/.vscode/extensions.json +7 -0
- package/README.md +53 -27
- package/apps/.gitkeep +0 -0
- package/apps/z80-assembler-app/.eslintrc.json +18 -0
- package/apps/z80-assembler-app/index.html +16 -0
- package/apps/z80-assembler-app/postcss.config.js +24 -0
- package/apps/z80-assembler-app/project.json +70 -0
- package/apps/z80-assembler-app/public/favicon.ico +0 -0
- package/apps/z80-assembler-app/public/logo.png +0 -0
- package/apps/z80-assembler-app/public/logo192.png +0 -0
- package/apps/z80-assembler-app/public/logo512.png +0 -0
- package/apps/z80-assembler-app/public/manifest.json +25 -0
- package/apps/z80-assembler-app/public/robots.txt +3 -0
- package/apps/z80-assembler-app/src/app/app.module.css +0 -0
- package/apps/z80-assembler-app/src/app/app.tsx +122 -0
- package/apps/z80-assembler-app/src/app/binary.tsx +39 -0
- package/apps/z80-assembler-app/src/app/editor.tsx +228 -0
- package/apps/z80-assembler-app/src/app/errors.tsx +31 -0
- package/apps/z80-assembler-app/src/app/footer.tsx +20 -0
- package/apps/z80-assembler-app/src/app/header.tsx +57 -0
- package/apps/z80-assembler-app/src/app/misc.ts +13 -0
- package/apps/z80-assembler-app/src/app/opcodes.tsx +335 -0
- package/apps/z80-assembler-app/src/assets/.gitkeep +0 -0
- package/apps/z80-assembler-app/src/assets/images/logo192.png +0 -0
- package/apps/z80-assembler-app/src/main.tsx +22 -0
- package/apps/z80-assembler-app/src/styles.css +8 -0
- package/apps/z80-assembler-app/tailwind.config.js +28 -0
- package/apps/z80-assembler-app/tsconfig.app.json +22 -0
- package/apps/z80-assembler-app/tsconfig.json +21 -0
- package/apps/z80-assembler-app/tsconfig.spec.json +23 -0
- package/apps/z80-assembler-app/vite.config.ts +64 -0
- package/assets/images/compile.png +0 -0
- package/assets/images/logo.png +0 -0
- package/assets/images/menu.png +0 -0
- package/assets/images/opcodes-load8.png +0 -0
- package/assets/images/opcodes.png +0 -0
- package/assets/images/open-dir.png +0 -0
- package/assets/images/z80-assembler-app.png +0 -0
- package/docs/assembly.md +551 -0
- package/docs/images/ZX81-0x00.png +0 -0
- package/docs/images/ZX81-0x0B.png +0 -0
- package/docs/images/ZX81-0x0C.png +0 -0
- package/docs/images/ZX81-0x0D.png +0 -0
- package/docs/images/ZX81-0x0E.png +0 -0
- package/docs/images/ZX81-0x0F.png +0 -0
- package/docs/images/ZX81-0x10.png +0 -0
- package/docs/images/ZX81-0x11.png +0 -0
- package/docs/images/ZX81-0x12.png +0 -0
- package/docs/images/ZX81-0x13.png +0 -0
- package/docs/images/ZX81-0x14.png +0 -0
- package/docs/images/ZX81-0x15.png +0 -0
- package/docs/images/ZX81-0x16.png +0 -0
- package/docs/images/ZX81-0x17.png +0 -0
- package/docs/images/ZX81-0x18.png +0 -0
- package/docs/images/ZX81-0x19.png +0 -0
- package/docs/images/ZX81-0x1A.png +0 -0
- package/docs/images/ZX81-0x1B.png +0 -0
- package/docs/images/ZX81-0x1C.png +0 -0
- package/docs/images/ZX81-0x1D.png +0 -0
- package/docs/images/ZX81-0x1E.png +0 -0
- package/docs/images/ZX81-0x1F.png +0 -0
- package/docs/images/ZX81-0x20.png +0 -0
- package/docs/images/ZX81-0x21.png +0 -0
- package/docs/images/ZX81-0x22.png +0 -0
- package/docs/images/ZX81-0x23.png +0 -0
- package/docs/images/ZX81-0x24.png +0 -0
- package/docs/images/ZX81-0x25.png +0 -0
- package/docs/images/ZX81-0x26.png +0 -0
- package/docs/images/ZX81-0x27.png +0 -0
- package/docs/images/ZX81-0x28.png +0 -0
- package/docs/images/ZX81-0x29.png +0 -0
- package/docs/images/ZX81-0x2A.png +0 -0
- package/docs/images/ZX81-0x2B.png +0 -0
- package/docs/images/ZX81-0x2C.png +0 -0
- package/docs/images/ZX81-0x2D.png +0 -0
- package/docs/images/ZX81-0x2E.png +0 -0
- package/docs/images/ZX81-0x2F.png +0 -0
- package/docs/images/ZX81-0x30.png +0 -0
- package/docs/images/ZX81-0x31.png +0 -0
- package/docs/images/ZX81-0x32.png +0 -0
- package/docs/images/ZX81-0x33.png +0 -0
- package/docs/images/ZX81-0x34.png +0 -0
- package/docs/images/ZX81-0x35.png +0 -0
- package/docs/images/ZX81-0x36.png +0 -0
- package/docs/images/ZX81-0x37.png +0 -0
- package/docs/images/ZX81-0x38.png +0 -0
- package/docs/images/ZX81-0x39.png +0 -0
- package/docs/images/ZX81-0x3A.png +0 -0
- package/docs/images/ZX81-0x3B.png +0 -0
- package/docs/images/ZX81-0x3C.png +0 -0
- package/docs/images/ZX81-0x3D.png +0 -0
- package/docs/images/ZX81-0x3E.png +0 -0
- package/docs/images/ZX81-0x3F.png +0 -0
- package/docs/images/ZX81-0x80.png +0 -0
- package/docs/images/ZX81-0x8B.png +0 -0
- package/docs/images/ZX81-0x8C.png +0 -0
- package/docs/images/ZX81-0x8D.png +0 -0
- package/docs/images/ZX81-0x8E.png +0 -0
- package/docs/images/ZX81-0x8F.png +0 -0
- package/docs/images/ZX81-0x90.png +0 -0
- package/docs/images/ZX81-0x91.png +0 -0
- package/docs/images/ZX81-0x92.png +0 -0
- package/docs/images/ZX81-0x93.png +0 -0
- package/docs/images/ZX81-0x94.png +0 -0
- package/docs/images/ZX81-0x95.png +0 -0
- package/docs/images/ZX81-0x96.png +0 -0
- package/docs/images/ZX81-0x97.png +0 -0
- package/docs/images/ZX81-0x98.png +0 -0
- package/docs/images/ZX81-0x99.png +0 -0
- package/docs/images/ZX81-0x9A.png +0 -0
- package/docs/images/ZX81-0x9B.png +0 -0
- package/docs/images/ZX81-0x9C.png +0 -0
- package/docs/images/ZX81-0x9D.png +0 -0
- package/docs/images/ZX81-0x9E.png +0 -0
- package/docs/images/ZX81-0x9F.png +0 -0
- package/docs/images/ZX81-0xA0.png +0 -0
- package/docs/images/ZX81-0xA1.png +0 -0
- package/docs/images/ZX81-0xA2.png +0 -0
- package/docs/images/ZX81-0xA3.png +0 -0
- package/docs/images/ZX81-0xA4.png +0 -0
- package/docs/images/ZX81-0xA5.png +0 -0
- package/docs/images/ZX81-0xA6.png +0 -0
- package/docs/images/ZX81-0xA7.png +0 -0
- package/docs/images/ZX81-0xA8.png +0 -0
- package/docs/images/ZX81-0xA9.png +0 -0
- package/docs/images/ZX81-0xAA.png +0 -0
- package/docs/images/ZX81-0xAB.png +0 -0
- package/docs/images/ZX81-0xAC.png +0 -0
- package/docs/images/ZX81-0xAD.png +0 -0
- package/docs/images/ZX81-0xAE.png +0 -0
- package/docs/images/ZX81-0xAF.png +0 -0
- package/docs/images/ZX81-0xB0.png +0 -0
- package/docs/images/ZX81-0xB1.png +0 -0
- package/docs/images/ZX81-0xB2.png +0 -0
- package/docs/images/ZX81-0xB3.png +0 -0
- package/docs/images/ZX81-0xB4.png +0 -0
- package/docs/images/ZX81-0xB5.png +0 -0
- package/docs/images/ZX81-0xB6.png +0 -0
- package/docs/images/ZX81-0xB7.png +0 -0
- package/docs/images/ZX81-0xB8.png +0 -0
- package/docs/images/ZX81-0xB9.png +0 -0
- package/docs/images/ZX81-0xBA.png +0 -0
- package/docs/images/ZX81-0xBB.png +0 -0
- package/docs/images/ZX81-0xBC.png +0 -0
- package/docs/images/ZX81-0xBD.png +0 -0
- package/docs/images/ZX81-0xBE.png +0 -0
- package/docs/images/ZX81-0xBF.png +0 -0
- package/libs/.gitkeep +0 -0
- package/libs/z80-assembler/.eslintrc.json +18 -0
- package/libs/z80-assembler/package.json +20 -0
- package/libs/z80-assembler/project.json +35 -0
- package/libs/z80-assembler/public/README.md +54 -0
- package/{index.d.ts → libs/z80-assembler/src/index.ts} +1 -1
- package/libs/z80-assembler/src/lib/assets/code/basic-end.zx81 +4 -0
- package/libs/z80-assembler/src/lib/assets/code/basic-line1.zx81 +4 -0
- package/libs/z80-assembler/src/lib/assets/code/basic-line2.zx81 +9 -0
- package/libs/z80-assembler/src/lib/assets/code/characters.zx81 +190 -0
- package/libs/z80-assembler/src/lib/assets/code/display.zx81 +50 -0
- package/libs/z80-assembler/src/lib/assets/code/system-variables.zx81 +46 -0
- package/{lib/compiler/Assets.d.ts → libs/z80-assembler/src/lib/compiler/Assets.ts} +6 -1
- package/libs/z80-assembler/src/lib/compiler/Ast.ts +545 -0
- package/libs/z80-assembler/src/lib/compiler/Compiler.test.ts +2141 -0
- package/libs/z80-assembler/src/lib/compiler/Compiler.ts +185 -0
- package/libs/z80-assembler/src/lib/compiler/Formatter.ts +43 -0
- package/libs/z80-assembler/src/lib/compiler/Generator.ts +255 -0
- package/libs/z80-assembler/src/lib/compiler/Labels.ts +165 -0
- package/libs/z80-assembler/src/lib/grammar/LowLevel.ts +163 -0
- package/libs/z80-assembler/src/lib/grammar/Parse.ts +128 -0
- package/libs/z80-assembler/src/lib/grammar/z80.peg +1252 -0
- package/libs/z80-assembler/src/lib/grammar/z80.ts +10649 -0
- package/libs/z80-assembler/src/lib/types/Error.ts +105 -0
- package/{lib/types/Types.d.ts → libs/z80-assembler/src/lib/types/Types.ts} +26 -11
- package/libs/z80-assembler/tsconfig.json +23 -0
- package/libs/z80-assembler/tsconfig.lib.json +10 -0
- package/libs/z80-assembler/tsconfig.spec.json +19 -0
- package/libs/z80-assembler/vite.config.ts +58 -0
- package/nx.json +57 -0
- package/package.json +52 -14
- package/tsconfig.base.json +22 -0
- package/index.js +0 -312
- package/index.mjs +0 -6441
- package/lib/compiler/Ast.d.ts +0 -210
- package/lib/compiler/Compiler.d.ts +0 -53
- package/lib/compiler/Formatter.d.ts +0 -23
- package/lib/compiler/Generator.d.ts +0 -40
- package/lib/compiler/Labels.d.ts +0 -47
- package/lib/grammar/LowLevel.d.ts +0 -68
- package/lib/grammar/Parse.d.ts +0 -48
- package/lib/grammar/z80.d.ts +0 -2938
- package/lib/types/Error.d.ts +0 -62
- /package/{CHANGELOG.md → libs/z80-assembler/public/CHANGELOG.md} +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z80 Assembler in Typescript
|
|
3
|
+
*
|
|
4
|
+
* File: Compiler.ts
|
|
5
|
+
* Description: Compiler (assembler)
|
|
6
|
+
* Author: Sebastien Andrivet
|
|
7
|
+
* License: GPLv3
|
|
8
|
+
* Copyrights: Copyright (C) 2023 Sebastien Andrivet
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {Parser, PosInfo} from "../grammar/z80";
|
|
12
|
+
import {CompilationInfo, LinesInfo} from "../types/Types";
|
|
13
|
+
import {CompilationError} from "../types/Error";
|
|
14
|
+
import {computeLabels, generate} from "./Generator";
|
|
15
|
+
import {resetLabels} from "./Labels";
|
|
16
|
+
import {
|
|
17
|
+
assetBasicEnd,
|
|
18
|
+
assetBasicLine1,
|
|
19
|
+
assetBasicLine2,
|
|
20
|
+
assetCharacters,
|
|
21
|
+
assetDisplay,
|
|
22
|
+
assetSystemVariables
|
|
23
|
+
} from "./Assets";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Type of the internal data.
|
|
27
|
+
*/
|
|
28
|
+
interface ParseData {
|
|
29
|
+
// The name of the output file as declared by the output directive.
|
|
30
|
+
outputName: string,
|
|
31
|
+
// The name of the SLD file as declared by the output directive.
|
|
32
|
+
sldName: string,
|
|
33
|
+
// The name of the device as declared by the device directive.
|
|
34
|
+
deviceName: string,
|
|
35
|
+
// The base path of first file compiled. It is used to load included files.
|
|
36
|
+
basePath: string,
|
|
37
|
+
// The name of the current file compiled. It changes when a file is included and parsed.
|
|
38
|
+
fileName: string,
|
|
39
|
+
// The function to use when including a file to get its source code.
|
|
40
|
+
getFileCode: (filename: string) => string //
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Internal data (i.e. globals).
|
|
45
|
+
* Unfortunately, tsPEG does not allow to declare a context for the parsing.
|
|
46
|
+
* So instead, we use this ugly global.
|
|
47
|
+
*/
|
|
48
|
+
const parseData: ParseData = {
|
|
49
|
+
outputName: "",
|
|
50
|
+
sldName: "",
|
|
51
|
+
deviceName: "",
|
|
52
|
+
basePath: '',
|
|
53
|
+
fileName: '',
|
|
54
|
+
getFileCode: () => ''
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set the outputs names (output directive).
|
|
59
|
+
* @param filename Filename for the binary.
|
|
60
|
+
* @param sld Filename for the SLD.
|
|
61
|
+
*/
|
|
62
|
+
function setOutputName(filename: string, sld?: string) {
|
|
63
|
+
parseData.outputName = filename;
|
|
64
|
+
parseData.sldName = sld ? sld : filename.replace(/\.P$/, '.sld');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set the name of the target (device directive)
|
|
69
|
+
* @param name The name of the device
|
|
70
|
+
*/
|
|
71
|
+
function setDevice(name: string) {
|
|
72
|
+
parseData.deviceName = name.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Include an assembly file (include directive).
|
|
77
|
+
* @param pos Position of the include directive.
|
|
78
|
+
* @param filename The filename of the file to be included.
|
|
79
|
+
*/
|
|
80
|
+
function includeFile(pos: PosInfo, filename: string): LinesInfo {
|
|
81
|
+
const filepath = parseData.basePath + filename;
|
|
82
|
+
// Ask the code for the file to be included
|
|
83
|
+
const code = parseData.getFileCode(filepath);
|
|
84
|
+
// Return the lines (AST) and the associated filename.
|
|
85
|
+
return parseCode(filepath, code);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse some assembly code.
|
|
90
|
+
* @param filename Name of the file corresponding to the code.
|
|
91
|
+
* @param code The code to parse.
|
|
92
|
+
*/
|
|
93
|
+
function parseCode(filename: string, code: string): LinesInfo {
|
|
94
|
+
// It is not possible to attach a context to the parsing, so use this dirty trick.
|
|
95
|
+
const oldFileName = parseData.fileName;
|
|
96
|
+
parseData.fileName = filename;
|
|
97
|
+
|
|
98
|
+
// If the code does not end with a new line, add it.
|
|
99
|
+
if(!code.endsWith('\n')) code += '\n';
|
|
100
|
+
// Declare a parser for the code.
|
|
101
|
+
const parser = new Parser(code);
|
|
102
|
+
// Parse the code.
|
|
103
|
+
const result = parser.parse();
|
|
104
|
+
parseData.fileName = oldFileName;
|
|
105
|
+
|
|
106
|
+
// If there is an error, raise an exception.
|
|
107
|
+
if(result.errs.length > 0)
|
|
108
|
+
throw CompilationError.fromSyntaxErr(filename, result.errs[0])
|
|
109
|
+
// Returns the lines, i.e. the AST
|
|
110
|
+
return {lines: result.ast?.lines ?? [], filename: filename};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the file name of a file path.
|
|
115
|
+
* @param filepath The file path.
|
|
116
|
+
*/
|
|
117
|
+
function getBasePath(filepath: string): string {
|
|
118
|
+
let index = filepath.lastIndexOf('/');
|
|
119
|
+
if(index === -1)
|
|
120
|
+
index = filepath.lastIndexOf('\\');
|
|
121
|
+
if(index === -1)
|
|
122
|
+
return '';
|
|
123
|
+
return filepath.substring(0, index + 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Compile an assembly source code.
|
|
128
|
+
* @param filepath The file path of the source code.
|
|
129
|
+
* @param code The assembly source code.
|
|
130
|
+
* @param getFileCode A function to get the content of included files.
|
|
131
|
+
*/
|
|
132
|
+
function compile(filepath: string, code: string, getFileCode: (filename: string) => string): CompilationInfo {
|
|
133
|
+
// Set default values globally
|
|
134
|
+
parseData.outputName = filepath.replace(/\..*$/, '') + '.P';
|
|
135
|
+
parseData.sldName = parseData.outputName.replace(/\.P$/, '.sld');
|
|
136
|
+
parseData.basePath = getBasePath(filepath);
|
|
137
|
+
parseData.getFileCode = getFileCode;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// Reset the labels.
|
|
141
|
+
resetLabels();
|
|
142
|
+
// Parse this source code.
|
|
143
|
+
const parsed = postProcessing(parseCode(filepath, code));
|
|
144
|
+
// Compute the value of the labels.
|
|
145
|
+
computeLabels(0, parsed);
|
|
146
|
+
const generated = generate(filepath, 0, parsed);
|
|
147
|
+
|
|
148
|
+
// Generate the bytes, the SLD and return them.
|
|
149
|
+
return {
|
|
150
|
+
outputName: parseData.outputName,
|
|
151
|
+
bytes: generated.bytes,
|
|
152
|
+
sld: generated.sld,
|
|
153
|
+
errs: []
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch(ex: any) { // eslint-disable-line
|
|
157
|
+
// Return the errors.
|
|
158
|
+
return {
|
|
159
|
+
outputName: parseData.outputName,
|
|
160
|
+
bytes: [],
|
|
161
|
+
sld: '',
|
|
162
|
+
errs: [CompilationError.fromAny(parseData.fileName, ex)]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Processing of the AST after the parsing. This is used to inject code specific to the ZX81.
|
|
169
|
+
* @param info The parsed AST (lines).
|
|
170
|
+
*/
|
|
171
|
+
function postProcessing(info: LinesInfo): LinesInfo[] {
|
|
172
|
+
if(parseData.deviceName !== 'zx81') return [info];
|
|
173
|
+
|
|
174
|
+
return [
|
|
175
|
+
parseCode('@internal/characters.zx81', assetCharacters),
|
|
176
|
+
parseCode('@internal/system-variables.zx81', assetSystemVariables),
|
|
177
|
+
parseCode('@internal/basic-line1.zx81', assetBasicLine1),
|
|
178
|
+
info,
|
|
179
|
+
parseCode('@internal/basic-line2.zx81', assetBasicLine2),
|
|
180
|
+
parseCode('@internal/display.zx81', assetDisplay),
|
|
181
|
+
parseCode('@internal/basic-end.zx81', assetBasicEnd)
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export {parseData, compile, includeFile, setOutputName, setDevice};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z80 Assembler in Typescript
|
|
3
|
+
*
|
|
4
|
+
* File: Formatter.ts
|
|
5
|
+
* Description: Format bytes to display them easily
|
|
6
|
+
* Author: Sebastien Andrivet
|
|
7
|
+
* License: GPLv3
|
|
8
|
+
* Copyrights: Copyright (C) 2023 Sebastien Andrivet
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents a chunk of data to display binary.
|
|
13
|
+
*/
|
|
14
|
+
interface Chunk {
|
|
15
|
+
// The address of the chunk of data.
|
|
16
|
+
address: string;
|
|
17
|
+
// The bytes (converted to strings).
|
|
18
|
+
bytes: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Format bytes to display them.
|
|
23
|
+
* @param bytes An array of bytes.
|
|
24
|
+
* @param perLine Number of bytes per line.
|
|
25
|
+
*/
|
|
26
|
+
function formatBytes(bytes: number[], perLine: number): Chunk[] {
|
|
27
|
+
let data: Chunk[] = [];
|
|
28
|
+
let address = 0;
|
|
29
|
+
// For each byte...
|
|
30
|
+
while(address < bytes.length) {
|
|
31
|
+
// Cut a slice
|
|
32
|
+
const chunk = bytes.slice(address, address + perLine);
|
|
33
|
+
// Format the address and each byte
|
|
34
|
+
data = data.concat({
|
|
35
|
+
address: address.toString(16).padStart(4, '0'),
|
|
36
|
+
bytes: chunk.map(b => b.toString(16).padStart(2, '0')).join(' ')
|
|
37
|
+
});
|
|
38
|
+
address += perLine;
|
|
39
|
+
}
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {formatBytes, Chunk};
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z80 Assembler in Typescript
|
|
3
|
+
*
|
|
4
|
+
* File: Generator.ts
|
|
5
|
+
* Description: Generate bytes and SLD
|
|
6
|
+
* Author: Sebastien Andrivet
|
|
7
|
+
* License: GPLv3
|
|
8
|
+
* Copyrights: Copyright (C) 2023 Sebastien Andrivet
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {ASTKinds, Line, Lines, Statement} from "../grammar/z80";
|
|
12
|
+
import {bytes, LinesInfo} from '../types/Types';
|
|
13
|
+
import {addLabel, addLabelExpression, getLabelValue, isLabelUsed, resetLabelsRecursion} from "./Labels";
|
|
14
|
+
import {AstElement, AstElements, getByteSize, isAbstract} from "./Ast";
|
|
15
|
+
import {parseData} from "./Compiler";
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Information about a compiled program.
|
|
20
|
+
*/
|
|
21
|
+
interface ProgramInfo {
|
|
22
|
+
// Bytes of the compiled program.
|
|
23
|
+
bytes: bytes;
|
|
24
|
+
// Name of the binary file.
|
|
25
|
+
outputName: string;
|
|
26
|
+
// Name if the SLD file.
|
|
27
|
+
outputSldName: string;
|
|
28
|
+
// Next address after the compiled bytes.
|
|
29
|
+
address: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compute, whenever possible, the value associated with labels.
|
|
34
|
+
* @param address The starting address.
|
|
35
|
+
* @param infos The lines (i.e. the AST)
|
|
36
|
+
* @return the address that corresponds to the code after the lines (i.e. the new starting address).
|
|
37
|
+
*/
|
|
38
|
+
function computeLabels(address: number, infos: LinesInfo[]): number {
|
|
39
|
+
return infos.reduce((r: number, c: LinesInfo) => {
|
|
40
|
+
if(c.lines.length <= 0) return r;
|
|
41
|
+
computeEqualities(c.lines);
|
|
42
|
+
return computeVariableLabels(r, c.lines)
|
|
43
|
+
}, address);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Compute the value of labels associated with equalities.
|
|
48
|
+
* @param lines The lines (i.e. the AST)
|
|
49
|
+
*/
|
|
50
|
+
function computeEqualities(lines: Lines) {
|
|
51
|
+
// For each line...
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
resetLabelsRecursion();
|
|
54
|
+
// If it is an equality, add the label and its value (an expression).
|
|
55
|
+
if(line.kind === ASTKinds.LineEqual)
|
|
56
|
+
addLabelExpression(line.label.pos, line.label.name, line.equal.e);
|
|
57
|
+
// If it is a statement, and it contains an inclusion, compute recursively the labels for those lines.
|
|
58
|
+
else if(line.kind === ASTKinds.LineStatement && line.statement?.kind === ASTKinds.Statement_1)
|
|
59
|
+
computeEqualities(line.statement.inc.info.lines);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Compute labels with a value dependent of the code generated
|
|
65
|
+
* @param address The starting address.
|
|
66
|
+
* @param lines The lines (i.e. the AST)
|
|
67
|
+
* @return the address that corresponds to the code after the lines (i.e. the new starting address).
|
|
68
|
+
*/
|
|
69
|
+
function computeVariableLabels(address: number, lines: Lines): number {
|
|
70
|
+
// For each line...
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
// If it is not a line statement, the address remains the same.
|
|
73
|
+
if(line.kind !== ASTKinds.LineStatement) continue;
|
|
74
|
+
// If the line starts with a label, record it (and its address).
|
|
75
|
+
if(line.label) addLabel({filename: parseData.fileName, pos: line.label.pos}, line.label.name, address);
|
|
76
|
+
// If it is a statement, compute the new address (after the statement).
|
|
77
|
+
if(line.statement) address = computeAddress(address, line.statement);
|
|
78
|
+
}
|
|
79
|
+
return address;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Compute the address following a statement.
|
|
84
|
+
* @param address The starting address.
|
|
85
|
+
* @param statement The statement.
|
|
86
|
+
* @return the address that corresponds to the code after the statement (i.e. the new starting address).
|
|
87
|
+
*/
|
|
88
|
+
function computeAddress(address: number, statement: Statement): number {
|
|
89
|
+
switch(statement.kind) {
|
|
90
|
+
case ASTKinds.Statement_1:
|
|
91
|
+
// If it is an include statement, compute recursively the labels for those lines.
|
|
92
|
+
return computeLabels(address, [statement.info]);
|
|
93
|
+
|
|
94
|
+
case ASTKinds.Statement_2:
|
|
95
|
+
// If it is an instruction, increment the address by the number of bytes that will be generated.
|
|
96
|
+
return address + statement.elements.reduce((r: number, c) => r + getByteSize(c), 0);
|
|
97
|
+
|
|
98
|
+
case ASTKinds.Statement_3:
|
|
99
|
+
// If it is a directive, take either the address set by the directive (origin) or
|
|
100
|
+
// increment the address by the number of bytes that will be generated.
|
|
101
|
+
return statement.address ? statement.address :
|
|
102
|
+
address + statement.elements.reduce((r: number, c) => r + getByteSize(c), 0);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface GenerationData {
|
|
107
|
+
bytes: bytes;
|
|
108
|
+
sld: string;
|
|
109
|
+
address: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generate machine code bytes and SLD for an array of lines (i.e. an AST)
|
|
114
|
+
* @param mainfile Main file name
|
|
115
|
+
* @param address The current address.
|
|
116
|
+
* @param infos The lines (i.e. the AST) with the associated filename
|
|
117
|
+
* @return An array of bytes (numbers), debug data and the next address
|
|
118
|
+
*/
|
|
119
|
+
function generate(mainfile: string, address: number, infos: LinesInfo[]): GenerationData {
|
|
120
|
+
// Generate the SLD header.
|
|
121
|
+
const header = '|SLD.data.version|1\n' +
|
|
122
|
+
`${mainfile}|1||0|-1|-1|Z|pages.size:65536,pages.count:32,slots.count:1,slots.adr:0\n`;
|
|
123
|
+
const generated = generateLines(address, infos);
|
|
124
|
+
return {bytes: generated.bytes, sld: header + generated.sld, address: address};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generate machine code bytes and SLD for an array of lines (i.e. an AST)
|
|
129
|
+
* @param address The current address.
|
|
130
|
+
* @param infos The lines (i.e. the AST) with the associated filename
|
|
131
|
+
* @return An array of bytes (numbers), debug data and the next address
|
|
132
|
+
*/
|
|
133
|
+
function generateLines(address: number, infos: LinesInfo[]): GenerationData {
|
|
134
|
+
// Start with an empty array of bytes.
|
|
135
|
+
let bytes: bytes = [];
|
|
136
|
+
let sld = '';
|
|
137
|
+
let end = false;
|
|
138
|
+
|
|
139
|
+
// For each file...
|
|
140
|
+
for(const info of infos) {
|
|
141
|
+
// Each file starts at line 1.
|
|
142
|
+
let lineNumber = 1;
|
|
143
|
+
// For each line...
|
|
144
|
+
for (const line of info.lines) {
|
|
145
|
+
// If it is not a statement, take the next line.
|
|
146
|
+
if (line.kind !== ASTKinds.LineStatement || !line.statement) {
|
|
147
|
+
sld += generateSld(info.filename, lineNumber, address, line);
|
|
148
|
+
lineNumber += 1; // Next line
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
switch (line.statement?.kind) {
|
|
153
|
+
case ASTKinds.Statement_1: {
|
|
154
|
+
// If it is an include, generate the bytes for those lines and concatenate the result with the already computed bytes.
|
|
155
|
+
const generated = generateLines(address, [line.statement.info]);
|
|
156
|
+
bytes = bytes.concat(generated.bytes);
|
|
157
|
+
sld += generated.sld;
|
|
158
|
+
address = generated.address;
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case ASTKinds.Statement_2:
|
|
163
|
+
case ASTKinds.Statement_3: {
|
|
164
|
+
// Generate bytes for the AST elements of the line and concatenate the result with the already computed bytes.
|
|
165
|
+
bytes = bytes.concat(generateElements(address, line.statement.elements));
|
|
166
|
+
sld += generateSld(info.filename, lineNumber, address, line);
|
|
167
|
+
// Compute the address after the line.
|
|
168
|
+
address = computeAddress(address, line.statement);
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Special case for end directive
|
|
174
|
+
if(line.statement.kind === ASTKinds.Statement_3 && line.statement.dir.kind === ASTKinds.Directive_5) {
|
|
175
|
+
end = true;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if(end) break;
|
|
180
|
+
lineNumber += 1; // Next line
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {bytes: bytes, sld: sld, address: address};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Generate machine code bytes for some AST elements.
|
|
189
|
+
* @param address The current address.
|
|
190
|
+
* @param elements The AST elements.
|
|
191
|
+
*/
|
|
192
|
+
function generateElements(address: number, elements: AstElements): bytes {
|
|
193
|
+
// The element is either concrete (i.e. the raw byte) or abstract. In the later case, ask the abstract
|
|
194
|
+
// AST element to generate its machine code. Concatenate all the generated bytes.
|
|
195
|
+
return elements.reduce((r: bytes, c: AstElement) =>
|
|
196
|
+
r.concat(isAbstract(c) ? c.generate(address) : [c]), [] as bytes)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Generate debugging information (in Source Level Debugging format) for the lines.
|
|
201
|
+
* @param filename Name of the file parsed
|
|
202
|
+
* @param lineNumber Number of the current line in source code
|
|
203
|
+
* @param address Address for the line
|
|
204
|
+
* @param line Line of code
|
|
205
|
+
* @return The debugging information (in Source Level Debugging format)
|
|
206
|
+
*/
|
|
207
|
+
function generateSld(filename: string, lineNumber: number, address: number, line: Line): string {
|
|
208
|
+
// Generate SLD for labels and for instruction tracing.
|
|
209
|
+
return generateSldLabel(filename, lineNumber, line, address) +
|
|
210
|
+
generateSldTrace(filename, lineNumber, line, address);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generate a SLD Label for an AST line
|
|
215
|
+
* @param filename The current filename
|
|
216
|
+
* @param lineNumber The current line number
|
|
217
|
+
* @param line The AST line
|
|
218
|
+
* @param address Address for the line
|
|
219
|
+
*/
|
|
220
|
+
function generateSldLabel(filename: string, lineNumber: number, line: Line, address: number): string {
|
|
221
|
+
// If there is no label on the line, there is nothing to generate.
|
|
222
|
+
if(!line.label) return '';
|
|
223
|
+
// Get the value associated with the label.
|
|
224
|
+
const value = getLabelValue(address, line.label.name, {filename: filename,
|
|
225
|
+
pos: {line: lineNumber, offset: 0, overallPos: 0}}, false, true);
|
|
226
|
+
// Is this label used in the program?
|
|
227
|
+
const isUsed = isLabelUsed(line.label.name);
|
|
228
|
+
|
|
229
|
+
// Generate a SLD line like:
|
|
230
|
+
// ./includes/zx81-characters.asm|3||0|-1|0|L|,_SPC,,+equ,+used
|
|
231
|
+
if(line.kind === ASTKinds.LineEqual)
|
|
232
|
+
return `${filename}|${lineNumber}||0|-1|${value}|L|,${line.label.name},,+equ${isUsed ? ',+used' : ''}\n`;
|
|
233
|
+
|
|
234
|
+
// Generate a SLD line like:
|
|
235
|
+
// ./includes/zx81-system-variables.asm|12||0|0|16393|L|,VERSN,
|
|
236
|
+
return `${filename}|${lineNumber}||0|0|${value}|L|,${line.label.name},${isUsed ? ',+used' : ''}\n`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Generate a SLD Instruction Tracing for an AST line
|
|
241
|
+
* @param filename The current filename
|
|
242
|
+
* @param lineNumber The current line number
|
|
243
|
+
* @param line The AST line
|
|
244
|
+
* @param address The current address (i.e. the address of the line)
|
|
245
|
+
*/
|
|
246
|
+
function generateSldTrace(filename: string, lineNumber: number, line: Line, address: number): string {
|
|
247
|
+
// If it is not a statement, there is nothing to generate.
|
|
248
|
+
if(line.kind !== ASTKinds.LineStatement) return '';
|
|
249
|
+
|
|
250
|
+
// Generate a SLD line like:
|
|
251
|
+
// main.zx81|23||0|0|16531|T|
|
|
252
|
+
return line.statement?.kind === ASTKinds.Statement_2 ? `${filename}|${lineNumber}||0|0|${address}|T|\n` : '';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export {computeLabels, generate, ProgramInfo};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z80 Assembler in Typescript
|
|
3
|
+
*
|
|
4
|
+
* File: Labels.ts
|
|
5
|
+
* Description: Labels associated to locations in memory
|
|
6
|
+
* Author: Sebastien Andrivet
|
|
7
|
+
* License: GPLv3
|
|
8
|
+
* Copyrights: Copyright (C) 2023 Sebastien Andrivet
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {Expression, PosInfo} from "../grammar/z80";
|
|
12
|
+
import {Address, Position} from '../types/Types';
|
|
13
|
+
import {CompilationError} from "../types/Error";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A label, i.e. a name associated with a value or an address.
|
|
18
|
+
*/
|
|
19
|
+
interface Label {
|
|
20
|
+
// Expression associated with the label.
|
|
21
|
+
expression: Expression | null;
|
|
22
|
+
// The value associated with the label.
|
|
23
|
+
value: number;
|
|
24
|
+
// Is the value known, or it has to be computed?
|
|
25
|
+
known: boolean;
|
|
26
|
+
// Is the label used in the program or only declared?
|
|
27
|
+
used: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Labels are stored as a map.
|
|
32
|
+
*/
|
|
33
|
+
type Labels = Map<string, Label>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A global variable with all the labels of the program.
|
|
37
|
+
*/
|
|
38
|
+
const labels: Labels = new Map<string, Label>();
|
|
39
|
+
|
|
40
|
+
let getLabelValueRecursion = 0;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reset the labels.
|
|
44
|
+
*/
|
|
45
|
+
function resetLabels() {
|
|
46
|
+
labels.clear();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resetLabelsRecursion() {
|
|
50
|
+
getLabelValueRecursion = 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Add a label.
|
|
55
|
+
* @param pos The position of the label in the source code.
|
|
56
|
+
* @param name The name of the label.
|
|
57
|
+
* @param value The value of the label (if known) or null (if unknown).
|
|
58
|
+
*/
|
|
59
|
+
function addLabel(pos: Position, name: string, value: number | null) {
|
|
60
|
+
// Do we have already a label with this name?
|
|
61
|
+
const label = labels.get(name);
|
|
62
|
+
if(!label) {
|
|
63
|
+
// No, so create a new label and record it.
|
|
64
|
+
labels.set(name, {expression: null, value: value ?? 0, known: value != null, used: false});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Yes, we already have a label with this name. Is its value known and it is the same?
|
|
69
|
+
if(label.known && label.value != value) {
|
|
70
|
+
// The values are not the same, so raise a compilation error.
|
|
71
|
+
throw new CompilationError(pos,
|
|
72
|
+
`The value of the label '${name}' is redefined (old value: ${label.value}, new value: ${value})`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Set the value.
|
|
76
|
+
label.value = value ?? 0;
|
|
77
|
+
// If the value is not null, it is known.
|
|
78
|
+
label.known = value != null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Add a label and its associated expression.
|
|
83
|
+
* @param _ The position of the label in the source code.
|
|
84
|
+
* @param name The name of the label.
|
|
85
|
+
* @param expression The expression that gives that value of the label.
|
|
86
|
+
*/
|
|
87
|
+
function addLabelExpression(_: PosInfo, name: string, expression: Expression) {
|
|
88
|
+
// Do we have already a label with this name?
|
|
89
|
+
const label = labels.get(name);
|
|
90
|
+
if(!label) {
|
|
91
|
+
// No, so create a new label and record it.
|
|
92
|
+
labels.set(name, {expression: expression, value: 0, known: false, used: false});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Yes, we already have a label with this name. If the value is already known, nothing more to do.
|
|
97
|
+
if(label.known) return;
|
|
98
|
+
// If the value is unknown, record the expression.
|
|
99
|
+
label.expression = expression;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get the value associated with a label.
|
|
104
|
+
* @param pc Current program counter (PC).
|
|
105
|
+
* @param name The name of the label.
|
|
106
|
+
* @param pos Where this label is used.
|
|
107
|
+
* @param setUsed If set, mark the label as used.
|
|
108
|
+
* @param mustExist If set, the value of the label has to be known (not null).
|
|
109
|
+
* @return The value associated with a label or null if it is unknown.
|
|
110
|
+
*/
|
|
111
|
+
function getLabelValue(pc: Address, name: string, pos: Position, setUsed: boolean, mustExist: boolean): number | null {
|
|
112
|
+
if(getLabelValueRecursion > 20)
|
|
113
|
+
throw new CompilationError(pos, `Label '${name}' is undetermined (too many recursions)`);
|
|
114
|
+
|
|
115
|
+
// Pseudo label $
|
|
116
|
+
if(name === '$') return pc;
|
|
117
|
+
|
|
118
|
+
// Do we have a label with this name?
|
|
119
|
+
const label = labels.get(name);
|
|
120
|
+
if(!label) {
|
|
121
|
+
if(mustExist) throw new CompilationError(pos, `Label '${name}' is undefined`);
|
|
122
|
+
// No, so create a new label and record it as unknown.
|
|
123
|
+
labels.set(name, {expression: null, value: 0, known: false, used: true});
|
|
124
|
+
return null; // Value unknown. Valeur inconnue.
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Yes, we have a label with this name.
|
|
128
|
+
|
|
129
|
+
// If setUsed, record that this label is used in the program.
|
|
130
|
+
if(setUsed) label.used = true;
|
|
131
|
+
// If the value is already known, return it.
|
|
132
|
+
if(label.known) return label.value;
|
|
133
|
+
// If the value is unknown and there is no expression, we do not know how to compute the value so return null.
|
|
134
|
+
if(label.expression == null) {
|
|
135
|
+
if(mustExist) throw new CompilationError(pos, `Label '${name}' is undefined`);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The value is currently unknown, but we have an expression to compute it. So try to compute it.
|
|
140
|
+
getLabelValueRecursion += 1;
|
|
141
|
+
const value = label.expression.eval(pc, mustExist);
|
|
142
|
+
getLabelValueRecursion -= 1;
|
|
143
|
+
// It was not possible to compute the value yet, so return null.
|
|
144
|
+
if(value == null) {
|
|
145
|
+
if(mustExist) throw new CompilationError(pos, `Label '${name}' is undefined`);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// We were able to compute the value so record it, and it is now known.
|
|
150
|
+
label.value = value;
|
|
151
|
+
label.known = true;
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Is a label used in the program or only declared?
|
|
157
|
+
* @param name The name of the label.
|
|
158
|
+
* @return true if the label is used, false otherwise.
|
|
159
|
+
*/
|
|
160
|
+
function isLabelUsed(name: string) {
|
|
161
|
+
const label = labels.get(name);
|
|
162
|
+
return label?.used;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export {resetLabels, resetLabelsRecursion, addLabel, addLabelExpression, getLabelValue, isLabelUsed}
|