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 datetime import datetime 

54from .assembler import clsLineScanner, parseFunc, capasmError, CAPASM_VERSION 

55 

56# 

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

58# 

59def silentRemove(*args): 

60 for fileName in args: 

61 with contextlib.suppress(FileNotFoundError): 

62 os.remove(fileName) 

63 return False 

64# 

65# AsmSourceFileConverter class ----------------------------------------- 

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

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

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

69# HP-87 style is auto detected 

70# 

71class clsAsmSourceFileConverter(object): 

72 

73 def __init__(self): 

74 super().__init__() 

75 self.__outFile__=None 

76# 

77# output comment 

78# 

79 def printComment(self,d): 

80 self.__outFile__.write("! ") 

81 for c in d: 

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

83# 

84# output label or blanks 

85#  

86 def printLabel(self,d,labelLength): 

87 for i in range(0,labelLength): 

88 if d is None: 

89 self.__outFile__.write(" ") 

90 else: 

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

92 self.__outFile__.write(" ") 

93# 

94# process opcode, operands and trailing comment 

95# 

96 def printOpcodeAndOperand(self,d): 

97 operand="" 

98 for i in range(0,3): 

99 operand+=chr(d[i]) 

100 d=d[3:] 

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

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

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

104 operand+=chr(d[0]) 

105 d=d[1:] 

106 else: 

107 operand+=" " 

108 else: 

109 operand+=" " 

110 i=4 

111 self.__outFile__.write(operand) 

112 self.__outFile__.write(" ") 

113 for c in d: 

114 if c== 0xFE: 

115 for j in range(i,20): 

116 self.__outFile__.write(" ") 

117 self.__outFile__.write(" ! ") 

118 continue 

119 if c==0xFF: 

120 continue 

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

122 i+=1 

123 

124# 

125 def convert(self,inputFileName,outputFileName): 

126 print("") 

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

128 try: 

129 infile=open(inputFileName,"rb") 

130 asmBytes=infile.read() 

131 infile.close() 

132 except OSError: 

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

134 

135 try: 

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

137 except OSError: 

138 raise capasmError("cannot create output file") 

139# 

140# detect file type and skip header 

141# 

142 k=len(asmBytes) 

143 count=0 

144 if asmBytes[6]==0x20: 

145 is85=True 

146 i=24 

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

148 is85=False 

149 i=32 

150 else: 

151 raise capasmError("Illegal input file") 

152# 

153# process records 

154#  

155 try: 

156 while i < k: 

157# 

158# line number, HP87 has one more digit 

159# 

160 if not is85: 

161 labelLength=8 

162 h=asmBytes[i] 

163 if h==10: 

164 break 

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

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

167 i+=1 

168 else: 

169 labelLength=6 

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

171 break 

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

173 i+=2 

174 count+=1 

175# 

176# store tokenized assembler statement in d 

177# 

178 l=asmBytes[i] 

179 i+=1 

180 d=bytearray(0) 

181 self.__outFile__.write(lineNumber) 

182 for j in range (0,l): 

183 d.append(asmBytes[i]) 

184 i+=1 

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

186 raise capasmError("Illegal End byte detected") 

187 d=d[0:-1] 

188# 

189# comment line 

190# 

191 if d[0]==0xFE: 

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

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

194 continue 

195# 

196# label 

197# 

198 if d[0]==0xFF: 

199 d=d[1:] 

200 self.printLabel(d,labelLength) 

201 d=d[labelLength:] 

202 else: 

203 self.printLabel(None,labelLength) 

204# 

205# opcode, operands and trailing comment 

206# 

207 self.printOpcodeAndOperand(d) 

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

209 self.__outFile__.close() 

210 except OSError: 

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

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

213 outputFileName)) 

214 return False 

215 

216# 

217# SymbolFileConverter class --------------------------------------------- 

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

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

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

221# 

222class clsSymbolFileConverter(object): 

223 

224 def __init__(self): 

225 super().__init__() 

226# 

227# conversion method 

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

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

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

231# 

