Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/python3 

2# -*- coding: utf-8 -*- 

3# 

4# This module contains the following tools for the CAPASM assembler: 

5# - static methods to support regression testing 

6# - capglo utilty to create global symbol class files from text files 

7# - caplif utility to put assembled lex files into an import lif image file 

8# 

9# (c) 2020 Joachim Siebold 

10# 

11# This program is free software; you can redistribute it and/or 

12# modify it under the terms of the GNU General Public License 

13# as published by the Free Software Foundation; either version 2 

14# of the License, or (at your option) any later version. 

15# 

16# This program is distributed in the hope that it will be useful, 

17# but WITHOUT ANY WARRANTY; without even the implied warranty of 

18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19# GNU General Public License for more details. 

20# 

21# You should have received a copy of the GNU General Public License 

22# along with this program; if not, write to the Free Software 

23# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 

24# 

25#-------------------------------------------------------------------------- 

26# 

27# Changelog 

28# 18.05.2020 jsi: 

29# - start change log 

30# 19.05.2020 jsi 

31# - added clsSymClassGenerator class 

32# - added capglo entry point 

33# - removed main entry point 

34# 22.05.2020 jsi 

35# - raise custom exception on fatal error 

36# 29.05.2020 jsi 

37# - much refactoring 

38# - regression test utilities rewritten 

39# - removed mklex75 entry point 

40# - added caplex and caplif entry points and the corresponding classes 

41# 29.06.2020 jsi 

42# - added caprom tool 

43# 04.07.2020 jsi 

44# - fix rom number checking 

45# 26.07.2020 jsi 

46# - catch I/O errors in caplglo generate method 

47# 27.07.2020 jsi 

48# - added capconv tool 

49# 

50import sys, argparse,os, codecs,re,contextlib 

51from pathlib import Path 

52from itertools import groupby 

53from .capcommon import capasmError, clsLineScanner, parseFunc, CAPASM_VERSION,clsDateTime 

54 

55# 

56# silently remove files, continue if they do not exist 

57# 

58def silentRemove(*args): 

59 for fileName in args: 

60 with contextlib.suppress(FileNotFoundError): 

61 os.remove(fileName) 

62 return False 

63# 

64# AsmSourceFileConverter class ----------------------------------------- 

65# This class converts a tokenized Series 80 assembler source file  

66# which was for example extracted from a lif image file with "hpdir -extract" 

67# or "lifget -r" to an ascii file. The input file format (HP-85 or 

68# HP-87 style is auto detected 

69# 

70class clsAsmSourceFileConverter(object): 

71 

72 def __init__(self): 

73 super().__init__() 

74 self.__outFile__=None 

75# 

76# output comment 

77# 

78 def printComment(self,d): 

79 self.__outFile__.write("! ") 

80 for c in d: 

81 self.__outFile__.write(chr(c)) 

82# 

83# output label or blanks 

84#  

85 def printLabel(self,d,labelLength): 

86 for i in range(0,labelLength): 

87 if d is None: 

88 self.__outFile__.write(" ") 

89 else: 

90 self.__outFile__.write(chr(d[i])) 

91 self.__outFile__.write(" ") 

92# 

93# process opcode, operands and trailing comment 

94# 

95 def printOpcodeAndOperand(self,d): 

96 operand="" 

97 for i in range(0,3): 

98 operand+=chr(d[i]) 

99 d=d[3:] 

100 if operand in ["SBB","SBM","CMB","CMM","ANM","ADB","ADM", \ 

101 "PUB","PUM","POB","POM","LDB","LDM","STB","STM"]: 

102 if chr(d[0]) in ["D","I"]: 

103 operand+=chr(d[0]) 

104 d=d[1:] 

105 else: 

106 operand+=" " 

107 else: 

108 operand+=" " 

109 i=4 

110 self.__outFile__.write(operand) 

111 self.__outFile__.write(" ") 

112 for c in d: 

113 if c== 0xFE: 

114 for j in range(i,20): 

115 self.__outFile__.write(" ") 

116 self.__outFile__.write(" ! ") 

117 continue 

118 if c==0xFF: 

119 continue 

120 self.__outFile__.write(chr(c)) 

121 i+=1 

122 

123# 

124 def convert(self,inputFileName,outputFileName): 

125 print("") 

126 print("Converting file "+inputFileName+" to "+outputFileName) 

127 try: 

128 infile=open(inputFileName,"rb") 

129 asmBytes=infile.read() 

130 infile.close() 

131 except OSError: 

132 raise capasmError("cannot open/read input file") 

133 

134 try: 

135 self.__outFile__=open(outputFileName,"w") 

136 except OSError: 

137 raise capasmError("cannot create output file") 

138# 

139# detect file type and skip header 

140# 

141 k=len(asmBytes) 

142 count=0 

143 if asmBytes[6]==0x20: 

144 is85=True 

145 i=24 

146 elif asmBytes[6]==0x10 or asmBytes[6]==0x50: 

147 is85=False 

148 i=32 

149 else: 

150 raise capasmError("Illegal input file") 

151# 

152# process records 

153#  

154 try: 

155 while i < k: 

156# 

157# line number, HP87 has one more digit 

158# 

159 if not is85: 

160 labelLength=8 

161 h=asmBytes[i] 

