' 2010-02-25 new version number con _clkmode = xtal1 + pll8x _xinfreq = 10_000_000 tvPin = 24 sdPin = 16 obj term: "isxtv" kw: "kwdefs" st: "symbols" bt: "bintree" fs: "sxfile" str: "stringx" token: "tokenrdr" eval: "eval" methods: "methods" pub Main | err, p err := \Try if err if err > 0 term.str( err ) else term.str( string("Error ") ) term.dec( err ) term.out( 13 ) if stackset and long[pSymbolTableSpace] <> $6666_6666 term.str( string("Stack corrupt",13) ) term.dec( token.LineNumber ) term.out( "," ) term.dec( token.Column + 1 ) term.out( " " ) term.out( "'" ) term.str( token.Text ) term.out( "'" ) term.out( 13 ) fs.Close fs.Open( string("sphinx.bin"), "R" ) else fs.Close if link fs.Open( string("link.bin"), "R" ) else fs.Open( string("sphinx.bin"), "R" ) bt.Stop fs.Execute( 0 ) pri Try ProcessCommandLine if verbosity => 1 term.str( string("codegen 100225", 13) ) Start pri ProcessCommandLine | nArgs, l fs.Open( string("args.d8a"), "R" ) nArgs := fs.ReadByte ifnot nArgs-- abort string("usage: compile spinfile [options]") fs.ReadStringUpperCase( @tokenFilename, MAXFILENAMELENGTH ) l := strsize( @tokenFilename ) if tokenFilename[l-4] == "." and tokenFilename[l-3] == "S" and tokenFilename[l-2] == "P" and tokenFilename[l-1] == "N" tokenFilename[l-4]~ if strsize( @tokenFilename ) > 8 term.str( @tokenFilename ) abort string(" -- filename too long") str.Copy( @outputFilename, @tokenFilename ) str.Copy( @binFilename, @tokenFilename ) str.Append( @tokenFilename, string(".TOK") ) str.Append( @outputFilename, string(".SOB") ) str.Append( @binFilename, string(".BIN") ) ' Process command line arguments repeat while nArgs-- fs.ReadStringUpperCase( @stringBuffer, MAXSTRINGBUFFERLENGTH ) if stringBuffer[0] == "-" stringBuffer[0] := "/" if strcomp( @stringBuffer, string("/L") ) link~~ elseif strcomp( @stringBuffer, string("/V") ) ifnot nArgs-- abort string("/V must be followed by a number") verbosity := fs.ReadNumber elseif strcomp( @stringBuffer, string("/S") ) ifnot nArgs-- abort string("/S must be followed by a number") STACKSPACE := fs.ReadNumber if STACKSPACE & 3 abort string("/S argument must be a multiple of 4") elseif strcomp( @stringBuffer, string("/T") ) ifnot nArgs-- abort string("/T must be followed by a number") TABLESPACE := fs.ReadNumber if TABLESPACE & 3 abort string("/T argument must be a multiple of 4") 'else ' ignore fs.Close con MAXFILENAMELENGTH = 8 + 1 + 3 ' 8.3 MAXSTRINGBUFFERLENGTH = 32 SXTVRENDEZVOUS = $8000 - 4 RESERVED = SXTVRENDEZVOUS - 4 SDSPIRENDEZVOUS = RESERVED - 3 * 4 SXFS2RENDEZVOUS = SDSPIRENDEZVOUS - 4 * 4 ' four rendezvous variables SXFSRENDEZVOUS = SXFS2RENDEZVOUS - 4 * 4 ' four rendezvous variables METADATABUFFER = SXFSRENDEZVOUS - 512 _free = ($8000 - METADATABUFFER) / 4 var byte tokenFilename[MAXFILENAMELENGTH+1] ' input .tok file byte outputFilename[MAXFILENAMELENGTH+1] ' output .sob file byte binFilename[MAXFILENAMELENGTH+1] ' output .bin file (just used to empty the .bin) byte sobFilename[MAXFILENAMELENGTH+1] ' used for child .sob file(s) byte stringBuffer[MAXSTRINGBUFFERLENGTH+1] ' temp string buffer dat STACKSPACE long 575<<2 TABLESPACE long 1500<<2 verbosity byte 0 ' set by /V option stackset byte 0 link byte 0 ' set by /L; if non-zero, run link.bin automatically. { |<---STACKSPACE--->|<--------TABLESPACE---------->|<-----------OBJECTSPACE------------>|<-I/O RENDEZVOUS etc. +-----+------------+-----------+------------------+------+----------------+------------+--+ |stack| |symboltable| |header|dat/PASM|methods| | | +-----+------------+-----------+------------------+------+----------------+------------+--+ |tempstorage| ^ ^ |stringtemp| ^ | | ^ ^ | pObjSpace-+ pObjWork-+ | | +--$8000 pStringWork-+ pObjTop-+ } var ' These next 3 vars actually live in methods.spin now. ' word pObjSpace ' points to area where the compiled object's header and bytecode will live (this pointer doesn't change) ' word pObjWork ' points to next available byte (this pointer changes as code is compiled) ' word pObjTop ' points just beyond available object workspace word pDatWork ' like pObjWork but for the DAT segment word pSymbolTableSpace pri Start | i pSymbolTableSpace := word[ $0a ] + STACKSPACE methods.set_pObjSpace( pSymbolTableSpace + TABLESPACE ) methods.set_pObjTop( METADATABUFFER ) bt.Init( pSymbolTableSpace, methods.get_pObjSpace ) st.Init long[ bt.Alloc(4) ] := $6666_6666 stackset~~ ReadTimestamp if verbosity => 2 term.str( string("stack space: ") ) term.dec( STACKSPACE ) term.str( string(13,"table space: ") ) term.dec( TABLESPACE ) term.str( string(13,"work space: ") ) term.dec( methods.get_pObjTop - methods.get_pObjSpace ) term.out( 13 ) term.str( string("Opening ") ) term.str( @tokenFilename ) term.out( 13 ) token.Open( @tokenFilename ) bvarSize~ wvarSize~ lvarSize~ nPubs := 1 ' pub indexes start at 1; 0th entry in object header is not a pub nPris~ ' pri indexes have to be compensated later once final nPubs is known nObjs~ pObjList~ pObjListEnd~ ParsePass1 token.Close CheckStack varSize := (lvarSize + wvarSize + bvarSize + 3) & !3 pDatWork := methods.get_pObjSpace + (nPubs + nPris + nObjs) << 2 ' each PUB/PRI/OBJ takes up 4 bytes in the object header methods.set_pObjWork( pDatWork + datSize ) bytefill( methods.get_pObjSpace, 0, pDatWork - methods.get_pObjSpace ) ' zero the header FixupOffsets( bt.PeekW( st.GetPSymbolTable ) ) token.Open( @tokenFilename ) ParsePass2 token.Close methods.set_pObjWork( (methods.get_pObjWork + 3) & !3 ) ' Round up to multiple of 4 WriteSobFile pri CheckStack if long[pSymbolTableSpace] <> $6666_6666 abort string("Stack overflow") pri ReadTimestamp if verbosity => 3 term.str( string("reading timestamp: ") ) if fs.Open( string("timestmp.d8a"), "R" ) == 0 timestamp := fs.ReadLong + 1 fs.Close else timestamp := 1 if verbosity => 3 term.dec( timestamp ) term.out( 13 ) fs.Open( string("timestmp.d8a"), "W" ) fs.WriteLong( timestamp ) fs.Close pri WriteSobFile | p if nPubs == 1 ' recall that nPubs starts at 1 because of the 0th entry in the object header. abort string("No PUB routines defined") ifnot link ' If we're not running the linker immediately after codegen, if verbosity => 3 ' empty the .bin file if it exists(would be better to delete, term.str( string("Zeroing ") ) ' but sxfs doesn't have that ability). If we forget to link, term.str( @binFilename ) ' we'll get an error when we try to run the .bin. term.out( 13 ) if fs.Open( @binFilename, "R" ) == 0 ' Check for existence fs.Close fs.Open( @binFilename, "W" ) fs.Close objBinSize := methods.get_pObjWork - methods.get_pObjSpace ' This gets stored in .sob header word[methods.get_pObjSpace][0] := objBinSize ' This gets stored in object header word[methods.get_pObjSpace][1] := nPubs + nPris + nObjs << 8 ' Compute checksum and hash checksum~ hash~ p := methods.get_pObjSpace repeat objBinSize checksum += byte[p] hash := (hash <- 1) ^ byte[p++] if verbosity => 2 term.str( string("Writing ") ) term.str( @outputFilename ) term.out( 13 ) wordfill( @numExports, 0, 4 ) ComputeSobInfo( bt.PeekW( st.GetPSymbolTable ) ) fs.Open( @outputFilename, "W" ) fs.Write( @SobFileHeader, SIZEOFSOBHEADER ) WriteExports( bt.PeekW( st.GetPSymbolTable ) ) WriteImports fs.Write( methods.get_pObjSpace, objBinSize ) fs.Close pri FixupOffsets( p ) | s, t, v, size, headerSize, q {{ Goes through the symbol table and adjusts offsets of word VARs to put them after long VARs and puts byte VARs after word VARs. Also adjusts PRI method indexes so they come after PUBs, and OBJ indexes to put them after PRIs. And now also adjusts DAT labels upward by the size of the object header. }} ifnot p return FixupOffsets( bt.PeekW( p ) ) s := p + 4 s += strsize(s) + 1 if byte[s] == st#kVAR_SYMBOL ' var is followed by ++s size := byte[s++] ' 1-byte size (1, 2, 4) and 2-byte offset t~ if size == 2 t := lvarSize ' word vars come after long vars elseif size == 1 t := lvarSize + wvarSize ' byte vars come after word vars bt.PokeW( s, bt.PeekW(s) + t ) ' adjust var symbol's offset elseif byte[s] == st#kPRI_SYMBOL ++s byte[s] += nPubs ' the PUBs are 1..nPubs-1, the PRIs are nPubs..nPubs+nPris-1 elseif byte[s] == st#kOBJ_SYMBOL ++s byte[s][2] += nPubs + nPris ' the OBJs are nPubs+nPris.. elseif byte[s] == st#kDAT_SYMBOL headerSize := (nPubs + nPris + nObjs) << 2 q := s ' start at global label repeat ' and follow the linked list of local labels bt.PokeW( q+2, bt.PeekW( q+2 ) + headerSize ) ' adjust label's dp ifnot q := bt.PeekW( q+6 ) quit q += strsize(q) + 1 ' next in the list FixupOffsets( bt.PeekW( p + 2 ) ) { pri TraverseTable( p ) | s, t, v, q { Symbol table dump. Call thusly: TraverseTable( bt.PeekW( st.GetPSymbolTable ) ) } ifnot p return TraverseTable( bt.PeekW( p ) ) s := p + 4 term.str( s ) s += strsize(s) + 1 case t := byte[s++] st#kINT_CON_SYMBOL: v := bt.PeekL( s ) term.out( "=" ) term.dec( v ) st#kPRI_SYMBOL, st#kPUB_SYMBOL: if t == st#kPRI_SYMBOL term.str( string(" pri: ") ) else term.str( string(" pub: ") ) term.dec( byte[s++] ) ' method index term.out( " " ) term.dec( byte[s++] ) ' #params st#kDAT_SYMBOL: q := s-1 repeat while q := bt.PeekW( q+6 ) term.out(":") term.str( q ) q += strsize( q ) + 1 term.str( string(" dat ") ) case byte[s++] 1: term.str( string("byte@") ) 2: term.str( string("word@") ) 4: term.str( string("long@") ) term.hex( bt.PeekW(s), 4 ) term.out(",") term.hex( bt.PeekW(s+2), 4 ) st#kVAR_SYMBOL: term.str( string(" var ") ) case byte[s++] 1: term.str( string("byte@") ) 2: term.str( string("word@") ) 4: term.str( string("long@") ) term.hex( bt.PeekW(s), 4 ) st#kOBJ_SYMBOL: term.out( ":" ) term.str( bt.PeekW(s) ) st#kSOB_SYMBOL: term.out( ">" ) term.hex( bt.PeekW(s), 4) other: term.out( "?" ) term.hex( t, 2 ) term.out( ";" ) term.out( " " ) TraverseTable( bt.PeekW( p + 2 ) ) } con SIZEOFSOBHEADER = 26 dat SobFileHeader long byte "SOB1" timestamp long 0 hash long 0 numExports word 0 exportSize word 0 numImports word 0 importSize word 0 objBinSize word 0 checksum byte 0 {padding} byte 0 varSize word 0 ' total size in bytes of vars (rounded up to multiple of 4) pri ComputeSobInfo( p ) | pName, type {{ Traverses the symbol table and computes exportSize, numExports, importSize, numImports. Initialize those variables to 0 before entering this routine. }} ifnot p return pName := p + 4 type := byte[pName + strsize(pName) + 1] if type == st#kINT_CON_SYMBOL or type == st#kFLOAT_CON_SYMBOL ++numExports exportSize += strsize(pName) + 6 ' name + null + type + 4-byte value elseif type == st#kPUB_SYMBOL ++numExports exportSize += strsize(pName) + 4 ' name + null + type + index + #args elseif type == st#kOBJ_SYMBOL pName := bt.PeekW( pName + strsize(pName) + 2 ) ++numImports importSize += strsize(pName) - 4 + 4 ' name (excluding ".SOB") + null + 2-byte count + reserved ComputeSobInfo( bt.PeekW( p ) ) ComputeSobInfo( bt.PeekW( p + 2 ) ) pri WriteExports( p ) | pName, pData, type {{ Traverses the symbol table and writes exports to output file. }} ifnot p return pName := p + 4 pData := pName + strsize(pName) + 1 type := byte[pData] if type == st#kINT_CON_SYMBOL or type == st#kFLOAT_CON_SYMBOL ' name + null + type + 4-byte value fs.WriteString( pName ) fs.WriteByte( type ) fs.WriteLong( bt.PeekL( pData+1 ) ) elseif type == st#kPUB_SYMBOL ' name + null + type + index + #args fs.WriteString( pName ) fs.WriteByte( type ) fs.Write( pData+1, 2 ) ' index + #args WriteExports( bt.PeekW( p ) ) WriteExports( bt.PeekW( p + 2 ) ) pri WriteImports | pName, pData, type {{ Traverses the OBJ list and writes imports to output file. We use a list so that the imports are written in the order they appear in the source. This is important for OBJ symbols because we want the SOB imports to line up properly. }} pName := pObjList repeat while pName pData := pName + strsize(pName) + 1 ' pName is name of symbol (e.g. "DEBUG") ' pData points to symbol's data: 1-byte type + 2-byte pointer to SOB + 1-byte index + 2-byte count + 2-byte link pName := bt.PeekW( pData + 1 ) ' pName is name of .sob file (e.g. "TV_TEXT.SOB") fs.Write( pName, strsize(pName)-4 ) ' Write name but drop .SOB suffix. fs.WriteByte( 0 ) ' Write null terminator fs.WriteWord( bt.PeekW( pData + 4 ) ) ' Write count fs.WriteByte( 0 ) ' Write reserved byte pName := bt.PeekW( pData + 6 ) ' link to next pri ParsePass1 Eval.set_pass( 1 ) InitDat if verbosity => 2 term.str( string("Pass 1") ) term.out( 13 ) ParseCon1 repeat while token.Type <> kw#kEOF ifnot token.IsBlockDesignator abort string("Syntax error") if token.Column abort string("Block designator not in column 1") if token.AdvanceIf( kw#kCON ) ParseCon1 elseif token.AdvanceIf( kw#kDAT ) ParseDat1 elseif token.AdvanceIf( kw#kOBJ ) ParseObj1 elseif token.AdvanceIf( kw#kPRI ) ParseMethod1( st#kPRI_SYMBOL ) elseif token.AdvanceIf( kw#kPUB ) ParseMethod1( st#kPUB_SYMBOL ) elseif token.AdvanceIf( kw#kVAR ) ParseVar1 else abort string("Syntax error") pri ParsePass2 Eval.set_pass( 2 ) InitDat if verbosity => 2 term.str( string("Pass 2") ) term.out( 13 ) ParseCon2 repeat while token.Type <> kw#kEOF CheckStack ifnot token.IsBlockDesignator abort string("Syntax error") if token.AdvanceIf( kw#kCON ) ParseCon2 elseif token.AdvanceIf( kw#kDAT ) ParseDat2 elseif token.AdvanceIf( kw#kOBJ ) SkipBlock elseif token.AdvanceIf( kw#kPRI ) methods.Parse2( st#kPRI_SYMBOL ) elseif token.AdvanceIf( kw#kPUB ) methods.Parse2( st#kPUB_SYMBOL ) elseif token.AdvanceIf( kw#kVAR ) SkipBlock else abort string("Syntax error") pri SkipBlock repeat until token.IsBlockDesignator or token.Type == kw#kEOF token.Advance pri ParseCon1 | o, p, inc, v o~ token.AdvanceIf( kw#kEOL ) repeat inc := 1 if token.AdvanceIf( kw#kOCTOTHORP ) o := Eval.EvaluateExpression( 0 ) elseif token.IsId ' Id +? st.AddToTable( token.Text ) byte[ p := bt.Alloc(1) ] := st#kINT_CON_SYMBOL ' p points to symbol type byte ' in case we have to patch the type retroactively token.Advance if token.AdvanceIf( kw#kEQUAL ) ' Id = v := Eval.TryToEvaluateExpression( 0 ) if Eval.Succeeded bt.PokeL( bt.Alloc(4), v ) else ' it is undefined byte[p] := st#kUNDEFINED_CON_SYMBOL bt.Alloc(4) ' but we should allocate space for the eventual value. elseif token.AdvanceIf( kw#kLBRACKET ) ' Id [ inc := Eval.EvaluateExpression( 0 ) token.Eat( kw#kRBRACKET ) bt.PokeL( bt.Alloc(4), o ) o += inc else ' Plain Id bt.PokeL( bt.Alloc(4), o ) o += inc else quit if token.Type == kw#kCOMMA or token.Type == kw#kEOL token.Advance else abort string("Syntax error") pri ParseCon2 | o, p, inc o~ token.AdvanceIf( kw#kEOL ) repeat inc := 1 if token.AdvanceIf( kw#kOCTOTHORP ) o := Eval.EvaluateExpression( 0 ) elseif token.IsId ' Id +? p := st.SymbolLookup( token.Text ) token.Advance if token.AdvanceIf( kw#kEQUAL ) ' Id = bt.PokeL( p+1, Eval.EvaluateExpression( 0 ) ) byte[p] := st#kINT_CON_SYMBOL elseif token.AdvanceIf( kw#kLBRACKET ) ' Id [ inc := Eval.EvaluateExpression( 0 ) token.Eat( kw#kRBRACKET ) o += inc else ' Plain Id o += inc else quit if token.Type == kw#kCOMMA or token.Type == kw#kEOL token.Advance else abort string("Syntax error") var word dp ' dp is the address in DAT space of current source line. ' dp = pObjWork - pObjSpace word ooo ' dp + ooo = cogx4; cogx4 / 4 = cog address of current source line. word pGlobalLabel ' points to most-recently defined global label; used as the head of a linked list of local labels byte alignment ' 1, 2, or 4 { a DAT line consists of (in rough pseudo-bnf) one of the following: 1) [label | :label] ORG|RES [ ] 2) [label | :label] * (with commas between -- yeah, yeah, I said it was rough) 3) [label | :label] [ ] , * As we process DAT statements and emit bytes, pDatWork increases, always pointing where the next byte will go. dp = pDatWork - pObjSpace (i.e., dp 0 is the beginning of DAT space) cogx4 = dp + ooo, where ooo is updated each time an ORG directive is encountered: ooo = org_expression * 4 - dp Example: Let us say pObjSpace is 1000. The object header starts there. Say it takes up 100 bytes, so pDatWork is 1100. In terms of DAT space, dp = 1100 - 1000 = 100. Say there's an ORG 100, so ooo = 100 * 4 - 100 = 300. A label defined at this point would have dp = 100, cogx4 = 100 + 300 = 400 (divide this by 4 to get cog address 100). Suppose we assemble a PASM instruction and emit the four bytes. Now pDatWork = 1104, dp = 104, and cogx4 = 104 + 300 = 404 (cog address 101). } pri InitDat if Eval.get_pass == 1 dp~ ooo~ else ooo := methods.get_pObjSpace - pDatWork '= -dp pGlobalLabel~ alignment := 1 pri ParseDat1 | pLabelData, p, v, size, count, dpInc, temp, cogx4 Eval.EnteringDat cogx4~ repeat pLabelData~ if token.AdvanceIf( kw#kEOL ) next if token.IsId ' label? if byte[token.Text] == ":" ' local label? ' Traverse the list of locals to check for duplicates p := pGlobalLabel repeat while p := bt.PeekW( p + 6 ) ' p points to string part of local label if strcomp( p, token.Text ) abort string("Duplicate local label") p += strsize(p) + 1 ' Increment p past string part to data part p := bt.Alloc( strsize( token.Text ) + 9 ) ' p points to str.Copy( p, token.Text ) ' local label's text followed by pLabelData := p + strsize( token.Text ) + 1 ' 8 bytes of label data (to be filled in later) byte[ pLabelData ] := st#kDAT_SYMBOL ' type bt.PokeW( pLabelData+6, bt.PeekW(pGlobalLabel+6) ) ' pointer to local labels ifnot pGlobalLabel abort string("No preceding global label") bt.PokeW( pGlobalLabel+6, p ) ' Insert this local label at head of list else ' plain old label st.AddToTable( token.Text ) pLabelData := bt.Alloc( 8 ) ' local label data byte[ pLabelData ] := st#kDAT_SYMBOL ' type bt.PokeW( pLabelData+6, 0 ) ' pointer to local labels pGlobalLabel := pLabelData token.Advance dpInc~ if token.AdvanceIf( kw#kORG ) ' Case 1a: ORG [ ] alignment := 4 PadToAlignment temp~ ' default ORG is 0 if token.Type <> kw#kEOL temp := Eval.EvaluateExpression( pGlobalLabel ) << 2 ooo := temp - dp cogx4 := dp + ooo elseif token.AdvanceIf( kw#kRES ) ' Case 1b: RES [ ] alignment := 4 PadToAlignment temp := 4 ' default RES is 1 long if token.Type <> kw#kEOL temp := Eval.EvaluateExpression( pGlobalLabel ) << 2 cogx4 := dp + ooo ooo += temp elseif token.AdvanceIf( kw#kFIT ) temp := 496 if token.Type <> kw#kEOL temp := Eval.EvaluateExpression( pGlobalLabel ) if cogx4 > temp << 2 abort string("Origin exceeds FIT limit") elseif token.IsSize ' Case 2. BYTE/WORD/LONG * alignment := |< (token.Type - kw#kBYTE) ' 1/2/4 PadToAlignment cogx4 := dp + ooo token.Advance if token.Type <> kw#kEOL repeat size := alignment if token.IsSize size := |< (token.Type - kw#kBYTE) ' 1/2/4 -- just size token.Advance v := Eval.TryToEvaluateExpression( pGlobalLabel ) count := 1 if token.AdvanceIf( kw#kLBRACKET ) count := Eval.EvaluateExpression( pGlobalLabel ) token.Eat( kw#kRBRACKET ) dpInc += count * size while token.AdvanceIf( kw#kCOMMA ) elseif token.IsCond or token.IsPasm ' Case 3. [ ] alignment := 4 PadToAlignment cogx4 := dp + ooo dpInc := 4 repeat token.Advance until token.Type == kw#kEOL elseif token.Type <> kw#kEOL abort string("Syntax error") if pLabelData ' Was a label defined on the current source line? byte[pLabelData+1] := alignment ' data size bt.PokeW( pLabelData+2, dp ) ' Address in DAT space bt.PokeW( pLabelData+4, cogx4 ) ' Cog address x 4 ' The local label linked list pointer was filled in previously dp += dpInc cogx4 += dpInc until token.IsBlockDesignator or token.Type == kw#kEOF datSize := dp Eval.LeavingDat pri ParseDat2 | pLabelData, p, v, size, count, temp, cond, pasmOp, ds, effect, oooInc Eval.EnteringDat oooInc~ repeat pLabelData~ if token.AdvanceIf( kw#kEOL ) next if token.IsId ' label? if byte[token.Text] == ":" ' local label? ' Traverse the list of locals to find this one. p := pGlobalLabel repeat while p := bt.PeekW( p + 6 ) ' p points to string part of local label if strcomp( p, token.Text ) quit p += strsize(p) + 1 ' Increment p past string part to data part pLabelData := p + strsize(p) + 1 else ' plain old label pLabelData := st.SymbolLookup( token.Text ) pGlobalLabel := pLabelData token.Advance dp := pDatWork - methods.get_pObjSpace if token.AdvanceIf( kw#kORG ) ' Case 1: ORG temp~ ' default ORG is 0 alignment := 4 PadToAlignment if token.Type <> kw#kEOL temp := Eval.EvaluateExpression( pGlobalLabel ) << 2 ooo := temp - dp elseif token.AdvanceIf( kw#kRES ) ' Case 1b: RES [ ] alignment := 4 PadToAlignment oooInc := 4 ' default RES is 1 long if token.Type <> kw#kEOL oooInc := Eval.EvaluateExpression( pGlobalLabel ) << 2 elseif token.AdvanceIf( kw#kFIT ) ' ignore FIT in pass 2 if token.Type <> kw#kEOL Eval.EvaluateExpression( pGlobalLabel ) elseif token.IsSize ' Case 2. BYTE/WORD/LONG * alignment := |< (token.Type - kw#kBYTE) ' 1/2/4 PadToAlignment token.Advance if token.Type == kw#kEOL next repeat size := alignment if token.IsSize size := |< (token.Type - kw#kBYTE) ' 1/2/4 -- just size token.Advance v := Eval.EvaluateExpression( pGlobalLabel ) count := 1 if token.AdvanceIf( kw#kLBRACKET ) count := Eval.EvaluateExpression( pGlobalLabel ) token.Eat( kw#kRBRACKET ) repeat count temp := v repeat size EmitDat( temp ) temp >>= 8 while token.AdvanceIf( kw#kCOMMA ) elseif token.IsCond or token.IsPasm alignment := 4 PadToAlignment cond~~ if token.IsCond cond := token.GetCond token.Advance ifnot token.IsPasm abort string("Expected PASM instruction") ds := %11 case token.Type kw#kAND: pasmOp := $60bc0000 ' Special handling for double-duty PASM mnemonics kw#kOR: pasmOp := $68bc0000 kw#kWAITCNT: pasmOp := $f8bc0000 kw#kWAITPEQ: pasmOp := $f03c0000 kw#kWAITPNE: pasmOp := $f43c0000 kw#kWAITVID: pasmOp := $fc3c0000 kw#kCOGID: pasmOp := $0cfc0001 ds := %10 kw#kCOGINIT: pasmOp := $0c7c0002 ds := %10 kw#kCOGSTOP: pasmOp := $0cfc0003 ds := %10 kw#kCLKSET: pasmOp := $0c7c0000 ds := %10 other: pasmOp := token.GetPasmOp ds := token.GetPasmDS if token.Type == kw#kCALL ' special handling for CALL instruction token.Advance token.Eat( kw#kOCTOTHORP ) ifnot token.IsId abort string("Expected return label") str.Copy( @stringBuffer, token.Text ) str.Append( @stringBuffer, string("_RET") ) ifnot p := st.SymbolLookup( @stringBuffer ) abort string("Return label not found") if byte[p] <> st#kDAT_SYMBOL abort string("Non-DAT return label") temp := bt.PeekW( p+4 ) if temp & 3 abort string("Return label is not long") pasmOp |= ((temp >> 2 & $1ff) << 9) | (Eval.EvaluateExpression( pGlobalLabel ) & $1ff) ' combine destination and source else ' regular PASM instruction token.Advance if ds & %10 ' destination operand pasmOp |= (Eval.EvaluateExpression( pGlobalLabel ) & $1ff) << 9 if ds == %11 token.Eat( kw#kCOMMA ) if ds & %01 if token.AdvanceIf( kw#kOCTOTHORP ) ' immediate? pasmOp |= |< 22 ' set I pasmOp |= Eval.EvaluateExpression( pGlobalLabel ) & $1ff ' source operand effect~ if token.Type <> kw#kEOL repeat ifnot token.IsEffect abort string("Expected PASM effect") if token.GetEffect == 8 ' NR? effect &= %110 ' clear R else effect |= token.GetEffect token.Advance while token.AdvanceIf( kw#kCOMMA ) if pasmOp == 0 and (cond <> -1 or effect ) abort string("Conditions and effects not allowed on NOP") pasmOp |= (effect << 23) if cond <> -1 pasmOp := pasmOp & %111111_1111_0000_111111111_111111111 | (cond << 18) repeat 4 EmitDat( pasmOp ) pasmOp >>= 8 if pLabelData ' Was a label defined on the current source line? if byte[pLabelData+1] <> alignment { data size } or bt.PeekW( pLabelData+2 ) <> dp { Address in DAT space } or bt.PeekW( pLabelData+4 ) <> dp + ~~ooo ' Cog address x 4 term.dec(byte[pLabelData+1]) term.out(" ") term.dec(alignment) term.out(13) term.dec(bt.PeekW( pLabelData+2 )) term.out(" ") term.dec(dp) term.out(13) term.dec(bt.PeekW( pLabelData+4 )) term.out(" ") term.dec(dp+~~ooo) abort string("Phase error") ooo += oooInc~ until token.IsBlockDesignator or token.Type == kw#kEOF Eval.LeavingDat pri PadToAlignment repeat (alignment - dp) & (alignment - 1) ' number of bytes needed to pad to new alignment ++dp if Eval.get_pass == 2 EmitDat( 0 ) ' padding pri EmitDat( b ) byte[pDatWork++] := b var word pObjList ' OBJ symbols are linked together in a list. word pObjListEnd ' Pointer to the last OBJ; append new OBJs at the end. pri ParseObj1 | i, count, pObj, pSob, pObjName {{ { [ ] } : }} token.AdvanceIf( kw#kEOL ) repeat while token.IsId pObjName := bt.Alloc( 0 ) + 4 ' Hacky way to get pointer to the name field of the symbol we're about to add. st.AddToTable( token.Text ) pObj := bt.Alloc( 8 ) ' type (1), ptr to sym table (2), index(1), count (2), ptr to next (2) byte[pObj] := st#kOBJ_SYMBOL token.Advance count := 1 if token.AdvanceIf( kw#kLBRACKET ) count := Eval.EvaluateExpression( 0 ) token.Eat( kw#kRBRACKET ) token.Eat( kw#kCOLON ) byte[pObj][3] := nObjs nObjs += count bt.PokeW( pObj+4, count ) ' Link this OBJ into the list of OBJs if pObjListEnd ' this points to last OBJ name field pObjListEnd += strsize(pObjListEnd)+1 ' now this points to OBJ data bt.PokeW( pObjListEnd+6, pObjName ) ' make a link to new OBJ name field pObjListEnd := pObjName bt.PokeW( pObj+6, 0 ) ifnot pObjList pObjList := pObjName i~ repeat ifnot token.IsIntLiteral abort string("Syntax error") sobFilename[i++] := token.Value if i > MAXFILENAMELENGTH-4 ' have to leave room for ".sob" suffix abort string("SOB filename too long") token.Advance while token.AdvanceIf( kw#kCOMMA ) sobFilename[i]~ str.ToUpper( @sobFilename ) str.Append( @sobFilename, string(".SOB") ) ifnot pSob := st.SymbolLookup( @sobFilename ) st.AddToTable( @sobFilename ) byte[ pSob := bt.Alloc(3) ] := st#kSOB_SYMBOL ' type (1), ptr to sym table (2) bt.PokeW( pSob+1, 0 ) ReadSobFile( pSob+1 ) bt.PokeW( pObj+1, pSob - strsize(@sobFilename) - 1 ) token.Eat( kw#kEOL ) con SOBFILEFORMATVERSION = "S" + "O" << 8 + "B" << 16 + "1" << 24 ' "SOB1" pri ReadSobFile( pSobTable ) | p, t if verbosity => 3 term.str( string("Reading ") ) term.str( @sobFilename ) term.out( 13 ) if fs.Open( @sobFilename, "R" ) <> 0 term.str( @sobFilename ) abort string(" -- couldn't open") if fs.Readlong <> SOBFILEFORMATVERSION ' SOB file format version abort string("Unrecognized SOB file format") fs.Readlong ' timestamp fs.ReadLong ' hash numExports := fs.ReadWord ' number of exports exportSize := fs.ReadWord ' size of exports segment fs.ReadWord ' number of imports importSize := fs.ReadWord ' size of imports segment fs.ReadWord ' size of bytecode segment fs.ReadByte ' checksum fs.ReadByte ' padding fs.ReadWord ' size of sob's VAR space repeat numExports fs.ReadStringUpperCase( @stringBuffer, MAXSTRINGBUFFERLENGTH ) bt.AddToTable( @stringBuffer, pSobTable ) case t := fs.ReadByte st#kINT_CON_SYMBOL, st#kFLOAT_CON_SYMBOL: p := bt.Alloc( 5 ) byte[p++] := t bt.PokeL( p, fs.ReadLong ) st#kPUB_SYMBOL: p := bt.Alloc( 3 ) byte[p++] := t byte[p++] := fs.ReadByte ' method index byte[p++] := fs.ReadByte ' #params fs.Close pri ParseMethod1( type ) | nargs ' type = st#kPRI_SYMBOL or st#kPUB_SYMBOL ifnot token.IsId abort string("Expected ID") st.AddToTable( token.Text ) token.Advance nargs~ if token.AdvanceIf( kw#kLPAREN ) repeat ++nargs ifnot token.IsId abort string("Expected ID") token.Advance while token.AdvanceIf( kw#kCOMMA ) token.Eat( kw#kRPAREN ) if nargs > 255 abort string("Too many parameters") byte[ bt.Alloc(1) ] := type if type == st#kPUB_SYMBOL byte[ bt.Alloc(1) ] := nPubs++ else byte[ bt.Alloc(1) ] := nPris++ byte[ bt.Alloc(1) ] := nargs SkipBlock var word bvarSize ' size in bytes of byte var area word wvarSize ' size in bytes of word var area word lvarSize ' size in bytes of long var area byte nPubs, nPris word nObjs word datSize ' size in bytes of DAT area pri ParseVar1 | s, t, dim token.AdvanceIf( kw#kEOL ) repeat while token.IsSize if token.Type == kw#kBYTE s~ elseif token.Type == kw#kWORD s := 1 elseif token.Type == kw#kLONG s := 2 token.Advance repeat ifnot token.IsId abort string("Expected ID") st.AddToTable( token.Text ) token.Advance byte[ bt.Alloc(1) ] := st#kVAR_SYMBOL byte[ bt.Alloc(1) ] := |< s if token.AdvanceIf( kw#kLBRACKET ) dim := Eval.EvaluateExpression( 0 ) token.Eat( kw#kRBRACKET ) else dim := 1 bt.PokeW( bt.Alloc(2), bvarSize[s] ) bvarSize[s] += dim << s while token.AdvanceIf( kw#kCOMMA ) token.Eat( kw#kEOL ) {{ Copyright (c) 2009 Michael Park +------------------------------------------------------------------------------------------------------------------------------+ | TERMS OF USE: MIT License | +------------------------------------------------------------------------------------------------------------------------------+ |Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation | |files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, | |modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software| |is furnished to do so, subject to the following conditions: | | | |The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.| | | |THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | |WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | +------------------------------------------------------------------------------------------------------------------------------+ }}