232 def convert(self,inputFileName,outputFileName): 

233 

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

235 print("") 

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

237 try: 

238 infile=open(inputFileName,"rb") 

239 gloBytes=infile.read() 

240 infile.close() 

241 except OSError: 

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

243 

244 try: 

245 outfile=open(outputFileName,"w") 

246 except OSError: 

247 raise capasmError("cannot create output file") 

248 

249 i=0 

250 count=0 

251 try: 

252 while True: 

253 header=gloBytes[i] 

254# 

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

256# 

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

258 raise capasmError("illegal input file") 

259 i+=1 

260# 

261# get length, if zero length then exit loop 

262# 

263 length=gloBytes[i] 

264 if (length==0): 

265 break 

266 i+=1 

267# 

268# extract bytes of entry 

269# 

270 d=bytearray(0) 

271# 

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

273# beginning of the second line 

274# 

275 for j in range (0,length): 

276 d.append(gloBytes[i]) 

277 i+=1 

278 if i % 256 == 0: 

279 i+=3 

280# 

281# get type of symbol 

282# 

283 typ=gloBytes[i] 

284# 

285# extract symbol name 

286# 

287 symName="" 

288 ci=length-3 

289 while d[ci]!=0: 

290 symName+=chr(d[ci]) 

291 ci-=1 

292# 

293# get symbol value 

294# 

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

296 d=None 

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

298 symValue)) 

299 i+=1 

300 count+=1 

301 outfile.close() 

302 except OSError: 

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

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

305 return False 

306# 

307# 

308# SymCassGenerator class ------------------------------------------------- 

309# 

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

311# the syntax rules of the CAPASM assembler. Only comments, DEF and EQU 

312# statements are allowed in this file. 

313# 

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

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

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

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

318# 

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

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

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

322# investigation. 

323# 

324class clsSymClassGenerator(object): 

325 

326 SYM_DAD=0 

327 SYM_EQU=1 

328 

329 def __init__(self): 

330 super().__init__() 

331 

332# 

333# generate method: 

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

335# symbol class definition which has the name outputFileName 

336# 

337# Returns: 

338# False: everything o.k. 

339# True: errors or duplicates 

340# Raises capasmError on i/o error 

341# 

342 def generate(self,inputFileName,outputFileName,labelLen=8): 

343 

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

345 symDict= { } 

346 duplicates=0 

347 errors=0 

348 hasErrors=False 

349 print("") 

350 print("Processing file "+inputFileName) 

351 try: 

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

353 errors="ignore") 

354 except OSError: 

355 raise capasmError("cannot open input file") 

356 

357 try: 

358 outfile=open(outputFileName,"w") 

359 except OSError: 

360 raise capasmError("cannot create output file") 

361# 

362# Write global symbol class definition 

363# 

364 try: 

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

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

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

368 outfile.write("#\n") 

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

370 outfile.write("\n") 

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

372# 

373# Process lines 

374# 

375 lineCount=0 

376 while True: 

377 line=infile.readline() 

378 if not line: 

379 break 

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

381 lineCount+=1 

382# 

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

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

385# - label 

386# - opcode 

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

388# 

389 scannedLine=lineScanner.scanLine(line) 

390 lineNumber=str(lineCount) 

391# 

392# Empty line 

393# 

394 if scannedLine[1]==None: 

395 continue 

396# 

397# Comment 

398# 

399 symbolName=scannedLine[1].string 

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

401 continue 

402# 

403# Check symbol name 

404# 

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

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

407 print("illegal symbol") 

408 errors+=1 

409 continue 

410# 

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

412# 

413 if scannedLine[2] is None: 

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

415 print("missing opcode") 

416 errors+=1 

417 continue 

418 opCode= scannedLine[2].string 

419 if opCode== "EQU": 

420 opTyp=clsSymClassGenerator.SYM_EQU 

421 elif opCode == "DAD": 

422 opTyp=clsSymClassGenerator.SYM_DAD 

423 else: 

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

425 print("illegal opcode") 

426 errors+=1 

427 continue 

428# 