162 if h==10: 

163 break 

164 lineNumber="{:1x}{:02x}{:02x} ".format(asmBytes[i],\ 

165 asmBytes[i+1],asmBytes[i+2]) 

166 i+=1 

167 else: 

168 labelLength=6 

169 if asmBytes[i]==0x99 and asmBytes[i+1]==0xA9: 

170 break 

171 lineNumber="{:02x}{:02x} ".format(asmBytes[i+1],asmBytes[i]) 

172 i+=2 

173 count+=1 

174# 

175# store tokenized assembler statement in d 

176# 

177 l=asmBytes[i] 

178 i+=1 

179 d=bytearray(0) 

180 self.__outFile__.write(lineNumber) 

181 for j in range (0,l): 

182 d.append(asmBytes[i]) 

183 i+=1 

184 if d[-1]!=0x0E: 

185 raise capasmError("Illegal End byte detected") 

186 d=d[0:-1] 

187# 

188# comment line 

189# 

190 if d[0]==0xFE: 

191 self.printComment(d[1:-1]) 

192 self.__outFile__.write("\n") 

193 continue 

194# 

195# label 

196# 

197 if d[0]==0xFF: 

198 d=d[1:] 

199 self.printLabel(d,labelLength) 

200 d=d[labelLength:] 

201 else: 

202 self.printLabel(None,labelLength) 

203# 

204# opcode, operands and trailing comment 

205# 

206 self.printOpcodeAndOperand(d) 

207 self.__outFile__.write("\n") 

208 self.__outFile__.close() 

209 except OSError: 

210 raise capasmError("cannot write to output file") 

211 print("{:d} assembler source lines written to file {:s}".format(count,\ 

212 outputFileName)) 

213 return False 

214 

215# 

216# SymbolFileConverter class --------------------------------------------- 

217# This class converts a Series 80 binary global symbols file  

218# which was for example extracted from a lif image file with "hpdir -extract" 

219# or "lifget -r" to an ascii file 

220# 

221class clsSymbolFileConverter(object): 

222 

223 def __init__(self): 

224 super().__init__() 

225# 

226# conversion method 

227# The binary input file has a fixed record length of 256 bytes. Each 

228# global symbol has an entry of 11 bytes length. An entry may span 

229# a record boundary. A zero length record marks end of file. 

230# 

231 def convert(self,inputFileName,outputFileName): 

232 

233 tdict= {0: "EQU", 1: "DAD", 2:"DAD"} 

234 print("") 

235 print("Converting file "+inputFileName+" to "+outputFileName) 

236 try: 

237 infile=open(inputFileName,"rb") 

238 gloBytes=infile.read() 

239 infile.close() 

240 except OSError: 

241 raise capasmError("cannot open/read input file") 

242 

243 try: 

244 outfile=open(outputFileName,"w") 

245 except OSError: 

246 raise capasmError("cannot create output file") 

247 

248 i=0 

249 count=0 

250 try: 

251 while True: 

252 header=gloBytes[i] 

253# 

254# check header byte 0xDF: normal entry, 0xCF: entry spans two records 

255# 

256 if not header in [0xDF, 0xCF]: 

257 raise capasmError("illegal input file") 

258 i+=1 

259# 

260# get length, if zero length then exit loop 

261# 

262 length=gloBytes[i] 

263 if (length==0): 

264 break 

265 i+=1 

266# 

267# extract bytes of entry 

268# 

269 d=bytearray(0) 

270# 

271# if the entry spans two records, skip the header at the 

272# beginning of the second line 

273# 

274 for j in range (0,length): 

275 d.append(gloBytes[i]) 

276 i+=1 

277 if i % 256 == 0: 

278 i+=3 

279# 

280# get type of symbol 

281# 

282 typ=gloBytes[i] 

283# 

284# extract symbol name 

285# 

286 symName="" 

287 ci=length-3 

288 while d[ci]!=0: 

289 symName+=chr(d[ci]) 

290 ci-=1 

291# 

292# get symbol value 

293# 

294 symValue=d[-2]*256+d[-1] 

295 d=None 

296 outfile.write("{:8s} {:3s} {:o}\n".format(symName,tdict[typ],\ 

297 symValue)) 

298 i+=1 

299 count+=1 

300 outfile.close() 

301 except OSError: 

302 raise capasmError("cannot write to output file") 

303 print("{:d} symbols generated in file {:s}".format(count,outputFileName)) 

304 return False 

305# 

306# 

307# SymCassGenerator class ------------------------------------------------- 

308# 

309# An object of this class reads a global symbol file which must match 

310# the syntax rules of the CAPASM assembler. Only comments, DAD (or ADDR) and EQU 

311# statements are allowed in this file. 

312# 

313# The object generates a Python script which a static class "globalSymbols" 

314# to access global symbols for a specific machine. At the moment CAPASM  

315# supports the script file names globals75.py, globals85.py, globals87.py.  

316# The -m option of the assembler controls which file is used for the assembly. 

317# 

318# The program checks for duplicate global symbol definitions which exist 

319# in the file globals75.txt. At the moment duplicate definitions overwrite  

320# an existing definition. The reason for duplicateentries is under  

321# investigation. 

322# 

323class clsSymClassGenerator(object): 

324 

325 SYM_DAD=0 

