AssemblyCraftCompiler 1.2.0__py3-none-any.whl
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.
- asmcc/__init__.py +238 -0
- assemblycraftcompiler-1.2.0.dist-info/METADATA +29 -0
- assemblycraftcompiler-1.2.0.dist-info/RECORD +6 -0
- assemblycraftcompiler-1.2.0.dist-info/WHEEL +5 -0
- assemblycraftcompiler-1.2.0.dist-info/licenses/LICENSE +11 -0
- assemblycraftcompiler-1.2.0.dist-info/top_level.txt +1 -0
asmcc/__init__.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
__version__ = "1.2.0"
|
|
2
|
+
import struct
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
class AssemblyCraftCompilerError(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
class AssemblyCraftCompiler:
|
|
9
|
+
MAGIC_HEADER = (b"ACX"+__version__.split(".")[0].encode())
|
|
10
|
+
|
|
11
|
+
ALLOC_SIZES = {
|
|
12
|
+
'ds': 16,
|
|
13
|
+
'dm': 32,
|
|
14
|
+
'db': 64
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
REGISTERS = {
|
|
18
|
+
'rax': 0x00, 'rbx': 0x01, 'rcx': 0x02, 'rdx': 0x03,
|
|
19
|
+
'rsi': 0x04, 'rdi': 0x05, 'rbp': 0x06, 'rsp': 0x07,
|
|
20
|
+
'r8': 0x08, 'r9': 0x09, 'r10': 0x0A, 'r11': 0x0B,
|
|
21
|
+
'r12': 0x0C, 'r13': 0x0D, 'r14': 0x0E, 'r15': 0x0F
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
OPCODES = {
|
|
25
|
+
'mov': 0x01,
|
|
26
|
+
'push': 0x02,
|
|
27
|
+
'pop': 0x03,
|
|
28
|
+
'call': 0x04,
|
|
29
|
+
'ret': 0x05,
|
|
30
|
+
'cmp': 0x06,
|
|
31
|
+
'jmp': 0x07,
|
|
32
|
+
'je': 0x08,
|
|
33
|
+
'jne': 0x09,
|
|
34
|
+
'jg': 0x0A,
|
|
35
|
+
'jl': 0x0B,
|
|
36
|
+
'jge': 0x0C,
|
|
37
|
+
'jle': 0x0D,
|
|
38
|
+
'xor': 0x0E,
|
|
39
|
+
'add': 0x0F,
|
|
40
|
+
'sub': 0x10,
|
|
41
|
+
'mul': 0x11,
|
|
42
|
+
'div': 0x12,
|
|
43
|
+
'syscall': 0x13
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
ARG_TYPE_REG = 0x01
|
|
47
|
+
ARG_TYPE_VAR = 0x02
|
|
48
|
+
ARG_TYPE_IMM = 0x03
|
|
49
|
+
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.variables = {}
|
|
52
|
+
self.labels = {}
|
|
53
|
+
self.bytecode = bytearray()
|
|
54
|
+
|
|
55
|
+
def compile(self, source_code: str, output_filepath: str):
|
|
56
|
+
data_lines, text_lines = self._split_sections(source_code)
|
|
57
|
+
|
|
58
|
+
if not text_lines:
|
|
59
|
+
raise AssemblyCraftCompilerError("Compilation failed: Missing or empty 'section .text'.")
|
|
60
|
+
|
|
61
|
+
self._parse_data_section(data_lines)
|
|
62
|
+
self._parse_text_section(text_lines)
|
|
63
|
+
|
|
64
|
+
binary_data = self._generate_binary()
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
with open(output_filepath, "wb") as f:
|
|
68
|
+
f.write(binary_data)
|
|
69
|
+
except OSError as e:
|
|
70
|
+
raise AssemblyCraftCompilerError(
|
|
71
|
+
f"Failed to write output file '{output_filepath}': {e}"
|
|
72
|
+
) from e
|
|
73
|
+
|
|
74
|
+
def _split_sections(self, code: str):
|
|
75
|
+
current_section = None
|
|
76
|
+
data_lines, text_lines = [], []
|
|
77
|
+
|
|
78
|
+
for line_num, line in enumerate(code.splitlines(), start=1):
|
|
79
|
+
line = re.sub(r';.*$', '', line).strip()
|
|
80
|
+
if not line:
|
|
81
|
+
continue
|
|
82
|
+
if line.startswith("section .data"):
|
|
83
|
+
current_section = "data"
|
|
84
|
+
continue
|
|
85
|
+
elif line.startswith("section .text"):
|
|
86
|
+
current_section = "text"
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if current_section == "data":
|
|
90
|
+
data_lines.append((line_num, line))
|
|
91
|
+
elif current_section == "text":
|
|
92
|
+
text_lines.append((line_num, line))
|
|
93
|
+
else:
|
|
94
|
+
raise AssemblyCraftCompilerError(
|
|
95
|
+
f"Line {line_num}: Statement '{line}' outside of any section "
|
|
96
|
+
f"(expected 'section .data' or 'section .text')."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return data_lines, text_lines
|
|
100
|
+
|
|
101
|
+
def _parse_data_section(self, lines: list):
|
|
102
|
+
var_id = 0
|
|
103
|
+
for line_num, line in lines:
|
|
104
|
+
match = re.match(r'^([a-zA-Z_]\w*)\s+(ds|dm|db)\s+(.+)$', line)
|
|
105
|
+
if not match:
|
|
106
|
+
raise AssemblyCraftCompilerError(
|
|
107
|
+
f"Line {line_num}: Malformed data statement '{line}'."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
var_name, size_type, values_str = match.groups()
|
|
111
|
+
|
|
112
|
+
if var_name in self.variables:
|
|
113
|
+
raise AssemblyCraftCompilerError(
|
|
114
|
+
f"Line {line_num}: Duplicate variable definition '{var_name}'."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
alloc_len = self.ALLOC_SIZES[size_type]
|
|
118
|
+
raw_bytes = bytearray()
|
|
119
|
+
tokens = re.findall(r'"([^"]*)"|(\d+|0x[0-9a-fA-F]+)', values_str)
|
|
120
|
+
|
|
121
|
+
if not tokens:
|
|
122
|
+
raise AssemblyCraftCompilerError(
|
|
123
|
+
f"Line {line_num}: No valid value initializers found for variable '{var_name}'."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
for str_val, num_val in tokens:
|
|
127
|
+
if str_val:
|
|
128
|
+
try:
|
|
129
|
+
decoded_str = str_val.encode('utf-8').decode('unicode_escape')
|
|
130
|
+
raw_bytes.extend(decoded_str.encode('utf-8'))
|
|
131
|
+
except Exception as e:
|
|
132
|
+
raise AssemblyCraftCompilerError(
|
|
133
|
+
f"Line {line_num}: Invalid escape sequence in string for '{var_name}': {e}"
|
|
134
|
+
) from e
|
|
135
|
+
elif num_val:
|
|
136
|
+
val = int(num_val, 0)
|
|
137
|
+
if not (0 <= val <= 255):
|
|
138
|
+
raise AssemblyCraftCompilerError(
|
|
139
|
+
f"Line {line_num}: Byte literal {val} out of range (0-255) in '{var_name}'."
|
|
140
|
+
)
|
|
141
|
+
raw_bytes.append(val)
|
|
142
|
+
|
|
143
|
+
if len(raw_bytes) > alloc_len:
|
|
144
|
+
raise AssemblyCraftCompilerError(
|
|
145
|
+
f"Line {line_num}: Initializer size ({len(raw_bytes)} bytes) exceeds allocated "
|
|
146
|
+
f"capacity for '{size_type}' ({alloc_len} bytes) in variable '{var_name}'."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
padded_data = bytes(raw_bytes).ljust(alloc_len, b'\x00')
|
|
150
|
+
|
|
151
|
+
self.variables[var_name] = {
|
|
152
|
+
'id': var_id,
|
|
153
|
+
'size_type': size_type,
|
|
154
|
+
'alloc_len': alloc_len,
|
|
155
|
+
'data': padded_data
|
|
156
|
+
}
|
|
157
|
+
var_id += 1
|
|
158
|
+
|
|
159
|
+
def _parse_text_section(self, lines: list):
|
|
160
|
+
bytecode_offset = 0
|
|
161
|
+
cleaned_instructions = []
|
|
162
|
+
|
|
163
|
+
for line_num, line in lines:
|
|
164
|
+
if line.endswith(':'):
|
|
165
|
+
label_name = line[:-1].strip()
|
|
166
|
+
if not label_name or not re.match(r'^[a-zA-Z_]\w*$', label_name):
|
|
167
|
+
raise AssemblyCraftCompilerError(
|
|
168
|
+
f"Line {line_num}: Invalid label identifier '{label_name}'."
|
|
169
|
+
)
|
|
170
|
+
if label_name in self.labels:
|
|
171
|
+
raise AssemblyCraftCompilerError(
|
|
172
|
+
f"Line {line_num}: Duplicate label definition '{label_name}'."
|
|
173
|
+
)
|
|
174
|
+
self.labels[label_name] = bytecode_offset
|
|
175
|
+
else:
|
|
176
|
+
cleaned_instructions.append((line_num, line))
|
|
177
|
+
parts = line.split(maxsplit=1)
|
|
178
|
+
args_str = parts[1] if len(parts) > 1 else ""
|
|
179
|
+
num_args = len([a.strip() for a in args_str.split(',') if a.strip()]) if args_str else 0
|
|
180
|
+
bytecode_offset += 1 + (num_args * 5)
|
|
181
|
+
|
|
182
|
+
for line_num, line in cleaned_instructions:
|
|
183
|
+
parts = line.split(maxsplit=1)
|
|
184
|
+
mnemonic = parts[0].lower()
|
|
185
|
+
args_str = parts[1] if len(parts) > 1 else ""
|
|
186
|
+
|
|
187
|
+
if mnemonic not in self.OPCODES:
|
|
188
|
+
raise AssemblyCraftCompilerError(
|
|
189
|
+
f"Line {line_num}: Unknown instruction mnemonic '{mnemonic}'."
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
self.bytecode.append(self.OPCODES[mnemonic])
|
|
193
|
+
|
|
194
|
+
if args_str:
|
|
195
|
+
args = [a.strip() for a in args_str.split(',') if a.strip()]
|
|
196
|
+
for arg in args:
|
|
197
|
+
arg_type, arg_val = self._resolve_operand(arg, line_num)
|
|
198
|
+
self.bytecode.append(arg_type)
|
|
199
|
+
try:
|
|
200
|
+
self.bytecode += struct.pack("<i", arg_val)
|
|
201
|
+
except struct.error:
|
|
202
|
+
raise AssemblyCraftCompilerError(
|
|
203
|
+
f"Line {line_num}: Operand value {arg_val} exceeds 32-bit signed integer limits."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def _resolve_operand(self, token: str, line_num: int = None):
|
|
207
|
+
prefix = f"Line {line_num}: " if line_num else ""
|
|
208
|
+
|
|
209
|
+
if token in self.REGISTERS:
|
|
210
|
+
return self.ARG_TYPE_REG, self.REGISTERS[token]
|
|
211
|
+
if token in self.variables:
|
|
212
|
+
return self.ARG_TYPE_VAR, self.variables[token]['id']
|
|
213
|
+
if token in self.labels:
|
|
214
|
+
return self.ARG_TYPE_IMM, self.labels[token]
|
|
215
|
+
try:
|
|
216
|
+
return self.ARG_TYPE_IMM, int(token, 0)
|
|
217
|
+
except ValueError:
|
|
218
|
+
raise AssemblyCraftCompilerError(
|
|
219
|
+
f"{prefix}Unresolved operand or label token '{token}'."
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _generate_binary(self) -> bytearray:
|
|
223
|
+
key_table = bytearray()
|
|
224
|
+
data_payload = bytearray()
|
|
225
|
+
|
|
226
|
+
base_offset = 10 + (len(self.variables) * 22)
|
|
227
|
+
current_data_offset = base_offset
|
|
228
|
+
|
|
229
|
+
for var_name, info in self.variables.items():
|
|
230
|
+
encoded_name = var_name.encode('utf-8')[:16].ljust(16, b'\x00')
|
|
231
|
+
key_table += struct.pack("<B16sIB", info['id'], encoded_name, current_data_offset, info['alloc_len'])
|
|
232
|
+
data_payload += info['data']
|
|
233
|
+
current_data_offset += info['alloc_len']
|
|
234
|
+
|
|
235
|
+
text_offset = current_data_offset
|
|
236
|
+
header = bytearray(self.MAGIC_HEADER) + struct.pack("<HI", len(self.variables), text_offset)
|
|
237
|
+
|
|
238
|
+
return header + key_table + data_payload + self.bytecode
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: AssemblyCraftCompiler
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: The official Assembly Craft compiler
|
|
5
|
+
Author-email: Orbinuity <orbinuity@ratgers.nl>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Donate, https://www.patreon.com/cw/Orbinuity
|
|
8
|
+
Project-URL: Homepage, https://orbinuity.nl/project#ASMCC
|
|
9
|
+
Project-URL: Source, https://github.com/orbinuity/ASMCC
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Assembly Craft Compiler
|
|
18
|
+
|
|
19
|
+
[](https://orbinuity.nl/license)
|
|
20
|
+
[](https://github.com/Orbinuity/ASMCC/commits)
|
|
21
|
+
[](https://github.com/Orbinuity/ASMCC/releases/v1.2.0)
|
|
22
|
+
[](https://orbinuity.nl/)
|
|
23
|
+
[](https://help.orbinuity.nl/doc?project=ASMCC)
|
|
24
|
+
|
|
25
|
+
The official Assembly Craft compiler
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
Before copying any part of this project, please read the [LICENSE](https://orbinuity.nl/license) file to understand the terms and conditions.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
asmcc/__init__.py,sha256=N2sIbVHN4Jdvw4I3LZk8w8PrWPgRVCXKem7fw2tXGaI,8825
|
|
2
|
+
assemblycraftcompiler-1.2.0.dist-info/licenses/LICENSE,sha256=5uESSdSbXo6n2Vf2g_CcPLDLLoudTw6H4KMbvJWVHpM,1458
|
|
3
|
+
assemblycraftcompiler-1.2.0.dist-info/METADATA,sha256=6JJyRhWNwRZlBs8nRNQzCqPkM6hDzCa8VIsuDPnInGU,1327
|
|
4
|
+
assemblycraftcompiler-1.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
5
|
+
assemblycraftcompiler-1.2.0.dist-info/top_level.txt,sha256=DjZ_MVLsCnX7-3suNs2qDoVFOkg5VsoE3DRc3MqUvIE,6
|
|
6
|
+
assemblycraftcompiler-1.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Copyright 2026 Orbinuity
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
4
|
+
|
|
5
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
6
|
+
|
|
7
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
8
|
+
|
|
9
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
10
|
+
|
|
11
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
asmcc
|