429# Check value which must be a valid number 

430# 

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

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

433 print("illegal label value") 

434 errors+=1 

435 continue 

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

437 intValue=parseFunc.parseNumber(value) 

438 if intValue==None: 

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

440 print("illegal label value") 

441 errors+=1 

442 continue 

443 if intValue > 0xFFFF: 

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

445 print("illegal label value") 

446 errors+=1 

447 continue 

448# 

449# Check and print duplicates 

450# 

451 if symbolName in symDict.keys(): 

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

453 ret=symDict[symbolName] 

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

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

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

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

458 duplicates+=1 

459 else: 

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

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

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

463# 

464# All input line processed, write access method 

465# 

466 infile.close() 

467 outfile.write(" }\n") 

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

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

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

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

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

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

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

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

476 outfile.write("\n") 

477 outfile.close() 

478 except OSError: 

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

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

481# 

482# return error condition 

483# 

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

485 return hasErrors 

486# 

487# Static classes for the comparision of files 

488# Returns: [l1, l2, diffs] 

489# l1: length of file 1 

490# l2: length of file 2 

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

492# pos: byte position of difference 

493# num: number of consecutive bytes that are different 

494# 

495# return None on file i/o error 

496# 

497# 

498class fileDiff(object): 

499 

500 @staticmethod 

501 def compareBin(fileName1, fileName2): 

502 try: 

503 name=fileName1 

504 f=open(name,"rb") 

505 f1=f.read() 

506 f.close() 

507 name=fileName2 

508 f=open(name,"rb") 

509 f2=f.read() 

510 f.close() 

511 l1=len(f1) 

512 l2=len(f2) 

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

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

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

516 except (OSError,FileNotFoundError): 

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

518 return None 

519 return l1,l2,diffs 

520 

521 @staticmethod 

522 def compareAsc(fileName1, fileName2): 

523 try: 

524 name=fileName1 

525 f=open(name,"r") 

526 f1=[] 

527 while True: 

528 l=f.readline() 

529 if not l: 

530 break 

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

532 f.close() 

533 name=fileName2 

534 f=open(name,"r") 

535 f2=[] 

536 while True: 

537 l=f.readline() 

538 if not l: 

539 break 

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

541 f.close() 

542 l1=len(f1) 

543 l2=len(f2) 

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

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

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

547 except (OSError,FileNotFoundError): 

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

549 return None 

550 return l1,l2,diffs 

551 

552 @staticmethod 

553 def testBinFile(fileName1,fileName2): 

554 ret=fileDiff.compareBin(fileName1,fileName2) 

555 if ret is None: 

556 return True 

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

558 return ret 

559 

560 @staticmethod 

561 def testAscFile(fileName1, fileName2): 