326 SYM_EQU=1 

327 

328 def __init__(self): 

329 super().__init__() 

330 

331# 

332# generate method: 

333# convert file with name inputFileName to a python file with a global 

334# symbol class definition which has the name outputFileName 

335# 

336# Returns: 

337# False: everything o.k. 

338# True: errors or duplicates 

339# Raises capasmError on i/o error 

340# 

341 def generate(self,inputFileName,outputFileName,labelLen=8,style="capasm"): 

342 

343 if style== "ncas": 

344 labelLen=32 

345 lineScanner=clsLineScanner("*",";","'`^"+'"') 

346 parseFunc.DELIMITER="'"+'"' 

347 parseFunc.LABELMATCHSTRING=\ 

348 "[A-Za-z][A-Za-z0-9_$\+\-\.#/?\(\!\&)=:<>\|@*^]{0," 

349 else: 

350 lineScanner=clsLineScanner("!","!",'"') 

351 parseFunc.DELIMITER='"' 

352 parseFunc.LABELMATCHSTRING="[(^0-9)(\x20-\x7A|\|)][\x20-\x7A|\|]{0," 

353 symDict= { } 

354 duplicates=0 

355 errors=0 

356 hasErrors=False 

357 print("") 

358 print("Processing file "+inputFileName) 

359 try: 

360 infile=codecs.open(inputFileName,"r",encoding="ISO-8859-1",\ 

361 errors="ignore") 

362 except OSError: 

363 raise capasmError("cannot open input file") 

364 

365 try: 

366 outfile=open(outputFileName,"w") 

367 except OSError: 

368 raise capasmError("cannot create output file") 

369# 

370# Write global symbol class definition 

371# 

372 try: 

373 outfile.write("#!/usr/bin/python3\n# -*- coding: utf-8 -*-\n") 

374 outfile.write("#\n# Global symbols from file "+inputFileName+"\n") 

375 outfile.write("# Autogenerated file, do not modify!\n") 

376 outfile.write("#\n") 

377 outfile.write("class globalSymbols():\n") 

378 outfile.write("\n") 

379 outfile.write(" symbols= {\n") 

380# 

381# Process lines 

382# 

383 lineCount=0 

384 while True: 

385 line=infile.readline() 

386 if not line: 

387 break 

388 line=line.strip("\r\n") 

389 lineCount+=1 

390# 

391# Scan line, we get a list of token: 

392# - lineNumber (from source code file, if any, ignored here) 

393# - label 

394# - opcode 

395# - list of operands which should consist only of the symbol value  

396# 

397 scannedLine=lineScanner.scanLine(line) 

398 lineNumber=str(lineCount) 

399# 

400# Empty line 

401# 

402 if scannedLine[1]==None: 

403 continue 

404# 

405# Comment 

406# 

407 symbolName=scannedLine[1].string 

408 if symbolName[0]=="*" or symbolName=="!": 

409 continue 

410# 

411# Check symbol name 

412# 

413 if parseFunc.parseLabel(symbolName,labelLen) is None: 

414 print("Line: "+lineNumber+": "+line) 

415 print("illegal symbol") 

416 errors+=1 

417 continue 

418# 

419# Check opcode, only "EQU" and "DAD" are allowed 

420# 

421 if scannedLine[2] is None: 

422 print("Line: "+lineNumber+": "+line) 

423 print("missing opcode") 

424 errors+=1 

425 continue 

426 opCode= scannedLine[2].string 

427 if opCode== "EQU": 

428 opTyp=clsSymClassGenerator.SYM_EQU 

429 elif opCode == "DAD" or opCode == "ADDR": 

430 opTyp=clsSymClassGenerator.SYM_DAD 

431 else: 

432 print("Line: "+lineNumber+": "+line) 

433 print("illegal opcode") 

434 errors+=1 

435 continue 

436# 

437# Check value which must be a valid number 

438# 

439 if len(scannedLine[3])!=1: 

440 print("Line: "+lineNumber+": "+line) 

441 print("illegal label value") 

442 errors+=1 

443 continue 

444 value=scannedLine[3][0].string 

445 intValue=parseFunc.parseNumber(value) 

446 if intValue==None: 

447 print("Line: "+lineNumber+": "+line) 

448 print("illegal label value") 

449 errors+=1 

450 continue 

451 if intValue > 0xFFFF: 

452 print("Line: "+lineNumber+": "+line) 

453 print("illegal label value") 

454 errors+=1 

455 continue 

456# 

457# Check and print duplicates 

458# 

459 if symbolName in symDict.keys(): 

460 print("Line: "+lineNumber+": "+line) 

461 ret=symDict[symbolName] 

462 print("symbol redefined, first definition was at line: "+ \ 

463 ret[0]+" opcode: "+ret[1]+" value: "+ret[2]) 

464 outfile.write(' "'+symbolName+'" : ['+str(opTyp)+ \ 

465 ","+str(intValue)+"],\n") 

466 duplicates+=1 

467 else: 

468 symDict[symbolName]=[lineNumber,opCode,value] 

469 outfile.write(' "'+symbolName+'" : ['+str(opTyp)+ \ 

470 ","+str(intValue)+"],\n") 

471# 

472# All input line processed, write access method 

473# 

474 infile.close() 

