assembler: add initial fragment shader parser
This commit is contained in:
parent
59b6b2a0d2
commit
adca6a1c66
@ -1,3 +1,3 @@
|
||||
; CONST[0] = { 1.3333 , _, _, _ }
|
||||
# CONST[0] = { 1.3333 , _, _, _ }
|
||||
out[1].xy = VE_MUL input[0].xy__ const[0].x1__
|
||||
out[0].xyzw = VE_ADD input[0].xyz1 input[0].0000
|
||||
|
||||
28
regs/assembler/error.py
Normal file
28
regs/assembler/error.py
Normal file
@ -0,0 +1,28 @@
|
||||
import sys
|
||||
|
||||
def print_error(filename, buf, e):
|
||||
assert len(e.args) == 2, e
|
||||
message, token = e.args
|
||||
lines = buf.splitlines()
|
||||
line = lines[token.line - 1]
|
||||
|
||||
error_name = str(type(e).__name__)
|
||||
col_indent = ' ' * token.col
|
||||
col_pointer = '^' * len(token.lexeme)
|
||||
RED = "\033[0;31m"
|
||||
DEFAULT = "\033[0;0m"
|
||||
print(f'File: "{filename}", line {token.line}, column {token.col}\n', file=sys.stderr)
|
||||
sys.stderr.write(' ')
|
||||
wrote_default = False
|
||||
for i, c in enumerate(line.decode('utf-8')):
|
||||
if i == token.col:
|
||||
sys.stderr.write(RED)
|
||||
sys.stderr.write(c)
|
||||
if i == token.col + len(token.lexeme):
|
||||
wrote_default = True
|
||||
sys.stderr.write(DEFAULT)
|
||||
if not wrote_default:
|
||||
sys.stderr.write(DEFAULT)
|
||||
sys.stderr.write('\n')
|
||||
print(f" {RED}{col_indent}{col_pointer}{DEFAULT}", file=sys.stderr)
|
||||
print(f'{RED}{error_name}{DEFAULT}: {message}', file=sys.stderr)
|
||||
73
regs/assembler/fs/keywords.py
Normal file
73
regs/assembler/fs/keywords.py
Normal file
@ -0,0 +1,73 @@
|
||||
from enum import Enum, auto
|
||||
|
||||
class KW(Enum):
|
||||
# ops
|
||||
CMP = auto()
|
||||
CND = auto()
|
||||
COS = auto()
|
||||
D2A = auto()
|
||||
DP = auto()
|
||||
DP3 = auto()
|
||||
DP4 = auto()
|
||||
EX2 = auto()
|
||||
FRC = auto()
|
||||
LN2 = auto()
|
||||
MAD = auto()
|
||||
MAX = auto()
|
||||
MDH = auto()
|
||||
MDV = auto()
|
||||
MIN = auto()
|
||||
RCP = auto()
|
||||
RSQ = auto()
|
||||
SIN = auto()
|
||||
SOP = auto()
|
||||
|
||||
# source/dest
|
||||
OUT = auto()
|
||||
TEMP = auto()
|
||||
FLOAT = auto()
|
||||
CONST = auto()
|
||||
SRC0 = auto()
|
||||
SRC1 = auto()
|
||||
SRC2 = auto()
|
||||
SRCP = auto()
|
||||
|
||||
# modifiers
|
||||
TEX_SEM_WAIT = auto()
|
||||
|
||||
_string_to_keyword = {
|
||||
b"CMP": KW.CMP,
|
||||
b"CND": KW.CND,
|
||||
b"COS": KW.COS,
|
||||
b"D2A": KW.D2A,
|
||||
b"DP": KW.DP,
|
||||
b"DP3": KW.DP3,
|
||||
b"DP4": KW.DP4,
|
||||
b"EX2": KW.EX2,
|
||||
b"FRC": KW.FRC,
|
||||
b"LN2": KW.LN2,
|
||||
b"MAD": KW.MAD,
|
||||
b"MAX": KW.MAX,
|
||||
b"MDH": KW.MDH,
|
||||
b"MDV": KW.MDV,
|
||||
b"MIN": KW.MIN,
|
||||
b"RCP": KW.RCP,
|
||||
b"RSQ": KW.RSQ,
|
||||
b"SIN": KW.SIN,
|
||||
b"SOP": KW.SOP,
|
||||
b"OUT": KW.OUT,
|
||||
b"TEMP": KW.TEMP,
|
||||
b"FLOAT": KW.FLOAT,
|
||||
b"CONST": KW.CONST,
|
||||
b"SRC0": KW.SRC0,
|
||||
b"SRC1": KW.SRC1,
|
||||
b"SRC2": KW.SRC2,
|
||||
b"SRCP": KW.SRCP,
|
||||
b"TEX_SEM_WAIT": KW.TEX_SEM_WAIT,
|
||||
}
|
||||
|
||||
def find_keyword(s):
|
||||
if s.upper() in _string_to_keyword:
|
||||
return _string_to_keyword[s.upper()]
|
||||
else:
|
||||
return None
|
||||
191
regs/assembler/fs/parser.py
Normal file
191
regs/assembler/fs/parser.py
Normal file
@ -0,0 +1,191 @@
|
||||
from enum import IntEnum
|
||||
from typing import Literal, Union
|
||||
from dataclasses import dataclass
|
||||
|
||||
from assembler.parser import BaseParser, ParserError
|
||||
from assembler.lexer import TT, Token
|
||||
from assembler.fs.keywords import KW, find_keyword
|
||||
from assembler.error import print_error
|
||||
|
||||
class Mod(IntEnum):
|
||||
nop = 0
|
||||
neg = 1
|
||||
abs = 2
|
||||
nab = 3
|
||||
|
||||
@dataclass
|
||||
class LetExpression:
|
||||
src_keyword: Token
|
||||
src_swizzle_identifier: Token
|
||||
addr_keyword: Token
|
||||
addr_value_identifier: Token
|
||||
|
||||
@dataclass
|
||||
class DestAddrSwizzle:
|
||||
dest_keyword: Token
|
||||
addr_identifier: Token
|
||||
swizzle_identifier: Token
|
||||
|
||||
@dataclass
|
||||
class SwizzleSel:
|
||||
sel_keyword: Token
|
||||
swizzle_identifier: Token
|
||||
mod: Mod
|
||||
|
||||
@dataclass
|
||||
class Operation:
|
||||
dest_addr_swizzles: list[DestAddrSwizzle]
|
||||
opcode_keyword: Token
|
||||
swizzle_sels: list[SwizzleSel]
|
||||
|
||||
@dataclass
|
||||
class Instruction:
|
||||
let_expressions: list[LetExpression]
|
||||
operations: list[Operation]
|
||||
|
||||
class Parser(BaseParser):
|
||||
def let_expression(self):
|
||||
src_keyword = self.consume(TT.keyword, "expected src keyword")
|
||||
self.consume(TT.dot, "expected dot")
|
||||
src_swizzle_identifier = self.consume(TT.identifier, "expected src swizzle identifier")
|
||||
self.consume(TT.equal, "expected equal")
|
||||
addr_keyword = self.consume(TT.keyword, "expected addr keyword")
|
||||
if addr_keyword.keyword is KW.FLOAT:
|
||||
self.consume(TT.left_paren, "expected left paren")
|
||||
else:
|
||||
self.consume(TT.left_square, "expected left square")
|
||||
|
||||
addr_value_identifier = self.consume(TT.identifier, "expected address identifier")
|
||||
|
||||
if addr_keyword.keyword is KW.FLOAT:
|
||||
self.consume(TT.right_paren, "expected right paren")
|
||||
else:
|
||||
self.consume(TT.right_square, "expected right square")
|
||||
|
||||
return LetExpression(
|
||||
src_keyword,
|
||||
src_swizzle_identifier,
|
||||
addr_keyword,
|
||||
addr_value_identifier,
|
||||
)
|
||||
|
||||
def dest_addr_swizzle(self):
|
||||
dest_keyword = self.consume(TT.keyword, "expected dest keyword")
|
||||
self.consume(TT.left_square, "expected left square")
|
||||
addr_identifier = self.consume(TT.identifier, "expected dest addr identifier")
|
||||
self.consume(TT.right_square, "expected left square")
|
||||
self.consume(TT.dot, "expected dot")
|
||||
swizzle_identifier = self.consume(TT.identifier, "expected dest swizzle identifier")
|
||||
self.consume(TT.equal, "expected equal")
|
||||
|
||||
def is_opcode(self):
|
||||
opcode_keywords = {
|
||||
KW.CMP, KW.CND, KW.COS, KW.D2A,
|
||||
KW.DP , KW.DP3, KW.DP4, KW.EX2,
|
||||
KW.FRC, KW.LN2, KW.MAD, KW.MAX,
|
||||
KW.MDH, KW.MDV, KW.MIN, KW.RCP,
|
||||
KW.RSQ, KW.SIN, KW.SOP,
|
||||
}
|
||||
if self.match(TT.keyword):
|
||||
token = self.peek()
|
||||
return token.keyword in opcode_keywords
|
||||
return False
|
||||
|
||||
def is_neg(self):
|
||||
result = self.match(TT.identifier) and self.peek().lexeme == b'-'
|
||||
if result:
|
||||
self.advance()
|
||||
return result
|
||||
|
||||
def is_abs(self):
|
||||
result = self.match(TT.bar)
|
||||
if result:
|
||||
self.advance()
|
||||
return result
|
||||
|
||||
def swizzle_sel(self):
|
||||
neg = self.is_neg()
|
||||
abs = self.is_abs()
|
||||
|
||||
if neg:
|
||||
self.consume(TT.left_paren, "expected left paren")
|
||||
|
||||
sel_keyword = self.consume(TT.keyword, "expected sel keyword")
|
||||
self.consume(TT.dot, "expected dot")
|
||||
swizzle_identifier = self.consume(TT.identifier, "expected swizzle identifier")
|
||||
|
||||
if abs:
|
||||
self.consume(TT.bar, "expected bar")
|
||||
if neg:
|
||||
self.consume(TT.right_paren, "expected right paren")
|
||||
|
||||
mod_table = {
|
||||
# (neg, abs)
|
||||
(False, False): Mod.nop,
|
||||
(False, True): Mod.abs,
|
||||
(True, False): Mod.neg,
|
||||
(True, True): Mod.nab,
|
||||
}
|
||||
mod = mod_table[(neg, abs)]
|
||||
return SwizzleSel(
|
||||
sel_keyword,
|
||||
swizzle_identifier,
|
||||
mod,
|
||||
)
|
||||
|
||||
def operation(self):
|
||||
dest_addr_swizzles = []
|
||||
while not self.is_opcode():
|
||||
dest_addr_swizzles.append(self.dest_addr_swizzle())
|
||||
|
||||
opcode_keyword = self.consume(TT.keyword, "expected opcode keyword")
|
||||
|
||||
swizzle_sels = []
|
||||
while not (self.match(TT.comma) or self.match(TT.semicolon)):
|
||||
swizzle_sels.append(self.swizzle_sel())
|
||||
|
||||
return Operation(
|
||||
dest_addr_swizzles,
|
||||
opcode_keyword,
|
||||
swizzle_sels
|
||||
)
|
||||
|
||||
def instruction(self):
|
||||
let_expressions = []
|
||||
while not self.match(TT.colon):
|
||||
let_expressions.append(self.let_expression())
|
||||
if not self.match(TT.colon):
|
||||
self.consume(TT.comma, "expected comma")
|
||||
|
||||
self.consume(TT.colon, "expected colon")
|
||||
|
||||
operations = []
|
||||
while not self.match(TT.semicolon):
|
||||
operations.append(self.operation())
|
||||
if not self.match(TT.semicolon):
|
||||
self.consume(TT.comma, "expected comma")
|
||||
|
||||
self.consume(TT.semicolon, "expected semicolon")
|
||||
|
||||
return Instruction(
|
||||
let_expressions,
|
||||
operations,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from assembler.lexer import Lexer
|
||||
buf = b"""
|
||||
src0.a = float(0), src0.rgb = temp[0] :
|
||||
out[0].none = temp[0].none = MAD src0.r src0.r src0.r ,
|
||||
out[0].none = temp[0].r = DP3 src0.rg0 src0.rg0 ;
|
||||
"""
|
||||
lexer = Lexer(buf, find_keyword, emit_newlines=False)
|
||||
tokens = list(lexer.lex_tokens())
|
||||
parser = Parser(tokens)
|
||||
from pprint import pprint
|
||||
try:
|
||||
pprint(parser.instruction())
|
||||
except ParserError as e:
|
||||
print_error(None, buf, e)
|
||||
raise
|
||||
print(parser.peek())
|
||||
@ -1,9 +1,7 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from itertools import chain
|
||||
from typing import Union
|
||||
|
||||
from assembler import keywords
|
||||
from typing import Union, Any
|
||||
|
||||
DEBUG = True
|
||||
|
||||
@ -18,6 +16,10 @@ class TT(Enum):
|
||||
dot = auto()
|
||||
identifier = auto()
|
||||
keyword = auto()
|
||||
colon = auto()
|
||||
semicolon = auto()
|
||||
bar = auto()
|
||||
comma = auto()
|
||||
|
||||
@dataclass
|
||||
class Token:
|
||||
@ -26,7 +28,7 @@ class Token:
|
||||
col: int
|
||||
type: TT
|
||||
lexeme: memoryview
|
||||
keyword: Union[keywords.VE, keywords.ME, keywords.KW] = None
|
||||
keyword: Any = None
|
||||
|
||||
identifier_characters = set(chain(
|
||||
b'abcdefghijklmnopqrstuvwxyz'
|
||||
@ -39,12 +41,14 @@ class LexerError(Exception):
|
||||
pass
|
||||
|
||||
class Lexer:
|
||||
def __init__(self, buf: memoryview):
|
||||
def __init__(self, buf: memoryview, find_keyword, emit_newlines=True):
|
||||
self.start_ix = 0
|
||||
self.current_ix = 0
|
||||
self.buf = memoryview(buf)
|
||||
self.line = 1
|
||||
self.col = 0
|
||||
self.find_keyword = find_keyword
|
||||
self.emit_newlines = emit_newlines
|
||||
|
||||
def at_end_p(self):
|
||||
return self.current_ix >= len(self.buf)
|
||||
@ -70,7 +74,7 @@ class Lexer:
|
||||
def identifier(self):
|
||||
while not self.at_end_p() and self.peek() in identifier_characters:
|
||||
self.advance()
|
||||
keyword = keywords.find_keyword(self.lexeme())
|
||||
keyword = self.find_keyword(self.lexeme())
|
||||
if keyword is not None:
|
||||
return Token(*self.pos(), TT.keyword, self.lexeme(), keyword)
|
||||
else:
|
||||
@ -96,7 +100,15 @@ class Lexer:
|
||||
return Token(*self.pos(), TT.equal, self.lexeme())
|
||||
elif c == ord('.'):
|
||||
return Token(*self.pos(), TT.dot, self.lexeme())
|
||||
elif c == ord('|'):
|
||||
return Token(*self.pos(), TT.bar, self.lexeme())
|
||||
elif c == ord(':'):
|
||||
return Token(*self.pos(), TT.colon, self.lexeme())
|
||||
elif c == ord(';'):
|
||||
return Token(*self.pos(), TT.semicolon, self.lexeme())
|
||||
elif c == ord(','):
|
||||
return Token(*self.pos(), TT.comma, self.lexeme())
|
||||
elif c == ord('#'):
|
||||
while not self.at_end_p() and self.peek() != ord('\n'):
|
||||
self.advance()
|
||||
elif c == ord(' ') or c == ord('\r') or c == ord('\t'):
|
||||
@ -105,11 +117,15 @@ class Lexer:
|
||||
pos = self.pos()
|
||||
self.line += 1
|
||||
self.col = 0
|
||||
return Token(*pos, TT.eol, self.lexeme())
|
||||
if self.emit_newlines:
|
||||
return Token(*pos, TT.eol, self.lexeme())
|
||||
else:
|
||||
continue
|
||||
elif c in identifier_characters:
|
||||
return self.identifier()
|
||||
else:
|
||||
raise LexerError(f"unexpected character at line:{self.line} col:{self.col}")
|
||||
token = Token(*self.pos(), None, self.lexeme())
|
||||
raise LexerError(f"unexpected character at line:{self.line} col:{self.col}", token)
|
||||
|
||||
def lex_tokens(self):
|
||||
while True:
|
||||
@ -119,7 +135,16 @@ class Lexer:
|
||||
break
|
||||
|
||||
if __name__ == "__main__":
|
||||
test = b"out[0].xz = VE_MAD input[0].-y-_-0-_ temp[0].x_0_ temp[0].y_0_"
|
||||
lexer = Lexer(test)
|
||||
for token in lexer.lex_tokens():
|
||||
print(token)
|
||||
def vs_test():
|
||||
from assembler.vskeywords import find_keyword
|
||||
test = b"out[0].xz = VE_MAD input[0].-y-_-0-_ temp[0].x_0_ temp[0].y_0_"
|
||||
lexer = Lexer(test, find_keyword)
|
||||
for token in lexer.lex_tokens():
|
||||
print(token)
|
||||
def fs_test():
|
||||
from assembler.fs.keywords import find_keyword
|
||||
test = b"src0.rgb = temp[0] : temp[0].a = OP_RSQ |src0.r| ;"
|
||||
lexer = Lexer(test, find_keyword)
|
||||
for token in lexer.lex_tokens():
|
||||
print(token)
|
||||
fs_test()
|
||||
|
||||
@ -1,125 +1,15 @@
|
||||
from itertools import pairwise
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
from assembler import lexer
|
||||
from assembler.lexer import TT, Token
|
||||
from assembler.keywords import KW, ME, VE
|
||||
|
||||
"""
|
||||
temp[0].xyzw = VE_ADD const[1].xyzw const[1].0000 const[1].0000
|
||||
temp[1].xyzw = VE_ADD const[1].xyzw const[1].0000 const[1].0000
|
||||
temp[0].x = VE_MAD const[0].x___ temp[1].x___ temp[0].y___
|
||||
temp[0].x = VE_FRC temp[0].x___ temp[0].0000 temp[0].0000
|
||||
temp[0].x = VE_MAD temp[0].x___ const[1].z___ const[1].w___
|
||||
temp[0].y = ME_COS temp[0].xxxx temp[0].0000 temp[0].0000
|
||||
temp[0].x = ME_SIN temp[0].xxxx temp[0].0000 temp[0].0000
|
||||
temp[0].yz = VE_MUL input[0]._xy_ temp[0]._yy_ temp[0].0000
|
||||
out[0].xz = VE_MAD input[0].-y-_-0-_ temp[0].x_0_ temp[0].y_0_
|
||||
out[0].yw = VE_MAD input[0]._x_0 temp[0]._x_0 temp[0]._z_1
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class DestinationOp:
|
||||
type: KW
|
||||
offset: int
|
||||
write_enable: set[int]
|
||||
opcode: Union[VE, ME]
|
||||
sat: bool
|
||||
macro: bool
|
||||
|
||||
@dataclass
|
||||
class SourceSwizzle:
|
||||
select: tuple[int, int, int, int]
|
||||
modifier: tuple[bool, bool, bool, bool]
|
||||
|
||||
@dataclass
|
||||
class Source:
|
||||
type: KW
|
||||
offset: int
|
||||
swizzle: SourceSwizzle
|
||||
|
||||
@dataclass
|
||||
class Instruction:
|
||||
destination_op: DestinationOp
|
||||
source0: Source
|
||||
source1: Source
|
||||
source2: Source
|
||||
from typing import Any
|
||||
|
||||
class ParserError(Exception):
|
||||
pass
|
||||
|
||||
def identifier_to_number(token):
|
||||
digits = set(b"0123456789")
|
||||
|
||||
assert token.type is TT.identifier
|
||||
if not all(d in digits for d in token.lexeme):
|
||||
raise ParserError("expected number", token)
|
||||
return int(bytes(token.lexeme), 10)
|
||||
|
||||
def we_ord(c):
|
||||
if c == ord("w"):
|
||||
return 3
|
||||
else:
|
||||
return c - ord("x")
|
||||
|
||||
def parse_dest_write_enable(token):
|
||||
we_chars = set(b"xyzw")
|
||||
assert token.type is TT.identifier
|
||||
we = bytes(token.lexeme).lower()
|
||||
if not all(c in we_chars for c in we):
|
||||
raise ParserError("expected destination write enable", token)
|
||||
if not all(we_ord(a) < we_ord(b) for a, b in pairwise(we)) or len(set(we)) != len(we):
|
||||
raise ParserError("misleading non-sequential write enable", token)
|
||||
return set(we_ord(c) for c in we)
|
||||
|
||||
def parse_source_swizzle(token):
|
||||
select_mapping = {
|
||||
ord('x'): 0,
|
||||
ord('y'): 1,
|
||||
ord('z'): 2,
|
||||
ord('w'): 3,
|
||||
ord('0'): 4,
|
||||
ord('1'): 5,
|
||||
ord('h'): 6,
|
||||
ord('_'): 7,
|
||||
ord('u'): 7,
|
||||
}
|
||||
state = 0
|
||||
ix = 0
|
||||
swizzle_selects = [None] * 4
|
||||
swizzle_modifiers = [None] * 4
|
||||
lexeme = bytes(token.lexeme).lower()
|
||||
while state < 4:
|
||||
if ix >= len(token.lexeme):
|
||||
raise ParserError("invalid source swizzle", token)
|
||||
c = lexeme[ix]
|
||||
if c == ord('-'):
|
||||
if (swizzle_modifiers[state] is not None) or (swizzle_selects[state] is not None):
|
||||
raise ParserError("invalid source swizzle modifier", token)
|
||||
swizzle_modifiers[state] = True
|
||||
elif c in select_mapping:
|
||||
if swizzle_selects[state] is not None:
|
||||
raise ParserError("invalid source swizzle select", token)
|
||||
swizzle_selects[state] = select_mapping[c]
|
||||
if swizzle_modifiers[state] is None:
|
||||
swizzle_modifiers[state] = False
|
||||
state += 1
|
||||
else:
|
||||
raise ParserError("invalid source swizzle", token)
|
||||
ix += 1
|
||||
if ix != len(lexeme):
|
||||
raise ParserError("invalid source swizzle", token)
|
||||
return SourceSwizzle(swizzle_selects, swizzle_modifiers)
|
||||
|
||||
class Parser:
|
||||
def __init__(self, tokens: list[lexer.Token]):
|
||||
class BaseParser:
|
||||
def __init__(self, tokens: list[Any]):
|
||||
self.current_ix = 0
|
||||
self.tokens = tokens
|
||||
|
||||
def peek(self, offset=0):
|
||||
token = self.tokens[self.current_ix + offset]
|
||||
#print(token)
|
||||
return token
|
||||
|
||||
def at_end_p(self):
|
||||
@ -145,100 +35,3 @@ class Parser:
|
||||
if token.type != token_type1 and token.type != token_type2:
|
||||
raise ParserError(message, token)
|
||||
return token
|
||||
|
||||
def destination_type(self):
|
||||
token = self.consume(TT.keyword, "expected destination type")
|
||||
destination_keywords = {KW.temporary, KW.a0, KW.out, KW.out_repl_x, KW.alt_temporary, KW.input}
|
||||
if token.keyword not in destination_keywords:
|
||||
raise ParserError("expected destination type", token)
|
||||
return token.keyword
|
||||
|
||||
def offset(self):
|
||||
self.consume(TT.left_square, "expected offset")
|
||||
identifier_token = self.consume(TT.identifier, "expected offset")
|
||||
value = identifier_to_number(identifier_token)
|
||||
self.consume(TT.right_square, "expected offset")
|
||||
return value
|
||||
|
||||
def opcode(self):
|
||||
token = self.consume(TT.keyword, "expected opcode")
|
||||
if type(token.keyword) != VE and type(token.keyword) != ME:
|
||||
raise ParserError("expected opcode", token)
|
||||
return token.keyword
|
||||
|
||||
def destination_op(self):
|
||||
destination_type = self.destination_type()
|
||||
offset_value = self.offset()
|
||||
self.consume(TT.dot, "expected write enable")
|
||||
write_enable_token = self.consume(TT.identifier, "expected write enable token")
|
||||
write_enable = parse_dest_write_enable(write_enable_token)
|
||||
self.consume(TT.equal, "expected equals")
|
||||
opcode = self.opcode()
|
||||
sat = False
|
||||
if self.match(TT.dot):
|
||||
self.advance()
|
||||
suffix = self.consume(TT.keyword, "expected saturation suffix")
|
||||
if suffix.keyword is not KW.saturation:
|
||||
raise ParserError("expected saturation suffix", token)
|
||||
sat = True
|
||||
|
||||
macro = False
|
||||
return DestinationOp(type=destination_type,
|
||||
offset=offset_value,
|
||||
write_enable=write_enable,
|
||||
opcode=opcode,
|
||||
sat=sat,
|
||||
macro=macro)
|
||||
|
||||
def source_type(self):
|
||||
token = self.consume(TT.keyword, "expected source type")
|
||||
source_keywords = {KW.temporary, KW.input, KW.constant, KW.alt_temporary}
|
||||
if token.keyword not in source_keywords:
|
||||
raise ParserError("expected source type", token)
|
||||
return token.keyword
|
||||
|
||||
def source_swizzle(self):
|
||||
token = self.consume(TT.identifier, "expected source swizzle")
|
||||
return parse_source_swizzle(token)
|
||||
|
||||
def source(self):
|
||||
"input[0].-y-_-0-_"
|
||||
source_type = self.source_type()
|
||||
offset = self.offset()
|
||||
self.consume(TT.dot, "expected source swizzle")
|
||||
source_swizzle = self.source_swizzle()
|
||||
return Source(source_type, offset, source_swizzle)
|
||||
|
||||
def instruction(self):
|
||||
while self.match(TT.eol):
|
||||
self.advance()
|
||||
first_token = self.peek()
|
||||
destination_op = self.destination_op()
|
||||
source0 = self.source()
|
||||
if self.match(TT.eol) or self.match(TT.eof):
|
||||
source1 = None
|
||||
else:
|
||||
source1 = self.source()
|
||||
if self.match(TT.eol) or self.match(TT.eof):
|
||||
source2 = None
|
||||
else:
|
||||
source2 = self.source()
|
||||
last_token = self.peek(-1)
|
||||
self.consume_either(TT.eol, TT.eof, "expected newline or EOF")
|
||||
return (
|
||||
Instruction(destination_op, source0, source1, source2),
|
||||
(first_token.start_ix, last_token.start_ix + len(last_token.lexeme))
|
||||
)
|
||||
|
||||
def instructions(self):
|
||||
while not self.match(TT.eof):
|
||||
yield self.instruction()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from assembler.lexer import Lexer
|
||||
buf = b"out[0].xz = VE_MAD input[0].-y-_-0-_ temp[0].x_0_ temp[0].y_0_"
|
||||
lexer = Lexer(buf)
|
||||
tokens = list(lexer.lex_tokens())
|
||||
parser = Parser(tokens)
|
||||
from pprint import pprint
|
||||
pprint(parser.instruction())
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import sys
|
||||
|
||||
from assembler.lexer import Lexer, LexerError
|
||||
from assembler.parser import Parser, ParserError
|
||||
from assembler.emitter import emit_instruction
|
||||
from assembler.validator import validate_instruction
|
||||
from assembler.vs.keywords import find_keyword
|
||||
from assembler.vs.parser import Parser, ParserError
|
||||
from assembler.vs.emitter import emit_instruction
|
||||
from assembler.vs.validator import validate_instruction
|
||||
|
||||
sample = b"""
|
||||
temp[0].xyzw = VE_ADD const[1].xyzw const[1].0000 const[1].0000
|
||||
@ -19,40 +20,13 @@ out[0].yw = VE_MAD input[0]._x_0 temp[0]._x_0 temp[0]._z_1
|
||||
"""
|
||||
|
||||
def frontend_inner(buf):
|
||||
lexer = Lexer(buf)
|
||||
lexer = Lexer(buf, find_keyword)
|
||||
tokens = list(lexer.lex_tokens())
|
||||
parser = Parser(tokens)
|
||||
for ins, start_end in parser.instructions():
|
||||
ins = validate_instruction(ins)
|
||||
yield list(emit_instruction(ins)), start_end
|
||||
|
||||
def print_error(filename, buf, e):
|
||||
assert len(e.args) == 2, e
|
||||
message, token = e.args
|
||||
lines = buf.splitlines()
|
||||
line = lines[token.line - 1]
|
||||
|
||||
error_name = str(type(e).__name__)
|
||||
col_indent = ' ' * token.col
|
||||
col_pointer = '^' * len(token.lexeme)
|
||||
RED = "\033[0;31m"
|
||||
DEFAULT = "\033[0;0m"
|
||||
print(f'File: "{filename}", line {token.line}, column {token.col}\n', file=sys.stderr)
|
||||
sys.stderr.write(' ')
|
||||
wrote_default = False
|
||||
for i, c in enumerate(line.decode('utf-8')):
|
||||
if i == token.col:
|
||||
sys.stderr.write(RED)
|
||||
sys.stderr.write(c)
|
||||
if i == token.col + len(token.lexeme):
|
||||
wrote_default = True
|
||||
sys.stderr.write(DEFAULT)
|
||||
if not wrote_default:
|
||||
sys.stderr.write(DEFAULT)
|
||||
sys.stderr.write('\n')
|
||||
print(f" {RED}{col_indent}{col_pointer}{DEFAULT}", file=sys.stderr)
|
||||
print(f'{RED}{error_name}{DEFAULT}: {message}', file=sys.stderr)
|
||||
|
||||
def frontend(filename, buf):
|
||||
try:
|
||||
yield from frontend_inner(buf)
|
||||
@ -1,5 +1,5 @@
|
||||
from assembler.keywords import ME, VE, MVE, KW
|
||||
from assembler.parser import Instruction, DestinationOp, Source
|
||||
from assembler.vs.keywords import ME, VE, MVE, KW
|
||||
from assembler.vs.parser import Instruction, DestinationOp, Source
|
||||
import pvs_dst
|
||||
import pvs_src
|
||||
import pvs_dst_bits
|
||||
208
regs/assembler/vs/parser.py
Normal file
208
regs/assembler/vs/parser.py
Normal file
@ -0,0 +1,208 @@
|
||||
from itertools import pairwise
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
from assembler.parser import BaseParser, ParserError
|
||||
from assembler.lexer import TT
|
||||
from assembler.vs.keywords import KW, ME, VE, find_keyword
|
||||
|
||||
"""
|
||||
temp[0].xyzw = VE_ADD const[1].xyzw const[1].0000 const[1].0000
|
||||
temp[1].xyzw = VE_ADD const[1].xyzw const[1].0000 const[1].0000
|
||||
temp[0].x = VE_MAD const[0].x___ temp[1].x___ temp[0].y___
|
||||
temp[0].x = VE_FRC temp[0].x___ temp[0].0000 temp[0].0000
|
||||
temp[0].x = VE_MAD temp[0].x___ const[1].z___ const[1].w___
|
||||
temp[0].y = ME_COS temp[0].xxxx temp[0].0000 temp[0].0000
|
||||
temp[0].x = ME_SIN temp[0].xxxx temp[0].0000 temp[0].0000
|
||||
temp[0].yz = VE_MUL input[0]._xy_ temp[0]._yy_ temp[0].0000
|
||||
out[0].xz = VE_MAD input[0].-y-_-0-_ temp[0].x_0_ temp[0].y_0_
|
||||
out[0].yw = VE_MAD input[0]._x_0 temp[0]._x_0 temp[0]._z_1
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class DestinationOp:
|
||||
type: KW
|
||||
offset: int
|
||||
write_enable: set[int]
|
||||
opcode: Union[VE, ME]
|
||||
sat: bool
|
||||
macro: bool
|
||||
|
||||
@dataclass
|
||||
class SourceSwizzle:
|
||||
select: tuple[int, int, int, int]
|
||||
modifier: tuple[bool, bool, bool, bool]
|
||||
|
||||
@dataclass
|
||||
class Source:
|
||||
type: KW
|
||||
offset: int
|
||||
swizzle: SourceSwizzle
|
||||
|
||||
@dataclass
|
||||
class Instruction:
|
||||
destination_op: DestinationOp
|
||||
source0: Source
|
||||
source1: Source
|
||||
source2: Source
|
||||
|
||||
def identifier_to_number(token):
|
||||
digits = set(b"0123456789")
|
||||
|
||||
assert token.type is TT.identifier
|
||||
if not all(d in digits for d in token.lexeme):
|
||||
raise ParserError("expected number", token)
|
||||
return int(bytes(token.lexeme), 10)
|
||||
|
||||
def we_ord(c):
|
||||
if c == ord("w"):
|
||||
return 3
|
||||
else:
|
||||
return c - ord("x")
|
||||
|
||||
def parse_dest_write_enable(token):
|
||||
we_chars = set(b"xyzw")
|
||||
assert token.type is TT.identifier
|
||||
we = bytes(token.lexeme).lower()
|
||||
if not all(c in we_chars for c in we):
|
||||
raise ParserError("expected destination write enable", token)
|
||||
if not all(we_ord(a) < we_ord(b) for a, b in pairwise(we)) or len(set(we)) != len(we):
|
||||
raise ParserError("misleading non-sequential write enable", token)
|
||||
return set(we_ord(c) for c in we)
|
||||
|
||||
def parse_source_swizzle(token):
|
||||
select_mapping = {
|
||||
ord('x'): 0,
|
||||
ord('y'): 1,
|
||||
ord('z'): 2,
|
||||
ord('w'): 3,
|
||||
ord('0'): 4,
|
||||
ord('1'): 5,
|
||||
ord('h'): 6,
|
||||
ord('_'): 7,
|
||||
ord('u'): 7,
|
||||
}
|
||||
state = 0
|
||||
ix = 0
|
||||
swizzle_selects = [None] * 4
|
||||
swizzle_modifiers = [None] * 4
|
||||
lexeme = bytes(token.lexeme).lower()
|
||||
while state < 4:
|
||||
if ix >= len(token.lexeme):
|
||||
raise ParserError("invalid source swizzle", token)
|
||||
c = lexeme[ix]
|
||||
if c == ord('-'):
|
||||
if (swizzle_modifiers[state] is not None) or (swizzle_selects[state] is not None):
|
||||
raise ParserError("invalid source swizzle modifier", token)
|
||||
swizzle_modifiers[state] = True
|
||||
elif c in select_mapping:
|
||||
if swizzle_selects[state] is not None:
|
||||
raise ParserError("invalid source swizzle select", token)
|
||||
swizzle_selects[state] = select_mapping[c]
|
||||
if swizzle_modifiers[state] is None:
|
||||
swizzle_modifiers[state] = False
|
||||
state += 1
|
||||
else:
|
||||
raise ParserError("invalid source swizzle", token)
|
||||
ix += 1
|
||||
if ix != len(lexeme):
|
||||
raise ParserError("invalid source swizzle", token)
|
||||
return SourceSwizzle(swizzle_selects, swizzle_modifiers)
|
||||
|
||||
class Parser(BaseParser):
|
||||
def destination_type(self):
|
||||
token = self.consume(TT.keyword, "expected destination type")
|
||||
destination_keywords = {KW.temporary, KW.a0, KW.out, KW.out_repl_x, KW.alt_temporary, KW.input}
|
||||
if token.keyword not in destination_keywords:
|
||||
raise ParserError("expected destination type", token)
|
||||
return token.keyword
|
||||
|
||||
def offset(self):
|
||||
self.consume(TT.left_square, "expected offset")
|
||||
identifier_token = self.consume(TT.identifier, "expected offset")
|
||||
value = identifier_to_number(identifier_token)
|
||||
self.consume(TT.right_square, "expected offset")
|
||||
return value
|
||||
|
||||
def opcode(self):
|
||||
token = self.consume(TT.keyword, "expected opcode")
|
||||
if type(token.keyword) != VE and type(token.keyword) != ME:
|
||||
raise ParserError("expected opcode", token)
|
||||
return token.keyword
|
||||
|
||||
def destination_op(self):
|
||||
destination_type = self.destination_type()
|
||||
offset_value = self.offset()
|
||||
self.consume(TT.dot, "expected write enable")
|
||||
write_enable_token = self.consume(TT.identifier, "expected write enable token")
|
||||
write_enable = parse_dest_write_enable(write_enable_token)
|
||||
self.consume(TT.equal, "expected equals")
|
||||
opcode = self.opcode()
|
||||
sat = False
|
||||
if self.match(TT.dot):
|
||||
self.advance()
|
||||
suffix = self.consume(TT.keyword, "expected saturation suffix")
|
||||
if suffix.keyword is not KW.saturation:
|
||||
raise ParserError("expected saturation suffix", token)
|
||||
sat = True
|
||||
|
||||
macro = False
|
||||
return DestinationOp(type=destination_type,
|
||||
offset=offset_value,
|
||||
write_enable=write_enable,
|
||||
opcode=opcode,
|
||||
sat=sat,
|
||||
macro=macro)
|
||||
|
||||
def source_type(self):
|
||||
token = self.consume(TT.keyword, "expected source type")
|
||||
source_keywords = {KW.temporary, KW.input, KW.constant, KW.alt_temporary}
|
||||
if token.keyword not in source_keywords:
|
||||
raise ParserError("expected source type", token)
|
||||
return token.keyword
|
||||
|
||||
def source_swizzle(self):
|
||||
token = self.consume(TT.identifier, "expected source swizzle")
|
||||
return parse_source_swizzle(token)
|
||||
|
||||
def source(self):
|
||||
"input[0].-y-_-0-_"
|
||||
source_type = self.source_type()
|
||||
offset = self.offset()
|
||||
self.consume(TT.dot, "expected source swizzle")
|
||||
source_swizzle = self.source_swizzle()
|
||||
return Source(source_type, offset, source_swizzle)
|
||||
|
||||
def instruction(self):
|
||||
while self.match(TT.eol):
|
||||
self.advance()
|
||||
first_token = self.peek()
|
||||
destination_op = self.destination_op()
|
||||
source0 = self.source()
|
||||
if self.match(TT.eol) or self.match(TT.eof):
|
||||
source1 = None
|
||||
else:
|
||||
source1 = self.source()
|
||||
if self.match(TT.eol) or self.match(TT.eof):
|
||||
source2 = None
|
||||
else:
|
||||
source2 = self.source()
|
||||
last_token = self.peek(-1)
|
||||
self.consume_either(TT.eol, TT.eof, "expected newline or EOF")
|
||||
return (
|
||||
Instruction(destination_op, source0, source1, source2),
|
||||
(first_token.start_ix, last_token.start_ix + len(last_token.lexeme))
|
||||
)
|
||||
|
||||
def instructions(self):
|
||||
while not self.match(TT.eof):
|
||||
yield self.instruction()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from assembler.lexer import Lexer
|
||||
buf = b"out[0].xz = VE_MAD input[0].-y-_-0-_ temp[0].x_0_ temp[0].y_0_"
|
||||
lexer = Lexer(buf, find_keyword)
|
||||
tokens = list(lexer.lex_tokens())
|
||||
parser = Parser(tokens)
|
||||
from pprint import pprint
|
||||
pprint(parser.instruction())
|
||||
@ -1,4 +1,4 @@
|
||||
from assembler.keywords import ME, VE, macro_vector_operations
|
||||
from assembler.vs.keywords import ME, VE, macro_vector_operations
|
||||
|
||||
class ValidatorError(Exception):
|
||||
pass
|
||||
Loading…
x
Reference in New Issue
Block a user