openc166 0.1.15__py3-none-win32.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.
openc166/__init__.py ADDED
@@ -0,0 +1,574 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """openc166: a Keil A166-compatible assembler for the Infineon C166/C167.
3
+
4
+ >>> import openc166
5
+ >>> result = openc166.assemble('''
6
+ ... PROG SECTION CODE AT 0E000H
7
+ ... MOV R4, #0BEEFH
8
+ ... RET
9
+ ... PROG ENDS
10
+ ... END
11
+ ... ''')
12
+ >>> result.ok, result.diagnostics
13
+ (True, [])
14
+ >>> image = result.image()
15
+ >>> hex(image.base), image.data.hex()
16
+ ('0xe000', 'e6f4efbecb00')
17
+
18
+ assemble_many() and assemble_files() assemble several modules and link
19
+ them, as Keil's A166 and L166 would: EXTERN symbols take their values from
20
+ PUBLIC ones in other modules.
21
+
22
+ assemble() never raises for mistakes in the source: they are listed in
23
+ Result.diagnostics. Result.image() raises AssemblyError if there are
24
+ errors, so code that only wants the bytes can write
25
+ ``openc166.assemble(src).image().data``.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import ctypes
31
+ import os
32
+ import threading
33
+ import weakref
34
+ from dataclasses import dataclass, field
35
+ from typing import Dict, Iterable, List, Mapping, Optional, Tuple, Union
36
+
37
+ from . import _native
38
+
39
+ __all__ = [
40
+ "assemble",
41
+ "assemble_file",
42
+ "assemble_many",
43
+ "assemble_files",
44
+ "Result",
45
+ "Diagnostic",
46
+ "Chunk",
47
+ "Image",
48
+ "Symbol",
49
+ "Section",
50
+ "AssemblyError",
51
+ "__version__",
52
+ ]
53
+
54
+ _lib = _native.lib
55
+
56
+ __version__: str = _lib.oc166_version().decode("ascii")
57
+
58
+ PathLike = Union[str, "os.PathLike[str]"]
59
+ SourceText = Union[str, bytes]
60
+
61
+
62
+ def _text(b: Optional[bytes]) -> str:
63
+ return b.decode("utf-8", "replace") if b else ""
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class Diagnostic:
68
+ """An error or warning. str() gives "file:line:column: error A62: message"."""
69
+
70
+ severity: str # "error" or "warning"
71
+ code: str # Keil A166 number such as "A62", or "OCn" for openc166's own
72
+ message: str
73
+ file: str
74
+ line: int # 1-based; 0 if not tied to a line
75
+ column: int # 1-based; 0 if unknown
76
+ text: str = field(repr=False, compare=False, default="")
77
+
78
+ @property
79
+ def is_error(self) -> bool:
80
+ return self.severity == "error"
81
+
82
+ def __str__(self) -> str:
83
+ return self.text
84
+
85
+
86
+ @dataclass(frozen=True)
87
+ class Chunk:
88
+ """Bytes emitted at consecutive addresses."""
89
+
90
+ address: int
91
+ data: bytes
92
+
93
+ @property
94
+ def end(self) -> int:
95
+ """Address after the last byte."""
96
+ return self.address + len(self.data)
97
+
98
+
99
+ @dataclass(frozen=True)
100
+ class Image:
101
+ """One contiguous memory image: data[0] belongs at address 'base'."""
102
+
103
+ base: int
104
+ data: bytes
105
+
106
+ @property
107
+ def end(self) -> int:
108
+ """Address after the last byte."""
109
+ return self.base + len(self.data)
110
+
111
+ def __bytes__(self) -> bytes:
112
+ return self.data
113
+
114
+ def __len__(self) -> int:
115
+ return len(self.data)
116
+
117
+
118
+ @dataclass(frozen=True)
119
+ class Symbol:
120
+ """A name defined in the source.
121
+
122
+ kind is how it was defined: "label", "equ", "set", "bit", "lit" or
123
+ "extern". An "extern" has the value of the PUBLIC symbol it names, and
124
+ its 'text' is the type from the EXTERN line, such as "WORD".
125
+ type says what value holds:
126
+ "number" a number or address
127
+ "gpr" a word register: 0..15 for R0..R15
128
+ "gpr_byte" a byte register: 0..15 for RL0, RH0, RL1, ...
129
+ "sfr" a special function register: its address
130
+ "bit" a bit: address of the word that holds it; see 'bit'
131
+ "cc" a condition code: its 4-bit encoding
132
+ "" a LIT, which has no value; see 'text'
133
+ value is None for a LIT or if it could not be worked out (an error).
134
+ """
135
+
136
+ name: str
137
+ kind: str
138
+ type: str
139
+ value: Optional[int]
140
+ label_type: str = "" # "near", "far", "byte", "word", "dword" or ""
141
+ bit: Optional[int] = None # bit number 0..15 of a "bit"
142
+ text: str = "" # replacement text of a LIT, type of an EXTERN
143
+ line: int = 0 # listing line of the definition, counted in its module
144
+ module: str = "" # file name of the module that defines it
145
+ exported: bool = False # named by PUBLIC or GLOBAL, so other modules can use it
146
+
147
+
148
+ @dataclass(frozen=True)
149
+ class Section:
150
+ """Where a SECTION went.
151
+
152
+ type is "CODE", "DATA", "CONST", "HDATA", "HCONST", "XDATA", "XCONST"
153
+ or "BIT". align is "BIT", "BYTE", "WORD", "DWORD", "PAGE", "SEGMENT",
154
+ "BITADDRESSABLE" or "PECADDRESSABLE". placement says how the address was chosen: "at"
155
+ (AT in the source), "given" (the 'sections' argument of assemble()) or
156
+ "located" (by openc166, in the first free place from the origin on).
157
+ class_name is the class from the SECTION line ('ROM' gives "ROM"), or "".
158
+ group is the DGROUP or CGROUP the section belongs to, or "".
159
+ size counts bytes; a BIT section counts every byte it touches. For a BIT
160
+ section, bit is the bit (0..7) of address it starts at and bit_size its
161
+ size in bits; other sections have bit 0 and bit_size 8 * size.
162
+ """
163
+
164
+ name: str
165
+ type: str
166
+ align: str
167
+ address: int
168
+ size: int
169
+ placement: str
170
+ class_name: str = ""
171
+ group: str = ""
172
+ bit: int = 0
173
+ bit_size: int = -1 # -1 gives 8 * size
174
+
175
+ def __post_init__(self) -> None:
176
+ if self.bit_size < 0:
177
+ object.__setattr__(self, "bit_size", 8 * self.size)
178
+
179
+ @property
180
+ def end(self) -> int:
181
+ """Address after the last byte."""
182
+ return self.address + self.size
183
+
184
+
185
+ class AssemblyError(Exception):
186
+ """The source has errors. 'diagnostics' lists them; 'result' is the whole Result."""
187
+
188
+ def __init__(self, result: "Result"):
189
+ self.result = result
190
+ self.diagnostics: List[Diagnostic] = result.errors
191
+ lines = [str(d) for d in self.diagnostics[:20]]
192
+ if len(self.diagnostics) > 20:
193
+ lines.append("... and {} more".format(len(self.diagnostics) - 20))
194
+ super().__init__("{} error(s):\n{}".format(len(self.diagnostics), "\n".join(lines)))
195
+
196
+
197
+ class Result:
198
+ """The outcome of assembling a source."""
199
+
200
+ def __init__(self, handle: int):
201
+ self._handle = handle
202
+ self._lock = threading.Lock()
203
+ self._finalizer = weakref.finalize(self, _lib.oc166_result_free, handle)
204
+ self._listing: Optional[str] = None
205
+
206
+ self.cpu: str = _lib.oc166_result_cpu(handle).decode("ascii")
207
+ """CPU the source was assembled for: "c166" or "c167"."""
208
+
209
+ self.diagnostics: List[Diagnostic] = []
210
+ """Errors and warnings in source order."""
211
+ d = _native.Diagnostic()
212
+ for i in range(_lib.oc166_result_diagnostic_count(handle)):
213
+ _lib.oc166_result_diagnostic(handle, i, ctypes.byref(d))
214
+ self.diagnostics.append(
215
+ Diagnostic(
216
+ "error" if d.is_error else "warning",
217
+ _text(d.code),
218
+ _text(d.message),
219
+ _text(d.file),
220
+ d.line,
221
+ d.column,
222
+ _text(d.text),
223
+ )
224
+ )
225
+
226
+ self.chunks: List[Chunk] = []
227
+ """Code and data in source order."""
228
+ c = _native.Chunk()
229
+ for i in range(_lib.oc166_result_chunk_count(handle)):
230
+ _lib.oc166_result_chunk(handle, i, ctypes.byref(c))
231
+ data = ctypes.string_at(c.data, c.size) if c.size else b""
232
+ self.chunks.append(Chunk(c.address, data))
233
+
234
+ self.modules: List[str] = [
235
+ _text(_lib.oc166_result_module(handle, i))
236
+ for i in range(_lib.oc166_result_module_count(handle))
237
+ ]
238
+ """File name of each module: one per source."""
239
+
240
+ self.all_symbols: List[Symbol] = []
241
+ """The names defined in every module, sorted by name, then module."""
242
+ s = _native.Symbol()
243
+ for i in range(_lib.oc166_result_symbol_count(handle)):
244
+ _lib.oc166_result_symbol(handle, i, ctypes.byref(s))
245
+ kind = _text(s.kind)
246
+ sym_type = _text(s.type)
247
+ module = _lib.oc166_result_symbol_module(handle, i)
248
+ self.all_symbols.append(
249
+ Symbol(
250
+ name=_text(s.name),
251
+ kind=kind,
252
+ type=sym_type,
253
+ value=s.value if s.known and kind != "lit" else None,
254
+ label_type=_text(s.label_type),
255
+ bit=s.bit if sym_type == "bit" else None,
256
+ text=_text(s.text),
257
+ line=s.line,
258
+ module=self.modules[module] if 0 <= module < len(self.modules) else "",
259
+ exported=_lib.oc166_result_symbol_exported(handle, i) == 1,
260
+ )
261
+ )
262
+
263
+ self.symbols: Dict[str, Symbol] = {}
264
+ """Defined names, spelled as in their definition, sorted by name.
265
+
266
+ If several modules define a name, this holds the PUBLIC or GLOBAL
267
+ one, or else the one in the first of them; all_symbols has them all.
268
+ """
269
+ for sym in self.all_symbols:
270
+ if sym.name not in self.symbols or (sym.exported and not self.symbols[sym.name].exported):
271
+ self.symbols[sym.name] = sym
272
+ self._folded = {name.upper(): sym for name, sym in self.symbols.items()}
273
+
274
+ self.sections: Dict[str, Section] = {}
275
+ """Sections in order of first appearance, by name as first spelled."""
276
+ sec = _native.Section()
277
+ first_bit = ctypes.c_int()
278
+ bit_count = ctypes.c_int64()
279
+ for i in range(_lib.oc166_result_section_count(handle)):
280
+ _lib.oc166_result_section(handle, i, ctypes.byref(sec))
281
+ _lib.oc166_result_section_bits(handle, i, ctypes.byref(first_bit), ctypes.byref(bit_count))
282
+ self.sections[_text(sec.name)] = Section(
283
+ name=_text(sec.name),
284
+ type=_text(sec.type),
285
+ align=_text(sec.align),
286
+ address=sec.address,
287
+ size=sec.size,
288
+ placement=_text(sec.placement),
289
+ class_name=_text(_lib.oc166_result_section_class(handle, i)),
290
+ group=_text(_lib.oc166_result_section_group(handle, i)),
291
+ bit=first_bit.value,
292
+ bit_size=bit_count.value,
293
+ )
294
+
295
+ @property
296
+ def errors(self) -> List[Diagnostic]:
297
+ return [d for d in self.diagnostics if d.is_error]
298
+
299
+ @property
300
+ def warnings(self) -> List[Diagnostic]:
301
+ return [d for d in self.diagnostics if not d.is_error]
302
+
303
+ @property
304
+ def ok(self) -> bool:
305
+ """True if there are no errors (warnings are allowed)."""
306
+ return not any(d.is_error for d in self.diagnostics)
307
+
308
+ def symbol(self, name: str) -> Symbol:
309
+ """Looks a symbol up ignoring case, as the assembler does. Raises KeyError."""
310
+ return self._folded[name.upper()]
311
+
312
+ @property
313
+ def listing(self) -> str:
314
+ """The listing: addresses, code bytes, source lines and diagnostics."""
315
+ if self._listing is None:
316
+ with self._lock:
317
+ text = _lib.oc166_result_listing(self._handle)
318
+ if text is None:
319
+ raise MemoryError("cannot format the listing")
320
+ self._listing = _text(text)
321
+ return self._listing
322
+
323
+ def image(self, base: Optional[int] = None, fill: int = 0xFF) -> Image:
324
+ """Lays all code and data out as one image.
325
+
326
+ The image starts at 'base', or at the lowest address used. Gaps are
327
+ filled with 'fill'. Raises AssemblyError if the source has errors,
328
+ and ValueError if code lies below 'base' or the image would be
329
+ larger than 16 MB.
330
+ """
331
+ if not self.ok:
332
+ raise AssemblyError(self)
333
+ if not 0 <= fill <= 0xFF:
334
+ raise ValueError("fill must be 0..255")
335
+ if base is not None and base < 0:
336
+ raise ValueError("base must not be negative")
337
+ img = _native.Image()
338
+ with self._lock:
339
+ rc = _lib.oc166_result_image(
340
+ self._handle, base is not None, base or 0, fill, ctypes.byref(img)
341
+ )
342
+ if rc != 0:
343
+ raise ValueError(_text(_lib.oc166_result_last_error(self._handle)))
344
+ data = ctypes.string_at(img.data, img.size) if img.size else b""
345
+ return Image(img.base, data)
346
+
347
+ def intel_hex(self, record_size: int = 16) -> str:
348
+ """The code and data as Intel HEX text.
349
+
350
+ Data records hold up to 'record_size' bytes (1..255). An extended
351
+ linear address record (type 04) comes before each 64 KB bank, and
352
+ lines end with CR LF, as Keil OH166 writes them. Only the bytes used
353
+ are written: gaps stay empty. Raises AssemblyError if the source has
354
+ errors, and ValueError for a bad record size.
355
+ """
356
+ return self._text_output(_lib.oc166_result_intel_hex, record_size)
357
+
358
+ def srec(self, record_size: int = 16, address_size: Optional[int] = None) -> str:
359
+ """The code and data as Motorola S-records.
360
+
361
+ address_size is 2 for S1 records, 3 for S2, 4 for S3, or None for
362
+ the smallest that holds every address. Data records hold up to
363
+ 'record_size' bytes. Raises AssemblyError if the source has errors,
364
+ and ValueError if an address does not fit or a size is out of range.
365
+ """
366
+ return self._text_output(_lib.oc166_result_srec, record_size, address_size or 0)
367
+
368
+ def _text_output(self, func, *args: int) -> str:
369
+ if not self.ok:
370
+ raise AssemblyError(self)
371
+ if any(not -(2**31) <= a < 2**31 for a in args):
372
+ raise ValueError("size out of range")
373
+ with self._lock:
374
+ text = func(self._handle, *args)
375
+ if text is None:
376
+ raise ValueError(_text(_lib.oc166_result_last_error(self._handle)))
377
+ return text.decode("ascii")
378
+
379
+ def __repr__(self) -> str:
380
+ return "<openc166.Result cpu={} errors={} warnings={} chunks={}>".format(
381
+ self.cpu, len(self.errors), len(self.warnings), len(self.chunks)
382
+ )
383
+
384
+
385
+ def assemble(
386
+ source: Union[str, bytes],
387
+ *,
388
+ file_name: Optional[PathLike] = None,
389
+ cpu: Optional[str] = None,
390
+ origin: int = 0,
391
+ include_dirs: Iterable[PathLike] = (),
392
+ sections: Optional[Mapping[str, int]] = None,
393
+ dialect: str = "keil",
394
+ controls: Iterable[str] = (),
395
+ ) -> Result:
396
+ """Assembles Keil A166 (or TASKING a166) source text.
397
+
398
+ source the text. A str is encoded as UTF-8; pass bytes to keep
399
+ another encoding (Keil sources are often Windows-1252).
400
+ file_name names the source in diagnostics; $INCLUDE also searches
401
+ its directory. The source is not read from it.
402
+ cpu "c166" (the default) or "c167". $MOD167 in the source
403
+ selects the C167 too.
404
+ origin address of code outside any section, and the lowest
405
+ address for sections without AT.
406
+ include_dirs more directories searched by $INCLUDE.
407
+ sections addresses for sections without AT, by name (any case),
408
+ like the SECTIONS control of Keil's L166:
409
+ {"MYDATA": 0xF600}. The other sections go in order of
410
+ appearance to the first free place from the origin on.
411
+ dialect "keil" (the default) for Keil A166 sources, or "tasking"
412
+ for TASKING a166 sources, which first pass through
413
+ openc166's m166 preprocessor (@SET, @IF, ...). TASKING
414
+ sources are always for the C167.
415
+ controls controls read before the source, as on the assembler's
416
+ command line: ["CASE", "STDNAMES(reg.def)"].
417
+ """
418
+ source = _source_bytes(source)
419
+ name = os.fsencode(os.fspath(file_name)) if file_name is not None else None
420
+ return _run(
421
+ lambda opts: _lib.oc166_assemble(opts, source, len(source), name),
422
+ cpu,
423
+ origin,
424
+ include_dirs,
425
+ sections,
426
+ dialect,
427
+ controls,
428
+ )
429
+
430
+
431
+ def _source_bytes(source: SourceText) -> bytes:
432
+ if isinstance(source, str):
433
+ return source.encode("utf-8")
434
+ if not isinstance(source, (bytes, bytearray, memoryview)):
435
+ raise TypeError("source must be str or bytes")
436
+ return bytes(source)
437
+
438
+
439
+ def _run(call, cpu, origin, include_dirs, sections, dialect, controls) -> Result:
440
+ """Makes the options, calls call(options) and wraps the result."""
441
+ opts = _lib.oc166_options_new()
442
+ if not opts:
443
+ raise MemoryError("cannot allocate options")
444
+ try:
445
+ if cpu is not None and _lib.oc166_options_set_cpu(opts, cpu.encode("ascii", "replace")) != 0:
446
+ raise ValueError("unknown CPU {!r} (use 'c166' or 'c167')".format(cpu))
447
+ if _lib.oc166_options_set_dialect(opts, str(dialect).encode("ascii", "replace")) != 0:
448
+ raise ValueError("unknown dialect {!r} (use 'keil' or 'tasking')".format(dialect))
449
+ if isinstance(controls, (str, bytes)):
450
+ raise TypeError("controls must be a list of strings, not a single string")
451
+ for c in controls:
452
+ if _lib.oc166_options_add_control(opts, str(c).encode("utf-8")) != 0:
453
+ raise MemoryError("cannot add control")
454
+ if _lib.oc166_options_set_origin(opts, origin) != 0:
455
+ raise ValueError("origin must not be negative")
456
+ for d in include_dirs:
457
+ _lib.oc166_options_add_include_dir(opts, os.fsencode(os.fspath(d)))
458
+ for sec_name, address in (sections or {}).items():
459
+ if not isinstance(address, int) or not 0 <= address <= 0xFFFFFF:
460
+ raise ValueError(
461
+ "bad address {!r} for section {!r} (use 0..0FFFFFFH)".format(address, sec_name)
462
+ )
463
+ if _lib.oc166_options_set_section_address(opts, str(sec_name).encode("utf-8"), address) != 0:
464
+ raise ValueError("bad section name {!r}".format(sec_name))
465
+ handle = call(opts)
466
+ finally:
467
+ _lib.oc166_options_free(opts)
468
+ if not handle:
469
+ raise MemoryError("out of memory while assembling")
470
+ return Result(handle)
471
+
472
+
473
+ def assemble_file(
474
+ path: PathLike,
475
+ *,
476
+ cpu: Optional[str] = None,
477
+ origin: int = 0,
478
+ include_dirs: Iterable[PathLike] = (),
479
+ sections: Optional[Mapping[str, int]] = None,
480
+ dialect: str = "keil",
481
+ controls: Iterable[str] = (),
482
+ ) -> Result:
483
+ """Reads and assembles a source file. See assemble() for the options."""
484
+ with open(path, "rb") as f:
485
+ source = f.read()
486
+ return assemble(
487
+ source,
488
+ file_name=path,
489
+ cpu=cpu,
490
+ origin=origin,
491
+ include_dirs=include_dirs,
492
+ sections=sections,
493
+ dialect=dialect,
494
+ controls=controls,
495
+ )
496
+
497
+
498
+ def assemble_many(
499
+ sources: Union[Mapping[PathLike, SourceText], Iterable[Tuple[PathLike, SourceText]]],
500
+ *,
501
+ cpu: Optional[str] = None,
502
+ origin: int = 0,
503
+ include_dirs: Iterable[PathLike] = (),
504
+ sections: Optional[Mapping[str, int]] = None,
505
+ dialect: str = "keil",
506
+ controls: Iterable[str] = (),
507
+ ) -> Result:
508
+ """Assembles several modules and links them into one program.
509
+
510
+ sources is a dict or a list of (file name, source text) pairs; each is
511
+ a module, as one .a66 file is for Keil's A166. As L166 would, the
512
+ modules are joined: an EXTERN symbol takes the value of the PUBLIC or
513
+ GLOBAL symbol of that name in another module, and sections with the
514
+ same name and class are combined. Code outside sections starts at the
515
+ origin in every module. See assemble() for the other options.
516
+
517
+ >>> result = assemble_many({
518
+ ... "main.a66": "EXTERN LIMIT:DATA16\\n"
519
+ ... "PROG SECTION CODE AT 0E000H\\n"
520
+ ... " MOV R4, #LIMIT\\n"
521
+ ... "PROG ENDS\\n"
522
+ ... " END\\n",
523
+ ... "consts.a66": "PUBLIC LIMIT\\nLIMIT EQU 1234H\\n END\\n",
524
+ ... })
525
+ >>> result.image().data.hex(), result.symbol("LIMIT").module
526
+ ('e6f43412', 'consts.a66')
527
+ """
528
+ items = list(sources.items()) if isinstance(sources, Mapping) else list(sources)
529
+ if not items:
530
+ raise ValueError("no sources")
531
+ names = [os.fsencode(os.fspath(name)) for name, _ in items]
532
+ texts = [_source_bytes(text) for _, text in items]
533
+ count = len(items)
534
+ c_texts = (ctypes.c_char_p * count)(*texts)
535
+ c_lengths = (ctypes.c_size_t * count)(*(len(t) for t in texts))
536
+ c_names = (ctypes.c_char_p * count)(*names)
537
+ return _run(
538
+ lambda opts: _lib.oc166_assemble_many(opts, count, c_texts, c_lengths, c_names),
539
+ cpu,
540
+ origin,
541
+ include_dirs,
542
+ sections,
543
+ dialect,
544
+ controls,
545
+ )
546
+
547
+
548
+ def assemble_files(
549
+ paths: Iterable[PathLike],
550
+ *,
551
+ cpu: Optional[str] = None,
552
+ origin: int = 0,
553
+ include_dirs: Iterable[PathLike] = (),
554
+ sections: Optional[Mapping[str, int]] = None,
555
+ dialect: str = "keil",
556
+ controls: Iterable[str] = (),
557
+ ) -> Result:
558
+ """Reads source files and assembles them as modules of one program.
559
+
560
+ See assemble_many().
561
+ """
562
+ items = []
563
+ for path in paths:
564
+ with open(path, "rb") as f:
565
+ items.append((path, f.read()))
566
+ return assemble_many(
567
+ items,
568
+ cpu=cpu,
569
+ origin=origin,
570
+ include_dirs=include_dirs,
571
+ sections=sections,
572
+ dialect=dialect,
573
+ controls=controls,
574
+ )
openc166/_native.py ADDED
@@ -0,0 +1,155 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Loads the openc166 shared library and declares its C functions.
3
+
4
+ The C interface is documented in include/openc166/openc166.h. The library
5
+ is looked for next to this file; the OPENC166_LIBRARY environment variable
6
+ names a different file (used to test a build tree).
7
+ """
8
+
9
+ import ctypes
10
+ import os
11
+ import sys
12
+
13
+ # OC166_API_VERSION this module was written for.
14
+ API_VERSION = 1
15
+
16
+
17
+ class Diagnostic(ctypes.Structure):
18
+ _fields_ = [
19
+ ("is_error", ctypes.c_int),
20
+ ("code", ctypes.c_char_p),
21
+ ("message", ctypes.c_char_p),
22
+ ("file", ctypes.c_char_p),
23
+ ("line", ctypes.c_int),
24
+ ("column", ctypes.c_int),
25
+ ("text", ctypes.c_char_p),
26
+ ]
27
+
28
+
29
+ class Chunk(ctypes.Structure):
30
+ _fields_ = [
31
+ ("address", ctypes.c_int64),
32
+ ("data", ctypes.POINTER(ctypes.c_uint8)),
33
+ ("size", ctypes.c_size_t),
34
+ ]
35
+
36
+
37
+ class Image(ctypes.Structure):
38
+ _fields_ = [
39
+ ("base", ctypes.c_int64),
40
+ ("data", ctypes.POINTER(ctypes.c_uint8)),
41
+ ("size", ctypes.c_size_t),
42
+ ]
43
+
44
+
45
+ class Symbol(ctypes.Structure):
46
+ _fields_ = [
47
+ ("name", ctypes.c_char_p),
48
+ ("kind", ctypes.c_char_p),
49
+ ("type", ctypes.c_char_p),
50
+ ("label_type", ctypes.c_char_p),
51
+ ("value", ctypes.c_int64),
52
+ ("bit", ctypes.c_int),
53
+ ("known", ctypes.c_int),
54
+ ("text", ctypes.c_char_p),
55
+ ("line", ctypes.c_int),
56
+ ]
57
+
58
+
59
+ class Section(ctypes.Structure):
60
+ _fields_ = [
61
+ ("name", ctypes.c_char_p),
62
+ ("type", ctypes.c_char_p),
63
+ ("align", ctypes.c_char_p),
64
+ ("address", ctypes.c_int64),
65
+ ("size", ctypes.c_int64),
66
+ ("placement", ctypes.c_char_p),
67
+ ]
68
+
69
+
70
+ def _library_paths():
71
+ override = os.environ.get("OPENC166_LIBRARY")
72
+ if override:
73
+ return [override]
74
+ here = os.path.dirname(os.path.abspath(__file__))
75
+ if sys.platform in ("win32", "cygwin"):
76
+ names = ["openc166.dll", "libopenc166.dll", "cygopenc166.dll"]
77
+ elif sys.platform == "darwin":
78
+ names = ["libopenc166.dylib"]
79
+ else:
80
+ names = ["libopenc166.so"]
81
+ return [os.path.join(here, name) for name in names]
82
+
83
+
84
+ def _load():
85
+ problems = []
86
+ for path in _library_paths():
87
+ if not os.path.exists(path):
88
+ problems.append(path + ": not found")
89
+ continue
90
+ try:
91
+ return ctypes.CDLL(path)
92
+ except OSError as e:
93
+ problems.append("{}: {}".format(path, e))
94
+ raise ImportError("cannot load the openc166 library:\n " + "\n ".join(problems))
95
+
96
+
97
+ lib = _load()
98
+
99
+
100
+ def _declare(name, restype, *argtypes):
101
+ f = getattr(lib, name)
102
+ f.restype = restype
103
+ f.argtypes = list(argtypes)
104
+
105
+
106
+ _p = ctypes.c_void_p
107
+ _size = ctypes.c_size_t
108
+ _int = ctypes.c_int
109
+ _str = ctypes.c_char_p
110
+
111
+ _declare("oc166_api_version", _int)
112
+ _declare("oc166_version", _str)
113
+ _declare("oc166_options_new", _p)
114
+ _declare("oc166_options_free", None, _p)
115
+ _declare("oc166_options_set_cpu", _int, _p, _str)
116
+ _declare("oc166_options_set_dialect", _int, _p, _str)
117
+ _declare("oc166_options_add_control", _int, _p, _str)
118
+ _declare("oc166_options_set_origin", _int, _p, ctypes.c_int64)
119
+ _declare("oc166_options_add_include_dir", _int, _p, _str)
120
+ _declare("oc166_options_set_section_address", _int, _p, _str, ctypes.c_int64)
121
+ _declare("oc166_assemble", _p, _p, _str, _size, _str)
122
+ _declare("oc166_result_free", None, _p)
123
+ _declare("oc166_result_error_count", _size, _p)
124
+ _declare("oc166_result_warning_count", _size, _p)
125
+ _declare("oc166_result_cpu", _str, _p)
126
+ _declare("oc166_result_diagnostic_count", _size, _p)
127
+ _declare("oc166_result_diagnostic", _int, _p, _size, ctypes.POINTER(Diagnostic))
128
+ _declare("oc166_result_chunk_count", _size, _p)
129
+ _declare("oc166_result_chunk", _int, _p, _size, ctypes.POINTER(Chunk))
130
+ _declare("oc166_result_image", _int, _p, _int, ctypes.c_int64, ctypes.c_uint8, ctypes.POINTER(Image))
131
+ _declare("oc166_result_intel_hex", _str, _p, _int)
132
+ _declare("oc166_result_srec", _str, _p, _int, _int)
133
+ _declare("oc166_result_last_error", _str, _p)
134
+ _declare("oc166_result_listing", _str, _p)
135
+ _declare("oc166_result_symbol_count", _size, _p)
136
+ _declare("oc166_result_symbol", _int, _p, _size, ctypes.POINTER(Symbol))
137
+ _declare("oc166_result_section_count", _size, _p)
138
+ _declare("oc166_result_section", _int, _p, _size, ctypes.POINTER(Section))
139
+ _declare("oc166_assemble_many", _p, _p, _size, ctypes.POINTER(_str), ctypes.POINTER(_size), ctypes.POINTER(_str))
140
+ _declare("oc166_result_module_count", _size, _p)
141
+ _declare("oc166_result_module", _str, _p, _size)
142
+ _declare("oc166_result_symbol_module", _int, _p, _size)
143
+ _declare("oc166_result_symbol_exported", _int, _p, _size)
144
+ _declare("oc166_result_section_class", _str, _p, _size)
145
+ _declare("oc166_result_section_group", _str, _p, _size)
146
+ _declare(
147
+ "oc166_result_section_bits", _int, _p, _size, ctypes.POINTER(_int), ctypes.POINTER(ctypes.c_int64)
148
+ )
149
+
150
+ if lib.oc166_api_version() != API_VERSION:
151
+ raise ImportError(
152
+ "the openc166 library has interface version {}, this module needs {}".format(
153
+ lib.oc166_api_version(), API_VERSION
154
+ )
155
+ )
openc166/openc166.dll ADDED
Binary file
openc166/py.typed ADDED
File without changes
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: openc166
3
+ Version: 0.1.15
4
+ Summary: Keil A166-compatible assembler for the Infineon C166/C167 microcontrollers
5
+ Keywords: c166,c167,st10,assembler,keil,a166,infineon,ecu
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Assembly
14
+ Classifier: Programming Language :: C++
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Assemblers
17
+ Classifier: Topic :: Software Development :: Embedded Systems
18
+ Classifier: Typing :: Typed
19
+ Project-URL: Homepage, https://github.com/Dante383/openc166
20
+ Project-URL: Issues, https://github.com/Dante383/openc166/issues
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+
24
+ # openc166
25
+
26
+ An open-source assembler for the Infineon C166 and C167 microcontroller
27
+ family (including ST10 parts), compatible with the Keil A166 assembler.
28
+
29
+ It turns Keil-style assembly source into a **raw binary image** at a fixed
30
+ address, or into an Intel HEX or Motorola S-record file. That suits small
31
+ programs loaded into RAM or flash and started directly, such as ECU
32
+ payloads, bootstrap loaders, and test code. No linker or object-file
33
+ converter is needed.
34
+
35
+ openc166 runs on Linux, Windows and macOS. It needs only a C++20 compiler and
36
+ CMake.
37
+
38
+ ## Status
39
+
40
+ Early development (version 0.x). What works today:
41
+
42
+ - **The complete C166 and C167 instruction set**, with all addressing modes.
43
+ Every instruction form is checked byte for byte against Keil A166 V5.38:
44
+ over 5,000 checks of code bytes, addresses and symbol values, all
45
+ identical.
46
+ - Keil expression syntax: operators, `HIGH`/`LOW`, `SOF`/`SEG`/`PAG`/`POF`,
47
+ `BOF`, `DATA3`/`DATA4`/`DATA8`/`DATA16`, character constants, bit
48
+ addresses such as `PSW.3` or `0FD00H.5`, and extended-SFR checks inside
49
+ `EXTR` sequences.
50
+ - Sections with an address (`SECTION CODE AT 0E000H`) and without one.
51
+ openc166 places sections without `AT` as Keil's L166 linker would, or at
52
+ addresses you give (`--section NAME=ADDR`), so code that leaves placement
53
+ to the linker can still be assembled straight to a binary. `DGROUP` and
54
+ `CGROUP` keep sections in one data page or code segment, as in L166.
55
+ `BIT` sections and `DBIT` hold bit variables, placed bit by bit as L166
56
+ places them.
57
+ - `PROC`/`ENDP`, `ORG`, `EVEN`, `EQU`/`SET`/`BIT`, `DB`/`DW`/`DD`,
58
+ `DS`/`DSB`/`DSW`/`DSD`, and `DPPTR`/`DSPTR`/`DBPTR`, including Keil's
59
+ automatic alignment of word data.
60
+ - Segmented mode (`$SEGMENTED`): `ASSUME`, `DPPn:` prefixes, and Keil's
61
+ rules for which data page register an address goes through.
62
+ - `DEFR`/`DEFA`/`DEFB`, the register-definition directives that register
63
+ files use, with Keil's rules for repeated definitions.
64
+ - A built-in `REG167.INC` register file, written from Infineon's manuals, so
65
+ `$INCLUDE(REG167.INC)` works without Keil installed.
66
+ - Keil's rules for label arithmetic, which decide between short and long
67
+ instruction forms.
68
+ - Generic `JMP` and `CALL`: like Keil, openc166 picks the shortest
69
+ instruction that reaches the target and shortens forward jumps over
70
+ several passes. Label types (`NEAR`, `FAR`, `WORD`, ...) and the `SHORT`
71
+ and type operators (`FAR x`, `WORD PTR x`, ...) work as in Keil.
72
+ - `$INCLUDE` and `$INCDIR`, with Keil's search order and listing format.
73
+ - Conditional assembly: `$SET`/`$RESET`, `$IF`/`$ELSEIF`/`$ELSE`/`$ENDIF`
74
+ and the `IF`/`ELSEIF`/`ELSE`/`ENDIF` statements, with Keil's quirks (only
75
+ bit 0 of a condition counts) and listing format.
76
+ - `LIT` text replacement (`PORT LIT 'R2'`), with Keil's rules for where
77
+ names are replaced.
78
+ - Keil's standard macros: `MACRO`/`ENDM` with parameters and `LOCAL`
79
+ labels, `REPT`, `IRP`, `IRPC`, `EXITM`, `NUL`, `&` and `%`, with Keil's
80
+ rules for splitting arguments. `$GEN`/`$NOGEN` choose whether the listing
81
+ shows expanded lines.
82
+ - The `$MOD167`, `$CASE`, `$SEGMENTED` and `$TABS` controls, with Keil's
83
+ rule that these come before the first statement. Listing-only controls
84
+ are accepted and ignored.
85
+ - Keil error and warning codes (A74, A111, ...), so messages line up with
86
+ the Keil manual.
87
+ - Raw binary, Intel HEX and Motorola S-record output. The HEX records match
88
+ Keil OH166's `H167` format.
89
+ - Several source files linked into one program, as L166 does: `EXTERN`,
90
+ `PUBLIC` and `GLOBAL` symbols, and sections of the same name combined
91
+ across files. Each file is assembled separately (a "module"), so a name
92
+ that is not `PUBLIC` stays private to its file.
93
+ - Register banks (`REGBANK`/`REGDEF`), placed in `0FC00H..0FDFFH` as L166
94
+ places them, and the `NAME`, `SSKDEF`, `PECDEF` and `TYPEDEC` directives.
95
+ - **TASKING sources** (`--dialect tasking`): the common part of TASKING's
96
+ m166 preprocessor (`@SET`, `@EVAL`, `@IF`/`@ELSE`/`@ENDI`, `@DEFINED`),
97
+ TASKING's controls and `STDNAMES` register files, and the places where
98
+ its assembler differs from Keil's. Infineon's MiniMon monitor and flash
99
+ drivers, which gkflasher's ECU code comes from, rebuild byte for byte
100
+ from their TASKING sources. See
101
+ [docs/tasking.md](https://github.com/Dante383/openc166/blob/main/docs/tasking.md).
102
+ - A Python package and a C API, for tools that assemble code themselves.
103
+
104
+ Not done yet:
105
+
106
+ - Keil's MPL macro language (`%DEFINE` and the like); put `$NOMPL` at the
107
+ top of the file to use `%` as text
108
+ - OMF-166 object files and libraries
109
+ - TASKING's m166 macros with parameters (`@DEFINE`), loops and string
110
+ functions
111
+
112
+ See [docs/compatibility.md](https://github.com/Dante383/openc166/blob/main/docs/compatibility.md) for exact behaviour and
113
+ the few places where openc166 deliberately differs from Keil.
114
+
115
+ ## Building
116
+
117
+ ```sh
118
+ cmake -S . -B build
119
+ cmake --build build
120
+ ctest --test-dir build
121
+ ```
122
+
123
+ The program is `build/cli/openc166`. On Windows with Visual Studio it is
124
+ under `build\cli\Debug\` or `build\cli\Release\`. The shared library with
125
+ the C API is built in `build/src/`.
126
+
127
+ ## Usage
128
+
129
+ ```sh
130
+ openc166 payload.a66 # writes payload.bin
131
+ openc166 payload.a66 -o out.bin -l out.lst # choose names, add a listing
132
+ openc166 payload.a66 -o payload.hex # Intel HEX
133
+ openc166 payload.a66 -o payload.s19 # S-records (S1)
134
+ openc166 main.a66 lib.a66 -o payload.bin # link two source files
135
+ ```
136
+
137
+ Several source files are linked into one program, the way Keil's L166
138
+ linker joins the object files of A166:
139
+
140
+ - An `EXTERN` name in one file takes the value of the `PUBLIC` or `GLOBAL`
141
+ name in another. A name that no file exports is an error.
142
+ - Sections with the same name and class are joined into one: the parts of
143
+ a `PUBLIC` or `GLOBAL` section follow one another, the parts of a
144
+ `COMMON` section lie on top of each other.
145
+ - Without `-o`, the output is named after the first file. With `-l`, the
146
+ listing has one part per file and a symbol table that names the file
147
+ each symbol comes from.
148
+ - Code outside sections starts at `--origin` in every file, so normally
149
+ each file puts its code in sections.
150
+
151
+ `$INCLUDE` looks in each file's own directory.
152
+ [docs/compatibility.md](https://github.com/Dante383/openc166/blob/main/docs/compatibility.md#linking-several-files)
153
+ has the exact rules.
154
+
155
+ The output format follows the extension of the `-o` file: `.hex`, `.h86`
156
+ and `.ihx` give Intel HEX; `.srec`, `.s19`, `.s28`, `.s37`, `.mot` and
157
+ `.sx` give S-records; anything else gives a raw binary. `--format` chooses
158
+ it explicitly.
159
+
160
+ A raw binary starts at the lowest address used. For example, code in
161
+ `SECTION CODE AT 0E000H` produces a file whose first byte belongs at
162
+ `0E000H`. Gaps between pieces of code are filled. HEX and S-record files
163
+ carry the address of every record, so they hold only the bytes used.
164
+
165
+ | Option | Meaning |
166
+ |---|---|
167
+ | `-f, --format FMT` | `bin`, `hex` or `srec` (default: from the output extension) |
168
+ | `--origin ADDR` | address of code outside sections, and the lowest address for sections without `AT` (default 0) |
169
+ | `--section NAME=ADDR` | put section `NAME`, which has no `AT`, at `ADDR` (may be repeated) |
170
+ | `--base ADDR` | binary: address of the first byte of the file |
171
+ | `--fill BYTE` | binary: value of gaps between pieces of code (default `0FFh`) |
172
+ | `--record-size N` | HEX and S-records: data bytes per record (default 16) |
173
+ | `--cpu c166\|c167` | instruction set (`$MOD167` in the source also selects C167) |
174
+ | `-I DIR` | also search `DIR` for `$INCLUDE` files (may be repeated) |
175
+ | `--dialect D` | `keil` (default) or `tasking`, for TASKING a166 sources |
176
+ | `-c, --control TEXT` | controls read before the sources, e.g. `-c "CASE STDNAMES(reg167.def)"` (may be repeated) |
177
+
178
+ Sections without `AT` go in order of first appearance to the first free,
179
+ suitably aligned place from the origin on, as L166 places them by default.
180
+ `--section` overrides that for chosen sections, like L166's `SECTIONS`
181
+ control. The listing ends with a table of where each section went.
182
+ [examples/sections.a66](https://github.com/Dante383/openc166/blob/main/examples/sections.a66) shows both.
183
+
184
+ Intel HEX files use extended linear address records (type 04) for addresses
185
+ above 64 KB, like OH166 with its `H167` option. S-records use S1, S2 or S3
186
+ records by extension (`.s19`, `.s28`, `.s37`); otherwise the smallest kind
187
+ that holds every address.
188
+
189
+ The exit status is 0 on success, 1 on assembly errors (no output is
190
+ written), and 2 for usage or file errors. Messages use the format
191
+ `file:line:column: error A74: ...`.
192
+
193
+ [examples/payload.a66](https://github.com/Dante383/openc166/blob/main/examples/payload.a66) is a short, commented example.
194
+ [examples/mtos/](https://github.com/Dante383/openc166/tree/main/examples/mtos) holds real ECU payloads for Siemens SIMK43
195
+ ECUs, taken from gkflasher.
196
+
197
+ ## Python
198
+
199
+ ```sh
200
+ pip install openc166 # or "pip install ." in a checkout
201
+ ```
202
+
203
+ Wheels are published for Linux, Windows and macOS on x86 and ARM; other
204
+ systems build from source, which needs CMake and a C++20 compiler. To get
205
+ just the `openc166` command, `pipx install openc166` works too.
206
+
207
+ ```python
208
+ import openc166
209
+
210
+ result = openc166.assemble("""
211
+ $MOD167
212
+ PROG SECTION CODE AT 0E000H
213
+ MOV R4, #0BEEFH
214
+ RET
215
+ PROG ENDS
216
+ END
217
+ """)
218
+ image = result.image() # raises openc166.AssemblyError if there are errors
219
+ image.base # 0xE000, the address of image.data[0]
220
+ image.data # b'\xe6\xf4\xef\xbe\xcb\x00'
221
+ ```
222
+
223
+ - `openc166.assemble(source, *, file_name=None, cpu=None, origin=0, include_dirs=(), sections=None, dialect="keil", controls=())`
224
+ assembles a `str` or `bytes`. `sections` gives addresses for sections
225
+ without `AT`, e.g. `{"VARS": 0xF600}`. `dialect="tasking"` reads TASKING
226
+ source; `controls` is a list of controls read before the source, as with
227
+ `-c`. `openc166.assemble_file(path, ...)`
228
+ reads a file; `$INCLUDE` then also searches the file's directory.
229
+ - The result has `ok`, `diagnostics` (each with `severity`, `code`,
230
+ `message`, `file`, `line`, `column`; `str()` gives the usual one-line
231
+ message), `errors`, `warnings`, `chunks` (address and bytes of each piece
232
+ of code), `symbols` (a dict of name to `Symbol`), `symbol(name)` (lookup
233
+ ignoring case), `sections` (a dict of name to `Section`: `type`, `align`,
234
+ `address`, `size`, and `placement`, which is `"at"`, `"given"` or
235
+ `"located"`), `listing` and `cpu`.
236
+ - `openc166.assemble_many({"main.a66": text1, "lib.a66": text2}, ...)`
237
+ links several sources into one program, like several files on the
238
+ command line; `openc166.assemble_files([path1, path2], ...)` reads them
239
+ from files. Each `Symbol` then says which file defines it (`module`) and
240
+ whether it is `PUBLIC` or `GLOBAL` (`exported`). `result.modules` lists
241
+ the file names; `result.symbols` holds one symbol per name (the exported
242
+ one if there is one), `result.all_symbols` all of them. Each `Section`
243
+ has its `class_name` and `group`.
244
+ - `result.image(base=None, fill=0xFF)` lays all code out as one block, like
245
+ the command-line tool.
246
+ - `result.intel_hex(record_size=16)` and
247
+ `result.srec(record_size=16, address_size=None)` return Intel HEX and
248
+ S-record text (`address_size` 2, 3 or 4 picks S1, S2 or S3 records).
249
+
250
+ The package needs Python 3.9 or newer. It also installs the `openc166`
251
+ command. It loads the library through `ctypes`, so one wheel works with
252
+ every Python version.
253
+
254
+ ## C API
255
+
256
+ [include/openc166/openc166.h](https://github.com/Dante383/openc166/blob/main/include/openc166/openc166.h) declares a plain C
257
+ interface, exported by the shared library (`libopenc166.so`,
258
+ `libopenc166.dylib` or `openc166.dll`). Any language that can call C can
259
+ use it: `oc166_assemble` assembles one source, `oc166_assemble_many`
260
+ links several. The header documents every function; the Python package is
261
+ an example of a complete binding.
262
+
263
+ ## Testing
264
+
265
+ - `ctest --test-dir build` runs the unit tests, the C API test and the
266
+ Python package tests, and assembles the examples, checking every byte.
267
+ - `tests/acceptance/minimon.py` rebuilds Infineon's MiniMon from its
268
+ TASKING sources and compares the result with its HEX files. MiniMon is
269
+ not included; set `MINIMON_DIR` to its directory (the one that holds
270
+ `MCPROG` and `Driver`) and `ctest` runs it too.
271
+ - `tests/diff/` contains the differential test against Keil A166. Keil is
272
+ **not** included. If you have your own copy, run:
273
+
274
+ ```sh
275
+ KEIL_A166=/path/to/A166.EXE python3 tests/diff/keil_diff.py tests/diff/cases/*.a66
276
+ ```
277
+
278
+ It compares every line: address, code bytes, warnings and errors, and
279
+ the final value of every symbol. Add `--image` to also link Keil's output
280
+ with L166 and OH166 and compare the complete memory image, and `--codes`
281
+ to list lines where both assemblers report an error but with different
282
+ codes. Lines marked `; DIVERGES:` are deliberate differences (see
283
+ [docs/compatibility.md](https://github.com/Dante383/openc166/blob/main/docs/compatibility.md)) and are skipped.
284
+
285
+ Given a directory instead of a file, it treats each `.a66` file in it as
286
+ one module, links Keil's objects with L166, and compares the linked
287
+ image and the linker's errors with openc166's:
288
+
289
+ ```sh
290
+ KEIL_A166=/path/to/A166.EXE python3 tests/diff/keil_diff.py tests/diff/cases/link/*/
291
+ ```
292
+
293
+ On Linux and macOS, Keil runs under Wine. To generate test lines that
294
+ cover the whole instruction set, run
295
+ `tests/diff/gen_sweep.py build/generated/openc166/isa.json c167 > sweep.a66`.
296
+
297
+ The test inputs were written independently for this project. Keil's output
298
+ is used only for the local comparison and is never stored in the
299
+ repository.
300
+
301
+ ## License
302
+
303
+ MIT; see [LICENSE](https://github.com/Dante383/openc166/blob/main/LICENSE). The bundled [doctest](https://github.com/Dante383/openc166/tree/main/third_party/doctest)
304
+ test framework is also MIT.
305
+
306
+ openc166 is not affiliated with Arm/Keil or Infineon. "Keil", "A166",
307
+ "C166", "C167" and "TASKING" are trademarks of their respective owners. The
308
+ names are used only to describe compatibility.
@@ -0,0 +1,9 @@
1
+ openc166/__init__.py,sha256=in9eCKDEZSj-yuYzx51NSBGqeZYxlOnBTlrw5KVsy1I,21856
2
+ openc166/_native.py,sha256=O1bnYN2NNHmGaerUSNNHsaukP2ThBZNsEnQNLuARAA8,5247
3
+ openc166/openc166.dll,sha256=7WE_pDbPpLOpSiXbSAydik2Tc4XNSpe77lHYnGbN7Bk,716288
4
+ openc166/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ openc166-0.1.15.data/scripts/openc166.exe,sha256=_TQ1DB4folswcmzX5ENhDgZWyRvGahp9UwcLfmRjUsQ,740864
6
+ openc166-0.1.15.dist-info/METADATA,sha256=Nv6tPorlUm59VHpUwPs0jR5YLnf4x0BEDKShmPwWEn4,15056
7
+ openc166-0.1.15.dist-info/WHEEL,sha256=IyQ3IRe-zd6q-qUW9K5lLyXhxJMDLZxhe1OuvrM-g78,98
8
+ openc166-0.1.15.dist-info/licenses/LICENSE,sha256=e2iZ1haqvx1-l9jomaSPVVH23WXENsT65NNPq-yL9Kk,1091
9
+ openc166-0.1.15.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: scikit-build-core 1.1.2
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win32
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Piotr Zdolski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.