475 outfile.write(" }\n") 

476 outfile.write(" @staticmethod\n") 

477 outfile.write(" def get(name):\n") 

478 outfile.write(" if name[0]=='=':\n") 

479 outfile.write(" name=name[1:]\n") 

480 outfile.write(" if name in globalSymbols.symbols.keys():\n") 

481 outfile.write(" return globalSymbols.symbols[name]\n") 

482 outfile.write(" else:\n") 

483 outfile.write(" return None\n") 

484 outfile.write("\n") 

485 outfile.close() 

486 except OSError: 

487 raise capasmError("I/O Error while converting global symbols file") 

488 print("Errors {:d}, duplicate entries {:d} ".format(errors,duplicates)) 

489# 

490# return error condition 

491# 

492 hasErrors=(errors!=0) or (duplicates!=0) 

493 return hasErrors 

494# 

495# Static classes for the comparision of files 

496# Returns: [l1, l2, diffs] 

497# l1: length of file 1 

498# l2: length of file 2 

499# diffs: list of differences, each member is a list of [pos, num] where 

500# pos: byte position of difference 

501# num: number of consecutive bytes that are different 

502# 

503# return None on file i/o error 

504# 

505# 

506class fileDiff(object): 

507 

508 @staticmethod 

509 def compareBin(fileName1, fileName2): 

510 try: 

511 name=fileName1 

512 f=open(name,"rb") 

513 f1=f.read() 

514 f.close() 

515 name=fileName2 

516 f=open(name,"rb") 

517 f2=f.read() 

518 f.close() 

519 l1=len(f1) 

520 l2=len(f2) 

521 diffs = [(next(g), len(list(g))+1) 

522 for k, g in groupby(range(min(len(f1), len(f2))), \ 

523 key=lambda i: f1[i] != f2[i]) if k] 

524 except (OSError,FileNotFoundError): 

525 print("Can not read binary file: "+name) 

526 return None 

527 return l1,l2,diffs 

528 

529 @staticmethod 

530 def compareAsc(fileName1, fileName2): 

531 try: 

532 name=fileName1 

533 f=open(name,"r") 

534 f1=[] 

535 while True: 

536 l=f.readline() 

537 if not l: 

538 break 

539 f1.append(l.strip("\r\n")) 

540 f.close() 

541 name=fileName2 

542 f=open(name,"r") 

543 f2=[] 

544 while True: 

545 l=f.readline() 

546 if not l: 

547 break 

548 f2.append(l.strip("\r\n")) 

549 f.close() 

550 l1=len(f1) 

551 l2=len(f2) 

552 diffs = [(next(g), len(list(g))+1) 

553 for k, g in groupby(range(min(len(f1), len(f2))), \ 

554 key=lambda i: f1[i] != f2[i]) if k] 

555 except (OSError,FileNotFoundError): 

556 print("Can not read ascii file: "+name) 

557 return None 

558 return l1,l2,diffs 

559 

560 @staticmethod 

561 def testBinFile(fileName1,fileName2): 

562 ret=fileDiff.compareBin(fileName1,fileName2) 

563 if ret is None: 

564 return True 

565 ret=fileDiff.postProc(fileName1, fileName2,ret) 

566 return ret 

567 

568 @staticmethod 

569 def testAscFile(fileName1, fileName2): 

570 ret=fileDiff.compareAsc(fileName1,fileName2) 

571 if ret is None: 

572 return True 

573 ret=fileDiff.postProc(fileName1, fileName2,ret) 

574 return ret 

575 

576 @staticmethod 

577 def postProc(fileName1,fileName2,ret): 

578 notIdentical=False 

579 l1,l2,diffs=ret 

580 if l1 !=l2: 

581 notIdentical=True 

582 print(fileName1+"!="+fileName2+", file length differs: "\ 

583 +str(l1)+"!="+str(l2)) 

584 

585 if len(diffs)!=0: 

586 notIdentical= True 

587 print(fileName1+"!="+fileName2+": compare failed at pos: ",end="") 

588 print (diffs) 

589 return notIdentical 

590 

591# 

592# LIF item classes ---------------------------------------------------------- 

593# 

594# This classes generate various items we need to create the structure of 

595# LIF files and volumes 

596# 

597# Base class for all LIF items 

598# 

599class clsBinaryItem(object): 

600 def __init__(self): 

601 super().__init__() 

602 self.__objectCode__= None 

603# 

604# get bytes of item 

605# 

606 def getBytes(self): 

607 return self.__objectCode__ 

608 

609# 

610# get byte length of item 

611# 

612 def getLen(self): 

613 return len(self.__objectCode__) 

614# 

615# LIF item: file content ------------------------------------------------ 

616# 

617class clsObjectFile(clsBinaryItem): 

618 

619 def __init__(self,filename): 

620 super().__init__() 

621 try: 

622 f=open(filename,"rb") 

623 self.__objectCode__=f.read() 

624 f.close() 

625 except OSError: 

626 raise capasmError("Can not read binary object file") 

627# 

628# LIF item: volume header, always 512 bytes long ------------------------ 

629# 

630class clsVolumeHeader(clsBinaryItem): 

631 

632 def __init__(self): 

633 super().__init__() 

634 self.__dt__=clsDateTime() 

635 