562 ret=fileDiff.compareAsc(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 postProc(fileName1,fileName2,ret): 

570 notIdentical=False 

571 l1,l2,diffs=ret 

572 if l1 !=l2: 

573 notIdentical=True 

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

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

576 

577 if len(diffs)!=0: 

578 notIdentical= True 

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

580 print (diffs) 

581 return notIdentical 

582 

583# 

584# Class to generates Date/Time as BCD --------------------------------------- 

585# 

586class clsDateTime(object): 

587 

588 def __init__(self): 

589 super().__init__() 

590 now=datetime.now() 

591 self.bcdYear= self.intToBcd(now.date().year-2000) 

592 self.bcdMonth= self.intToBcd(now.date().month) 

593 self.bcdDay= self.intToBcd(now.date().day) 

594 self.bcdHour= self.intToBcd(now.time().hour) 

595 self.bcdMin= self.intToBcd(now.time().minute) 

596 self.bcdSec= self.intToBcd(now.time().second) 

597 

598 def intToBcd(self,value): 

599 return (((int(value/10)%10)<<4)+(value%10)) 

600# 

601# LIF item classes ---------------------------------------------------------- 

602# 

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

604# LIF files and volumes 

605# 

606# Base class for all LIF items 

607# 

608class clsBinaryItem(object): 

609 def __init__(self): 

610 super().__init__() 

611 self.__objectCode__= None 

612# 

613# get bytes of item 

614# 

615 def getBytes(self): 

616 return self.__objectCode__ 

617 

618# 

619# get byte length of item 

620# 

621 def getLen(self): 

622 return len(self.__objectCode__) 

623# 

624# LIF item: file content ------------------------------------------------ 

625# 

626class clsObjectFile(clsBinaryItem): 

627 

628 def __init__(self,filename): 

629 super().__init__() 

630 try: 

631 f=open(filename,"rb") 

632 self.__objectCode__=f.read() 

633 f.close() 

634 except OSError: 

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

636# 

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

638# 

639class clsVolumeHeader(clsBinaryItem): 

640 

641 def __init__(self): 

642 super().__init__() 

643 self.__dt__=clsDateTime() 

644 

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

646# 

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

648# 

649 if machine=="75": 

650 self.__objectCode__=bytearray(512) 

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

652# 

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

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

655 else: 

656 self.__objectCode__=bytearray(256) 

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

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

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

660 i=2 

661 for c in labelName: 

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

663 i+=1 

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

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

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

667# 

668# Volume size 

669# 

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

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

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

673 self.__objectCode__[32]=0 

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

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

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

677# 

678# Date and Time 

679# 

680 if not isRegressionTest: 

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

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

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

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

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

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

687 

688# 

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

690# 

691class clsDirEntry(clsBinaryItem): 

692 

693 def __init__(self): 

694 super().__init__() 

695 self.__dt__=clsDateTime() 

696 

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

698 origLength=length 

699 self.__objectCode__=bytearray(32) 

700 i=0 

701 for c in filename: # LIF file name 

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

703 i+=1 

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

705 self.__objectCode__[9]=0x20 

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

707 self.__objectCode__[10]=0xE0 

708 self.__objectCode__[11]=0x89 

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

710 self.__objectCode__[10]=0xE0 

711 self.__objectCode__[11]=0x08 

712 

713 if not lexOnly: 

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

715 

716 length+=255 # calculate length in blocks 

717 self.__objectCode__[16]=0 

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

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

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

721 if not isRegressionTest: 

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

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

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

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

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

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

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

729 self.__objectCode__[27]=0x01 

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

731 self.__objectCode__[28]=0x20 

732 self.__objectCode__[29]=0x20 

733 self.__objectCode__[30]=0x20 

734 self.__objectCode__[31]=0x20 

735 else: # Series 80 file and block length 

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

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

738 self.__objectCode__[30]=0x00 

739 self.__objectCode__[31]=0x01 

740 

741# 

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

743# 

744 def createNullEntry(self): 

745 self.__objectCode__=bytearray(32) 

746 self.__objectCode__[10]=0xFF 

747 self.__objectCode__[11]=0xFF 

748 

749# 

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

751# 

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

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

754# 

755class clsFileHeader(clsBinaryItem): 

756 

757 def __init__(self): 

758 super().__init__() 

759 

760 def create(self,length,filename): 

761 self.__objectCode__=bytearray(18) 

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

763 low= length & 0xFF 

764 high= length >> 8 

765 self.__objectCode__[2]=low 

766 self.__objectCode__[3]=high 

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

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

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

770 self.__objectCode__[7]=0xBA 

771 self.__objectCode__[8]=0x7F 

772 self.__objectCode__[9]=0xA0 

773 i=10 

774 for c in filename: 

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

776 i+=1 

777# 

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

779# 

780def makeLifFileName(fName,machine): 

781 fName=fName.upper() 

782 if len(fName)> 8: 

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

784 if machine=="75": 

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

786 else: 

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

788 if not match: 

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

790 fName=fName.ljust(8) 

791 return fName 

792# 

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

794# 

795class clsLifCreator(object): 

796 

797 def __init__(self): 

798 super().__init__() 

799# 

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

801# - or - 

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

803# 

804# Returns: 

805# True: everything o.k. 

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

807# illegal 

808# 

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

810 lexOnly=False): 

811# 

