From 28358b55fe228d8987d6864c3894a12ead4e3223 Mon Sep 17 00:00:00 2001 From: Rangi <35663410+Rangi42@users.noreply.github.com> Date: Sun, 5 Nov 2023 13:13:33 -0500 Subject: [PATCH] Separate multiple instructions per line with `::` (#1210) --- man/rgbasm.5 | 52 +++++++++++++++++-------- src/asm/lexer.c | 46 +++++++++++----------- src/asm/parser.y | 15 ++++--- test/asm/anon-label-bad.err | 2 +- test/asm/macro-syntax.err | 4 +- test/asm/macro-syntax.simple.err | 4 +- test/asm/multiple-instructions.asm | 11 ++++++ test/asm/multiple-instructions.err | 0 test/asm/multiple-instructions.out | 0 test/asm/multiple-instructions.out.bin | Bin 0 -> 22 bytes 10 files changed, 88 insertions(+), 46 deletions(-) create mode 100644 test/asm/multiple-instructions.asm create mode 100644 test/asm/multiple-instructions.err create mode 100644 test/asm/multiple-instructions.out create mode 100644 test/asm/multiple-instructions.out.bin diff --git a/man/rgbasm.5 b/man/rgbasm.5 index d75a92fa..4175da99 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -24,35 +24,55 @@ but any program that processes RGBDS object files (described in .Xr rgbds 5 ) can be used in its place. .Sh SYNTAX -The syntax is line-based, just as in any other assembler, meaning that you do one instruction or directive per line: +The syntax is line-based, just as in any other assembler. +Each line may have components in this order: .Pp -.Dl Oo Ar label Oc Oo Ar instruction Oc Oo Ar ;\ comment Oc +.Dl Oo Ar directive Oc Oo Ar ;\ comment Oc +.Dl Oo Ar label: Oc Oo Ar instruction Oo Ar :: instruction ... Oc Oc Oo Ar ;\ comment Oc .Pp -Example: -.Bd -literal -offset indent -John: ld a,87 ;Weee -.Ed +Directives are commands to the assembler itself, such as +.Ic PRINTLN , +.Ic SECTION , +or +.Ic OPT . .Pp -All reserved keywords (directives, mnemonics, registers, etc.) are case-insensitive; -all identifiers (symbol names) are case-sensitive. +Labels tie a name to a specific location within a section (see +.Sx Labels +below). +They must come first in the line. +.Pp +Instructions are assembled into Game Boy opcodes. +Multiple instructions on one line can be separated by double colons +.Ql :: . +.Pp +All reserved keywords (directives, register names, etc.) are case-insensitive; +all identifiers (labels and other symbol names) are case-sensitive. .Pp Comments are used to give humans information about the code, such as explanations. The assembler .Em always ignores comments and their contents. .Pp -There are two syntaxes for comments. -The most common is that anything that follows a semicolon +There are two kinds of comments, inline and block. +Inline comments are anything that follows a semicolon .Ql \&; -not inside a string, is a comment until the end of the line. -The second is a block comment, beginning with +not inside a string, until the end of the line. +Block comments, beginning with .Ql /* and ending with -.Ql */ . -It can be split across multiple lines, or occur in the middle of an expression: +.Ql */ , +can be split across multiple lines, or occur in the middle of an expression. +.Pp +An example demonstrating these syntax features: .Bd -literal -offset indent -DEF X = /* the value of x - should be 3 */ 3 +SECTION "My Code", ROM0\ \ ;\ a directive +MyFunction:\ \ \ \ \ \ \ \ \ \ \ \ \ \ ;\ a label + push hl\ \ \ \ \ \ \ \ \ \ \ \ \ \ ;\ an instruction + /* ...and multiple instructions, + with mixed case */ + ld a, [hli] :: LD H, [HL] :: Ld l, a + pop /*wait for it*/ hl + ret .Ed .Pp Sometimes lines can be too long and it may be necessary to split them. diff --git a/src/asm/lexer.c b/src/asm/lexer.c index e80cd90d..034374b6 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -1759,11 +1759,8 @@ static int yylex_SKIP_TO_ENDC(void); // forward declaration for yylex_NORMAL static int yylex_NORMAL(void) { - uint32_t num = 0; - for (;;) { int c = nextChar(); - char secondChar; switch (c) { // Ignore whitespace and comments @@ -1910,15 +1907,19 @@ static int yylex_NORMAL(void) return T_OP_LOGICGT; } - // Handle colon, which may begin an anonymous label ref - - case ':': + case ':': // Either :, ::, or an anonymous label ref c = peek(); - if (c != '+' && c != '-') + switch (c) { + case ':': + shiftChar(); + return T_DOUBLE_COLON; + case '+': + case '-': + readAnonLabelRef(c); + return T_ANON; + default: return T_COLON; - - readAnonLabelRef(c); - return T_ANON; + } // Handle numbers @@ -1931,36 +1932,37 @@ static int yylex_NORMAL(void) case '6': case '7': case '8': - case '9': - num = readNumber(10, c - '0'); + case '9': { + uint32_t n = readNumber(10, c - '0'); + if (peek() == '.') { shiftChar(); - yylval.constValue = readFractionalPart(num); - } else { - yylval.constValue = num; + n = readFractionalPart(n); } + yylval.constValue = n; return T_NUMBER; + } case '&': // Either &=, binary AND, logical AND, or an octal constant - secondChar = peek(); - if (secondChar == '=') { + c = peek(); + if (c == '=') { shiftChar(); return T_POP_ANDEQ; - } else if (secondChar == '&') { + } else if (c == '&') { shiftChar(); return T_OP_LOGICAND; - } else if (secondChar >= '0' && secondChar <= '7') { + } else if (c >= '0' && c <= '7') { yylval.constValue = readNumber(8, 0); return T_NUMBER; } return T_OP_AND; case '%': // Either %=, MOD, or a binary constant - secondChar = peek(); - if (secondChar == '=') { + c = peek(); + if (c == '=') { shiftChar(); return T_POP_MODEQ; - } else if (secondChar == binDigits[0] || secondChar == binDigits[1]) { + } else if (c == binDigits[0] || c == binDigits[1]) { yylval.constValue = readBinaryNumber(); return T_NUMBER; } diff --git a/src/asm/parser.y b/src/asm/parser.y index daef5a3d..fe24eb16 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -542,7 +542,7 @@ enum { %token T_PERIOD "." %token T_COMMA "," -%token T_COLON ":" +%token T_COLON ":" T_DOUBLE_COLON "::" %token T_LBRACK "[" T_RBRACK "]" %token T_LPAREN "(" T_RPAREN ")" %token T_NEWLINE "newline" @@ -817,7 +817,7 @@ else : T_POP_ELSE T_NEWLINE { ; plain_directive : label - | label cpu_command + | label cpu_commands | label macro | label directive | assignment_directive @@ -844,7 +844,9 @@ redef_id : T_POP_REDEF { } ; -scoped_id : T_ID | T_LOCAL_ID; +// T_LABEL covers identifiers followed by a double colon (e.g. `call Function::ret`, +// to be read as `call Function :: ret`). This should not conflict with anything. +scoped_id : T_ID | T_LOCAL_ID | T_LABEL; scoped_anon_id : scoped_id | T_ANON; label : %empty @@ -860,11 +862,11 @@ label : %empty | T_LABEL T_COLON { sym_AddLabel($1); } - | T_LOCAL_ID T_COLON T_COLON { + | T_LOCAL_ID T_DOUBLE_COLON { sym_AddLocalLabel($1); sym_Export($1); } - | T_LABEL T_COLON T_COLON { + | T_LABEL T_DOUBLE_COLON { sym_AddLabel($1); sym_Export($1); } @@ -1776,6 +1778,9 @@ sectattrs : %empty { } ; +cpu_commands : cpu_command + | cpu_command T_DOUBLE_COLON cpu_commands +; cpu_command : z80_adc | z80_add diff --git a/test/asm/anon-label-bad.err b/test/asm/anon-label-bad.err index c63b1734..9c85f37c 100644 --- a/test/asm/anon-label-bad.err +++ b/test/asm/anon-label-bad.err @@ -3,5 +3,5 @@ error: anon-label-bad.asm(2): error: anon-label-bad.asm(6): Reference to anonymous label 2 before, when only 1 has been created so far error: anon-label-bad.asm(18): - syntax error, unexpected : + syntax error, unexpected :: error: Assembly aborted (3 errors)! diff --git a/test/asm/macro-syntax.err b/test/asm/macro-syntax.err index b535501b..f7cc6d32 100644 --- a/test/asm/macro-syntax.err +++ b/test/asm/macro-syntax.err @@ -1,7 +1,9 @@ +error: macro-syntax.asm(7): + Label "old" created outside of a SECTION error: macro-syntax.asm(7): syntax error, unexpected MACRO error: macro-syntax.asm(8): Macro argument '\1' not defined error: macro-syntax.asm(9): syntax error, unexpected ENDM -error: Assembly aborted (3 errors)! +error: Assembly aborted (4 errors)! diff --git a/test/asm/macro-syntax.simple.err b/test/asm/macro-syntax.simple.err index 453c1264..ba2db6d9 100644 --- a/test/asm/macro-syntax.simple.err +++ b/test/asm/macro-syntax.simple.err @@ -1,7 +1,9 @@ +error: macro-syntax.asm(7): + Label "old" created outside of a SECTION error: macro-syntax.asm(7): syntax error error: macro-syntax.asm(8): Macro argument '\1' not defined error: macro-syntax.asm(9): syntax error -error: Assembly aborted (3 errors)! +error: Assembly aborted (4 errors)! diff --git a/test/asm/multiple-instructions.asm b/test/asm/multiple-instructions.asm new file mode 100644 index 00000000..0a7afb07 --- /dev/null +++ b/test/asm/multiple-instructions.asm @@ -0,0 +1,11 @@ +SECTION "test", ROM0 + +push hl :: pop hl :: ret + +Label: nop :: call z, .local :: ld b, a +.local push bc :: jr z, Label :: pop bc + nop :: ld a, \ + b :: ret + +Label2::jr Label2::ret +.local2::call nz, .local2::ret diff --git a/test/asm/multiple-instructions.err b/test/asm/multiple-instructions.err new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/multiple-instructions.out b/test/asm/multiple-instructions.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/multiple-instructions.out.bin b/test/asm/multiple-instructions.out.bin new file mode 100644 index 0000000000000000000000000000000000000000..643ee9436de0945b93fef362d526b4c4a791b53c GIT binary patch literal 22 ecmaF*@Fc?-4hHw58b1y)RGgIfck+l3!$|;w#|sbu literal 0 HcmV?d00001