636 def create(self,length,machine,isRegressionTest): 

637# 

638# for the HP75 we create a standard Volume header with 2 sectors 

639# 

640 if machine=="75": 

641 self.__objectCode__=bytearray(512) 

642 self.__objectCode__[11]=2 # start of directory 

643# 

644# for Series80 we create a 256 byte sector, because 

645# the HP-85 emulator requires that for upload files 

646 else: 

647 self.__objectCode__=bytearray(256) 

648 self.__objectCode__[11]=2 # start of directory 

649 self.__objectCode__[0]=0x80 # LIF volume identifier 

650 labelName="HFSLIF" # Volumen label, always HFSLIF 

651 i=2 

652 for c in labelName: 

653 self.__objectCode__[i]=ord(c) 

654 i+=1 

655 self.__objectCode__[12]=0x10 # HP-3000 LIF identifier  

656 self.__objectCode__[19]=0x01 # one sector in directory 

657 self.__objectCode__[21]=0x01 # LIF version number 

658# 

659# Volume size 

660# 

661 self.__objectCode__[27]=0x01 # tracks per surface 

662 self.__objectCode__[31]=0x01 # number of surfaces 

663 length+=+512+255 # calculate length of volume in blocks 

664 self.__objectCode__[32]=0 

665 self.__objectCode__[33]= (length >> 24) & 0xFF 

666 self.__objectCode__[34]= (length >> 16) & 0xFF 

667 self.__objectCode__[35]= (length >> 8) & 0xFF 

668# 

669# Date and Time 

670# 

671 if not isRegressionTest: 

672 self.__objectCode__[36]=self.__dt__.bcdYear 

673 self.__objectCode__[37]=self.__dt__.bcdMonth 

674 self.__objectCode__[38]=self.__dt__.bcdDay 

675 self.__objectCode__[39]=self.__dt__.bcdHour 

676 self.__objectCode__[40]=self.__dt__.bcdMin 

677 self.__objectCode__[41]=self.__dt__.bcdSec 

678 

679# 

680# LIF item: directory entry, always 32 bytes long ------------------------- 

681# 

682class clsDirEntry(clsBinaryItem): 

683 

684 def __init__(self): 

685 super().__init__() 

686 self.__dt__=clsDateTime() 

687 

688 def createFileEntry(self,length,machine,filename,lexOnly,isRegressionTest): 

689 origLength=length 

690 self.__objectCode__=bytearray(32) 

691 i=0 

692 for c in filename: # LIF file name 

693 self.__objectCode__[i]=ord(c) 

694 i+=1 

695 self.__objectCode__[8]=0x20 # pad file name 

696 self.__objectCode__[9]=0x20 

697 if machine =="75": # file type for HP-75: LEX75 

698 self.__objectCode__[10]=0xE0 

699 self.__objectCode__[11]=0x89 

700 else: # file type for Series 80: BPGM binary P. 

701 self.__objectCode__[10]=0xE0 

702 self.__objectCode__[11]=0x08 

703 

704 if not lexOnly: 

705 self.__objectCode__[15]=3 # start at sector 3 

706 

707 length+=255 # calculate length in blocks 

708 self.__objectCode__[16]=0 

709 self.__objectCode__[17]= (length >> 24) & 0xFF 

710 self.__objectCode__[18]= (length >> 16) & 0xFF 

711 self.__objectCode__[19]= (length >> 8) & 0xFF 

712 if not isRegressionTest: 

713 self.__objectCode__[20]=self.__dt__.bcdYear 

714 self.__objectCode__[21]=self.__dt__.bcdMonth 

715 self.__objectCode__[22]=self.__dt__.bcdDay 

716 self.__objectCode__[23]=self.__dt__.bcdHour 

717 self.__objectCode__[24]=self.__dt__.bcdMin 

718 self.__objectCode__[25]=self.__dt__.bcdSec 

719 self.__objectCode__[26]=0x80 # Implementing bytes 

720 self.__objectCode__[27]=0x01 

721 if machine =="75": # HP-75 Password 

722 self.__objectCode__[28]=0x20 

723 self.__objectCode__[29]=0x20 

724 self.__objectCode__[30]=0x20 

725 self.__objectCode__[31]=0x20 

726 else: # Series 80 file and block length 

727 self.__objectCode__[28]=origLength & 0xFF 

728 self.__objectCode__[29]=(origLength >> 8) & 0xFF 

729 self.__objectCode__[30]=0x00 

730 self.__objectCode__[31]=0x01 

731 

732# 

733# this creates an empty directory entry, file type is 0xFFFD 

734# 

735 def createNullEntry(self): 

736 self.__objectCode__=bytearray(32) 

737 self.__objectCode__[10]=0xFF 

738 self.__objectCode__[11]=0xFF 

739 

740# 

741# LIF item: HP-75 RAM file system header -------------------------------- 

742# 

743# This header must be prepended to the file content. 

744# Series 80 computers do not need that because they start with a NAM header 

745# 

746class clsFileHeader(clsBinaryItem): 

747 

748 def __init__(self): 

749 super().__init__() 

750 

751 def create(self,length,filename): 

752 self.__objectCode__=bytearray(18) 

753 length=length+18 # length of file including header 

754 low= length & 0xFF 

755 high= length >> 8 