812# check if we run in regression test mode 

813# 

814 isRegressionTest=False 

815 if os.getenv("CAPASMREGRESSIONTEST"): 

816 isRegressionTest=True 

817# 

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

819# 

820 if outputFileName=="": 

821 if lexOnly: 

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

823 else: 

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

825# 

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

827# 

828 if lifFileName=="": 

829 if machine == "75": 

830 fname=Path(binFileName).stem 

831 else: 

832 fname="WS_FILE" 

833 else: 

834 fname=lifFileName 

835 lifFileName=makeLifFileName(fname,machine) 

836# 

837# read object file into memory 

838# 

839 objectFile=clsObjectFile(binFileName) 

840# 

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

842# 

843 fileLength=objectFile.getLen() 

844 if machine == "75": 

845 fileHeader=clsFileHeader() 

846 fileHeader.create(fileLength,lifFileName) 

847 fileLength+=18 

848# 

849# create directory Entry 

850# 

851 dirEntry=clsDirEntry() 

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

853 lexOnly,isRegressionTest) 

854# 

855# create LIF volume header 

856# 

857 volHeader=clsVolumeHeader() 

858 volHeader.create(fileLength, machine, isRegressionTest) 

859# 

860# write LIF image or LEX file 

861# 

862 try: 

863 imgFile=open(outputFileName,"wb") 

864# 

865# if LEX file: write only the directory header 

866# 

867 if lexOnly: 

868 imgFile.write(dirEntry.getBytes()) 

869 else: 

870# 

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

872# 

873 imgFile.write(volHeader.getBytes()) 

874 imgFile.write(dirEntry.getBytes()) 

875 dirEntry.createNullEntry() 

876 for i in range(0,7): 

877 imgFile.write(dirEntry.getBytes()) 

878# 

879# HP-75 only: write RAM file header 

880# 

881 if machine == "75": 

882 imgFile.write(fileHeader.getBytes()) 

883# 

884# write file content 

885# 

886 imgFile.write(objectFile.getBytes()) 

887# 

888# fill up remaining bytes in sector 

889#  

890 rem=256-(fileLength % 256) 

891 imgFile.write(bytearray(rem)) 

892 imgFile.close() 

893 except OSError: 

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

895 

896 if lexOnly: 

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

898 else: 

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

900 + lifFileName+" (LEX"+machine+")") 

901# 

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

903# an exception 

904# 

905 return False 

906# 

907# ROM file creator class ---------------------------------------- 

908# 

909class clsRomCreator(object): 

910 

911 def __init__(self): 

912 super().__init__() 

913# 

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

915# 

916# Returns: 

917# True: everything o.k. 

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

919# 

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

921# 

922# check if we run in regression test mode 

923# 

924 isRegressionTest=False 

925 if os.getenv("CAPASMREGRESSIONTEST"): 

926 isRegressionTest=True 

927# 

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

929# 

930 if romFileName=="": 

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

932# 

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

934# 

935 romSize=romSize*1024 

936 rom75=False 

937 objectFile=clsObjectFile(binFileName) 

938 code=bytearray(objectFile.getBytes()) 

939# 

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

941# 

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

943 rom75=True 

944 if romSize < len(code)+2: 

945 raise capasmError("ROM size too small") 

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

947# 

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

949# 

950 else: 

951 romNo=code[0] 

952 checkRomNo= ~ romNo &0xFF 

953 if checkRomNo== code[1]: 

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

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

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

957 else: 

958 raise capasmError("Invalid ROM number") 

959 if romSize < len(code)+4: 

960 raise capasmError("ROM size too small") 

961# 

962# fill code to length of rom 

963# 

964 fill=romSize - len(code) 

965 for i in range(0,fill): 

966 code.append(0) 

967# 

968# HP-75 check sum 

969# 

970 if rom75: 

971 c=0 

972 i=0 

973 while(i<len(code)): 

974 c+=code[i] 

975 while c> 255: 

976 c+=1 

977 c-= 256 

978 i+=1 

979 cInv= ~c &0xFF 

