1072 lines
38 KiB
Plaintext
1072 lines
38 KiB
Plaintext
' 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 [ <expr> ]
|
|
2) [label | :label] <size> <expr>* (with commas between <expr> -- yeah, yeah, I said it was rough)
|
|
3) [label | :label] [ <cond> ] <pasm op> <dst> , <src> <effect>*
|
|
|
|
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 [ <expr> ]
|
|
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 [ <expr> ]
|
|
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 <expr>*
|
|
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. [ <cond> ] <pasm op>
|
|
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 <expr>
|
|
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 [ <expr> ]
|
|
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 <expr>*
|
|
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
|
|
{{
|
|
<objId> { [ <const expr> ] } : <sobId>
|
|
}}
|
|
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. |
|
|
+------------------------------------------------------------------------------------------------------------------------------+
|
|
}}
|
|
|