756 self.__objectCode__[2]=low 

757 self.__objectCode__[3]=high 

758 self.__objectCode__[4]=0x8D # access bits, fake 

759 self.__objectCode__[5]=0x4C # "L" 

760 self.__objectCode__[6]=0xC7 # date and time faked 

761 self.__objectCode__[7]=0xBA 

762 self.__objectCode__[8]=0x7F 

763 self.__objectCode__[9]=0xA0 

764 i=10 

765 for c in filename: 

766 self.__objectCode__[i]=ord(c) 

767 i+=1 

768# 

769# Check the filename which is used in the LIF directory entry 

770# 

771def makeLifFileName(fName,machine): 

772 fName=fName.upper() 

773 if len(fName)> 8: 

774 raise capasmError("LIF filename: "+fName+" exceeds 8 characters") 

775 if machine=="75": 

776 match=re.fullmatch("[A-Z][A-Z0-9]*",fName) 

777 else: 

778 match=re.fullmatch("[A-Z][A-Z0-9_]*",fName) # allow underscores here 

779 if not match: 

780 raise capasmError("illegal LIF file name: "+fName) 

781 fName=fName.ljust(8) 

782 return fName 

783# 

784# LIF LEX and Image creator class ---------------------------------------- 

785# 

786class clsLifCreator(object): 

787 

788 def __init__(self): 

789 super().__init__() 

790# 

791# create LIF LEX files (lexOnly= True) from binary input file 

792# - or - 

793# create LIF import image (lexOnly=False) from binary input file 

794# 

795# Returns: 

796# True: everything o.k. 

797# Raises capasmError on i/o error or if the LIF directory file name is 

798# illegal 

799# 

800 def create(self,binFileName,machine,outputFileName="",lifFileName="", 

801 lexOnly=False): 

802# 

803# check if we run in regression test mode 

804# 

805 isRegressionTest=False 

806 if os.getenv("CAPASMREGRESSIONTEST"): 

807 isRegressionTest=True 

808# 

809# build name of file image or lex file if not specified 

810# 

811 if outputFileName=="": 

812 if lexOnly: 

813 outputFileName=Path(binFileName).with_suffix(".lex").name 

814 else: 

815 outputFileName=Path(binFileName).with_suffix(".dat").name 

816# 

817# build the file name for the LIF directory entry, if not specified 

818# 

819 if lifFileName=="": 

820 if machine == "75": 

821 fname=Path(binFileName).stem 

822 else: 

823 fname="WS_FILE" 

824 else: 

825 fname=lifFileName 

826 lifFileName=makeLifFileName(fname,machine) 

827# 

828# read object file into memory 

829# 

830 objectFile=clsObjectFile(binFileName) 

831# 

832# create the RAM file header for the HP-75 

833# 

834 fileLength=objectFile.getLen() 

835 if machine == "75": 

836 fileHeader=clsFileHeader() 

837 fileHeader.create(fileLength,lifFileName) 

838 fileLength+=18 

839# 

840# create directory Entry 

841# 

842 dirEntry=clsDirEntry() 

843 dirEntry.createFileEntry(fileLength,machine,lifFileName, \ 

844 lexOnly,isRegressionTest) 

845# 

846# create LIF volume header 

847# 

848 volHeader=clsVolumeHeader() 

849 volHeader.create(fileLength, machine, isRegressionTest) 

850# 

851# write LIF image or LEX file 

852# 

853 try: 

854 imgFile=open(outputFileName,"wb") 

855# 

856# if LEX file: write only the directory header 

857# 

858 if lexOnly: 

859 imgFile.write(dirEntry.getBytes()) 

860 else: 

861# 

862# if LIF image file, write volume header and directory 

863# 

864 imgFile.write(volHeader.getBytes()) 

865 imgFile.write(dirEntry.getBytes()) 

866 dirEntry.createNullEntry() 

867 for i in range(0,7): 

868 imgFile.write(dirEntry.getBytes()) 

869# 

870# HP-75 only: write RAM file header 

871# 

872 if machine == "75": 

873 imgFile.write(fileHeader.getBytes()) 

874# 

875# write file content 

876# 

877 imgFile.write(objectFile.getBytes()) 

878# 

879# fill up remaining bytes in sector 

880#  

881 rem=256-(fileLength % 256) 

882 imgFile.write(bytearray(rem)) 

883 imgFile.close() 

884 except OSError: 

885 raise capasmError("cannot write lif image file") 

886 

887 if lexOnly: 

888 print("LEX file "+outputFileName+" created") 

889 else: 

890 print("LIF image file "+outputFileName+" created which contains: " \ 

891 + lifFileName+" (LEX"+machine+")") 

892# 

893# we return always false here, because all error conditions raise 

894# an exception 

895# 

896 return False 

897# 

898# ROM file creator class ---------------------------------------- 

899# 

900class clsRomCreator(object): 

901 

902 def __init__(self): 

903 super().__init__() 

904# 

905# create LIF import image (lexOnly=False) from binary input file 

906# 

907# Returns: 

908# True: everything o.k. 

909# Raises capasmError on i/o error or if the ROM size is too small 

910# 

911 def create(self,binFileName,romFileName="",romSize=2): 

912# 

913# check if we run in regression test mode 

914# 