980 code[-1]=cInv 

981 else: 

982# 

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

984# 

985 t=0 

986 i=0 

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

988 c1=code[i] 

989 i+=1 

990 c2=code[i] 

991 i+=1 

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

993 for j in range (0,16): 

994 r26 = t & 0xff 

995 r27 = t>>8 

996 r45 = r27 

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

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

999 w = w>>1 

1000 r45 = r45 ^ r26 

1001 r45 = r45 ^ r27 

1002 if (r45 & 1): 

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

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

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

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

1007# 

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

1009# 

1010 t=0 

1011 i=0 

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

1013 c1=code[i] 

1014 i+=1 

1015 c2=code[i] 

1016 i+=1 

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

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

1019 t = s>>8 

1020 s = s & 0xff 

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

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

1023# 

1024# write rom file 

1025# 

1026 try: 

1027 romFile=open(romFileName,"wb") 

1028 romFile.write(code) 

1029 romFile.close() 

1030 except OSError: 

1031 raise capasmError("cannot write rom file") 

1032 return False 

1033# 

1034# entry point caplif ------------------------------------------------------ 

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

1036# 

1037def caplif(): # pragma: no cover 

1038# 

1039# command line arguments processing 

1040# 

1041 argparser=argparse.ArgumentParser(description=\ 

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

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

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

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

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

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

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

1049 default="") 

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

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

1052 default="") 

1053 args= argparser.parse_args() 

1054 

1055 l=clsLifCreator() 

1056 try: 

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

1058 False) 

1059 except capasmError as e: 

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

1061 sys.exit(1) 

1062# 

1063# entry point caplex ------------------------------------------------------ 

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

1065# 

1066def caplex(): # pragma: no cover 

1067# 

1068# command line arguments processing 

1069# 

1070 argparser=argparse.ArgumentParser(description=\ 

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

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

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

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

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

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

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

1078 default="") 

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

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

1081 default="") 

1082 args= argparser.parse_args() 

1083 

1084 l=clsLifCreator() 

1085 try: 

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

1087 True) 

1088 except capasmError as e: 

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

1090 sys.exit(1) 

1091# 

1092# entry point capglo ------------------------------------------------------- 

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

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

1095# the suffix ".py" 

1096# 

1097def capglo(): # pragma: no cover 

1098 

1099 p=argparse.ArgumentParser(description=\ 

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

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

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

1103 args=p.parse_args() 

1104 

1105 gen=clsSymClassGenerator() 

1106 hasErrors=False 

1107 for inputFileName in args.inputfiles: 

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

1109 try: 

1110 hasErrors!=gen.generate(inputFileName,outputFileName) 

1111 except capasmError as e: 

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

1113 hasErrors=True 

1114 break 

1115 if hasErrors: 

1116 sys.exit(1) 

1117# 

1118# entry point caprom ------------------------------------------------------- 

1119# 

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

1121# 

1122 

1123def caprom(): # pragma: no cover 

1124 

1125 argparser=argparse.ArgumentParser(description=\ 

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

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

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

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

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

1131 default="") 

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

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

1134 args= argparser.parse_args() 

1135 

1136 l=clsRomCreator() 

1137 try: 

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

1139 except capasmError as e: 

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

1141 sys.exit(1) 

1142# 

1143# entry point capconv ------------------------------------------------------- 

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

1145# DAD or EQU symbol definitions 

1146# 

1147def capconv(): # pragma: no cover 

1148 

1149 p=argparse.ArgumentParser(description=\ 

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

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

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

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

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

1155 args=p.parse_args() 

1156 

1157 if args.type=="glo": 

1158 conv=clsSymbolFileConverter() 

1159 suffix=".glo" 

1160 else: 

1161 conv=clsAsmSourceFileConverter() 

1162 suffix=".asm" 

1163 hasErrors=False 

1164 for inputFileName in args.inputfiles: 

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

1166 try: 

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

1168 except capasmError as e: 

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

1170 hasErrors=True 

1171 break 

1172 if hasErrors: 

1173 sys.exit(1) 

1174#