915 isRegressionTest=False 

916 if os.getenv("CAPASMREGRESSIONTEST"): 

917 isRegressionTest=True 

918# 

919# build name of file image or lex file if not specified 

920# 

921 if romFileName=="": 

922 romFileName=Path(binFileName).with_suffix(".rom").name 

923# 

924# read object file into memory, check rom number and rom size 

925# 

926 romSize=romSize*1024 

927 rom75=False 

928 objectFile=clsObjectFile(binFileName) 

929 code=bytearray(objectFile.getBytes()) 

930# 

931# HP-75 has always ROM number 0xE3 and the HP-85 style complementary no 

932# 

933 if code[0]==0xE3 and code[1]==0x1C: 

934 rom75=True 

935 if romSize < len(code)+2: 

936 raise capasmError("ROM size too small") 

937 print("creating HP-75 ROM file ",romFileName) 

938# 

939# determine if we have a HP-85 or HP-87 file 

940# 

941 else: 

942 romNo=code[0] 

943 checkRomNo= ~ romNo &0xFF 

944 if checkRomNo== code[1]: 

945 print("creating HP-85 ROM file ",romFileName) 

946 elif checkRomNo+1 == code[1]: 

947 print("creating HP-87 ROM file ",romFileName) 

948 else: 

949 raise capasmError("Invalid ROM number") 

950 if romSize < len(code)+4: 

951 raise capasmError("ROM size too small") 

952# 

953# fill code to length of rom 

954# 

955 fill=romSize - len(code) 

956 for i in range(0,fill): 

957 code.append(0) 

958# 

959# HP-75 check sum 

960# 

961 if rom75: 

962 c=0 

963 i=0 

964 while(i<len(code)): 

965 c+=code[i] 

966 while c> 255: 

967 c+=1 

968 c-= 256 

969 i+=1 

970 cInv= ~c &0xFF 

971 code[-1]=cInv 

972 else: 

973# 

974# determine secondary checksum, thanks to Philippe (hp80series@groups.io) 

975# 

976 t=0 

977 i=0 

978 while(i< len(code)-4): 

979 c1=code[i] 

980 i+=1 

981 c2=code[i] 

982 i+=1 

983 w = (c1 & 0xff) + ((c2 & 0xff)<<8) 

984 for j in range (0,16): 

985 r26 = t & 0xff 

986 r27 = t>>8 

987 r45 = r27 

988 r27 = (r27<<4) & 0xff 

989 r26 = ((r26<<1) & 0xff) | (w & 1) 

990 w = w>>1 

991 r45 = r45 ^ r26 

992 r45 = r45 ^ r27 

993 if (r45 & 1): 

994 r45 = (r45 + 0x80) & 0xff 

995 t = ((t<<1) & 0xffff) | (r45 >>7) 

996 code[-4]=(t & 0xFF) 

997 code[-3]=((t>>8) & 0xFF) 

998# 

999# determine primary checksum, thanks to Philippe (hp80series@groups.io) 

1000# 

1001 t=0 

1002 i=0 

1003 while(i< len(code)-2): 

1004 c1=code[i] 

1005 i+=1 

1006 c2=code[i] 

1007 i+=1 

1008 t = t + (c1 & 0xff) + ((c2 & 0xff)<<8) 

1009 s = ((t>>16) + (t & 0xffff)) & 0xffff 

1010 t = s>>8 

1011 s = s & 0xff 

1012 code[-2]=(255-s) 

1013 code[-1]=(255-t) 

1014# 

1015# write rom file 

1016# 

1017 try: 

1018 romFile=open(romFileName,"wb") 

1019 romFile.write(code) 

1020 romFile.close() 

1021 except OSError: 

1022 raise capasmError("cannot write rom file") 

1023 return False 

1024# 

1025# entry point caplif ------------------------------------------------------ 

1026# put assembled binary file to an import LIF image file 

1027# 

1028def caplif(): # pragma: no cover 

1029# 

1030# command line arguments processing 

1031# 

1032 argparser=argparse.ArgumentParser(description=\ 

1033 "Utility to put an assembled binary file into an import LIF image file",\ 

1034 epilog="See https://github.com/bug400/capasm for details. "+CAPASM_VERSION) 

1035 argparser.add_argument("binfile",help="binary object code file (required)") 

1036 argparser.add_argument("-m","--machine",choices=['75','85','87'], \ 

1037 help="Machine type (default=85)",default='85') 

1038 argparser.add_argument("-l","--lifimagefilename", help=\ 

1039 "name of the Upload LIF image file (default: objectfile name with suffix .dat)",\ 

1040 default="") 

1041 argparser.add_argument("-f","--filename", \ 

1042 help="name of the LIF directory entry (default: WS_FILE for Series 80, deduced from object file name for HP-75)",\ 

1043 default="") 

1044 args= argparser.parse_args() 

1045 

1046 l=clsLifCreator() 

1047 try: 

1048 l.create(args.binfile,args.machine,args.lifimagefilename,args.filename, 

1049 False) 

1050 except capasmError as e: 

1051 print(e.msg+" -- program terminated") 

1052 sys.exit(1) 

1053# 

1054# entry point caplex ------------------------------------------------------ 

1055# convert assembled binary file to a LIF file of type LEX75 or BPGM80 

1056# 

1057def caplex(): # pragma: no cover 

1058# 

1059# command line arguments processing 

1060# 

1061 argparser=argparse.ArgumentParser(description=\ 

1062 "Utility to convert an assembled binary file into a LIF file of type LEX75 or BPGM80",\ 

1063 epilog="See https://github.com/bug400/capasm for details. "+CAPASM_VERSION) 

1064 argparser.add_argument("binfile",help="binary object code file (required)") 

1065 argparser.add_argument("-m","--machine",choices=['75','85','87'], \ 

1066 help="Machine type (default:85)",default='85') 

1067 argparser.add_argument("-l","--lexfilename",help=\ 

1068 "name of the LIF output file (default: objectfile name with suffix .lex)",\ 

1069 default="") 

1070 argparser.add_argument("-f","--filename", help=\ 

1071 "file name in the LIF header (default: deduced from objectfilename)",\ 

1072 default="") 

1073 args= argparser.parse_args() 

1074 

1075 l=clsLifCreator() 

1076 try: 

1077 l.create(args.binfile,args.machine,args.lexfilename,args.filename, 

1078 True) 

1079 except capasmError as e: 

1080 print(e.msg+" -- program terminated") 

1081 sys.exit(1) 

1082# 

1083# entry point capglo ------------------------------------------------------- 

1084# convert a list of input files to a python files with a global 

1085# symbol class definition which have the name of the input file with 

1086# the suffix ".py" 

1087# 

1088def capglo(): # pragma: no cover 

1089 

1090 p=argparse.ArgumentParser(description=\ 

1091 "Utility to convert global HP-85/HP-87/HP-75 symbol files for the capasm assembler",\ 

1092 epilog="See https://github.com/bug400/capasm for details. "+CAPASM_VERSION) 

1093 p.add_argument('inputfiles',nargs='+',help="list of gobal symbol assembler files (one argument required)") 

1094 p.add_argument("-s","--style",help="Source style (capasm/ncas), default=capasm",default="capasm",choices=["capasm","ncas"]) 

1095 args=p.parse_args() 

1096 

1097 gen=clsSymClassGenerator() 

1098 hasErrors=False 

1099 labelLen=6 

1100 style=args.style 

1101 for inputFileName in args.inputfiles: 

1102 outputFileName=Path(inputFileName).with_suffix(".py").name 

1103 try: 

1104 hasErrors!=gen.generate(inputFileName,outputFileName,labelLen,style) 

1105 except capasmError as e: 

1106 print(e.msg+" -- program terminated") 

1107 hasErrors=True 

1108 break 

1109 if hasErrors: 

1110 sys.exit(1) 

1111# 

1112# entry point caprom ------------------------------------------------------- 

1113# 

1114# convert an assembled binary file to a Series 80 ROM file with checksums 

1115# 

1116 

1117def caprom(): # pragma: no cover 

1118 

1119 argparser=argparse.ArgumentParser(description=\ 

1120 "Utility to convert an assembled binary file to a Series 80 ROM file",\ 

1121 epilog="See https://github.com/bug400/capasm for details. "+CAPASM_VERSION) 

1122 argparser.add_argument("binfile",help="binary object code file (required)") 

1123 argparser.add_argument("-r","--romfilename",help=\ 

1124 "name of the LIF output file (default: objectfile name with suffix .lex)",\ 

1125 default="") 

1126 argparser.add_argument("-s","--romsize",choices=[2,4,8,16,32],type=int, \ 

1127 help="ROM size in KB (default:2)",default=2) 

1128 args= argparser.parse_args() 

1129 

1130 l=clsRomCreator() 

1131 try: 

1132 l.create(args.binfile,args.romfilename,args.romsize) 

1133 except capasmError as e: 

1134 print(e.msg+" -- program terminated") 

1135 sys.exit(1) 

1136# 

1137# entry point capconv ------------------------------------------------------- 

1138# convert a binary Series 80 global symbols file to an ascii file with 

1139# DAD or EQU symbol definitions 

1140# 

1141def capconv(): # pragma: no cover 

1142 

1143 p=argparse.ArgumentParser(description=\ 

1144 "Utility to convert binary HP-85/HP-87/HP-75 symbol files to ascii files",\ 

1145 epilog="See https://github.com/bug400/capasm for details. "+CAPASM_VERSION) 

1146 p.add_argument('inputfiles',nargs='+',help="list of gobal symbol assembler files (one argument required)") 

1147 p.add_argument("-t","--type",required=True,help="what to convert", \ 

1148 choices=["asm","glo"]) 

1149 args=p.parse_args() 

1150 

1151 if args.type=="glo": 

1152 conv=clsSymbolFileConverter() 

1153 suffix=".glo" 

1154 else: 

1155 conv=clsAsmSourceFileConverter() 

1156 suffix=".asm" 

1157 hasErrors=False 

1158 for inputFileName in args.inputfiles: 

1159 outputFileName=Path(inputFileName).with_suffix(suffix).name 

1160 try: 

1161 hasErrors!=conv.convert(inputFileName,outputFileName) 

1162 except capasmError as e: 

1163 print(e.msg+" -- program terminated") 

1164 hasErrors=True 

1165 break 

1166 if hasErrors: 

1167 sys.exit(1) 

1168#