tree-sitter-m68k
Version:
Motorola 68000 family assembly grammar for tree-sitter
1,506 lines (1,375 loc) • 31.8 kB
JavaScript
// TODO:
// C Style macros
// Extern keyword
const binaryOperators = [
"||",
"&&",
choice("==", "!=", "=", "<>"),
choice("<", ">", ">=", "<="),
choice("+", "-"),
choice("*", "/", "%", "//"),
choice("|", "!"),
choice("^", "~"),
"&",
choice("<<", ">>"),
];
const PREC = {
unary: binaryOperators.length + 1,
address: 1,
definition: 2,
};
const instructions = [
"bchg",
"bclr",
"bset",
"btst",
"lsr",
"move",
"tst",
"moves",
"wddata",
"neg",
"negx",
"not",
"or",
"ori",
"addq",
"addi",
"addx",
"and",
"andi",
"asl",
"asr",
"cas",
"chk2",
"clr",
"cmp",
"cmpi",
"cmpm",
"cmp2",
"eor",
"eori",
"rol",
"ror",
"roxl",
"roxr",
"sub",
"subi",
"subq",
"subx",
"tbls",
"tblsn",
"tblu",
"tblun",
"add",
"lsl",
"fsincos",
"ftst",
"maaac",
"mac",
"traplo",
"masac",
"trapne",
"trapeq",
"movea",
"movem",
"traple",
"movep",
"movm",
"msaac",
"msac",
"mssac",
"muls",
"mulu",
"pbbs",
"pbbc",
"adda",
"pblc",
"pbss",
"pbls",
"pbsc",
"pbas",
"pbac",
"pbws",
"pbwc",
"pbis",
"pbic",
"pbgs",
"pbgc",
"pbcs",
"pbcc",
"cas2",
"chk",
"cmpa",
"ptrapbs",
"ptrapbc",
"ptrapls",
"ptraplc",
"ptrapss",
"ptrapsc",
"ptrapas",
"ptrapac",
"ptrapws",
"ptrapwc",
"ptrapis",
"divs",
"ptrapic",
"ptrapgs",
"divu",
"ptrapgc",
"ptrapcs",
"ptrapcc",
"ext",
"fbf",
"fbeq",
"fbogt",
"fboge",
"fbolt",
"fbole",
"fbogl",
"fbor",
"fbun",
"fbueq",
"fbugt",
"fbuge",
"fbult",
"fbule",
"fbne",
"fbt",
"fbsf",
"fbseq",
"fbgt",
"fbge",
"fblt",
"fble",
"fbgl",
"fbgle",
"suba",
"fbngle",
"fbngl",
"fbnle",
"fbnlt",
"fbnge",
"fbngt",
"fbsne",
"fbst",
"tpf",
"trapt",
"trapf",
"traphi",
"trapls",
"trapcc",
"traphs",
"trapcs",
"trapge",
"trapmi",
"trappl",
"trapvs",
"trapvc",
"traplt",
"link",
"trapgt",
"ftrapf",
"ftrapeq",
"ftrapogt",
"ftrapoge",
"ftrapolt",
"ftrapole",
"ftrapogl",
"ftrapor",
"ftrapun",
"ftrapueq",
"ftrapugt",
"ftrapuge",
"ftrapult",
"ftrapule",
"ftrapne",
"ftrapt",
"ftrapsf",
"ftrapseq",
"ftrapgt",
"ftrapge",
"ftraplt",
"ftraple",
"ftrapgl",
"ftrapgle",
"ftrapngle",
"ftrapngl",
"ftrapnle",
"ftrapnlt",
"ftrapnge",
"ftrapngt",
"ftrapsne",
"ftrapst",
"move16",
"unlk",
"unpk",
"pack",
"stop",
"bfchg",
"pflush",
"bfclr",
"pflusha",
"bfexts",
"pflushan",
"bfextu",
"pflushn",
"bfffo",
"pflushr",
"bfins",
"pflushs",
"bfset",
"ploadr",
"bftst",
"ploadw",
"bgnd",
"plpar",
"plpaw",
"bkpt",
"callm",
"prestore",
"psave",
"cinvl",
"cinvp",
"cinva",
"cpushl",
"cpushp",
"cpusha",
"ptestr",
"ptestw",
"pulse",
"rtd",
"rtm",
"trap",
"fnop",
"frestore",
"fsave",
"halt",
"intouch",
"jmp",
"jsr",
"linea",
"line_a",
"linef",
"line_f",
"bhs",
"blo",
"bhi",
"bls",
"bcc",
"bcs",
"bne",
"beq",
"bvc",
"bvs",
"bpl",
"bmi",
"bge",
"blt",
"bgt",
"ble",
"bra",
"bsr",
"jbhs",
"jblo",
"jbhi",
"jbls",
"jbcc",
"jbcs",
"jbne",
"jbeq",
"jbvc",
"jbvs",
"jbpl",
"jbmi",
"jbge",
"jblt",
"jbgt",
"jble",
"jbra",
"jbsr",
"pmovefd",
"mov3q",
"movclr",
"movec",
"moveq",
"wdebug",
"pea",
"bitrev",
"byterev",
"divsl",
"divul",
"exg",
"pvalid",
"rems",
"extb",
"remu",
"sats",
"ff1",
"lea",
"lpstop",
"pdbbs",
"pdbbc",
"pdbls",
"pdblc",
"pdbss",
"pdbsc",
"pdbas",
"pdbac",
"pdbws",
"pdbwc",
"pdbis",
"pdbic",
"pdbgs",
"pdbgc",
"pdbcs",
"pdbcc",
"dbt",
"dbf",
"dbra",
"dbhi",
"dbls",
"dbcc",
"dbhs",
"dbcs",
"dblo",
"dbne",
"dbvs",
"dbeq",
"dbpl",
"dbvc",
"dbmi",
"dbge",
"dblt",
"dbgt",
"dble",
"strldsr",
"swap",
"fdbf",
"fdbeq",
"fdbogt",
"fdboge",
"fdbolt",
"fdbole",
"fdbogl",
"fdbor",
"fdbun",
"fdbueq",
"fdbugt",
"fdbuge",
"fdbult",
"fdbule",
"fdbne",
"fdbt",
"fdbsf",
"fdbseq",
"fdbgt",
"fdbge",
"fdblt",
"fdble",
"fdbgl",
"fdbgle",
"fdbngle",
"fdbngl",
"fdbnle",
"fdbnlt",
"fdbnge",
"fdbngt",
"fdbsne",
"fdbst",
"nbcd",
"abcd",
"psbs",
"psbc",
"psls",
"pslc",
"psss",
"pssc",
"psas",
"psac",
"psws",
"pswc",
"psis",
"psic",
"psgs",
"psgc",
"pscs",
"pscc",
"sbcd",
"st",
"sf",
"shi",
"sls",
"scc",
"shs",
"scs",
"slo",
"sne",
"seq",
"svc",
"svs",
"spl",
"smi",
"sge",
"slt",
"sgt",
"sle",
"tas",
"fsf",
"fseq",
"fsogt",
"fsoge",
"fsolt",
"fsole",
"fsogl",
"fsor",
"fsun",
"fsueq",
"fsugt",
"fsuge",
"fsult",
"fsule",
"fsne",
"fst",
"fssf",
"fsseq",
"fsgt",
"fsge",
"fslt",
"fsle",
"fsgl",
"fsgle",
"fsngle",
"fsngl",
"fsnle",
"fsnlt",
"fsnge",
"fsngt",
"fssne",
"fsst",
"mvs",
"mvz",
"pmove",
"fmovem",
"fmovecr",
"fabs",
"fsabs",
"fdabs",
"facos",
"fadd",
"fsadd",
"fdadd",
"fasin",
"fatan",
"fatanh",
"fcmp",
"fcos",
"fcosh",
"fdiv",
"fsdiv",
"fddiv",
"fetox",
"fetoxm1",
"fgetexp",
"fgetman",
"fint",
"fintrz",
"flog10",
"flog2",
"flogn",
"flognp1",
"fmod",
"fmove",
"fsmove",
"fdmove",
"fmul",
"fsmul",
"fdmul",
"fneg",
"fsneg",
"fdneg",
"frem",
"fscale",
"fsgldiv",
"fsglmul",
"fsin",
"fsinh",
"fsqrt",
"fssqrt",
"fdsqrt",
"fsub",
"fssub",
"fdsub",
"ftan",
"ftanh",
"ftentox",
"ftwotox",
];
const instructionsNoOperands = [
"nop",
"reset",
"rte",
"rtr",
"rts",
"trapv",
"illegal",
];
const directives = [
"align",
"cnop",
"cargs",
"comm",
"echo",
"end",
"fail",
"fpu",
"idnt",
"jumpptr",
"llen",
"load",
"machine",
"mmu",
"offset",
"opword",
"org",
"output",
"plen",
"printt",
"printv",
"record",
"rorg",
"rsset",
"setfo",
"setso",
"spc",
"ttl",
];
const directiveNoOperands = [
"clrfo",
"clrso",
"comment",
"einline",
"even",
"inline",
"list",
"mexit",
"nolist",
"nopage",
"odd",
"page",
"popsection",
"pushsection",
"rsreset",
];
const sectionTypes = [
"bss",
"bss_c",
"bss_f",
"bss_p",
"text",
"text_c",
"text_f",
"text_p",
"code",
"code_c",
"code_f",
"code_p",
"cseg",
"data",
"data_c",
"data_f",
"data_p",
"dseg",
];
const conditionalExp = [
"if",
"ifeq",
"ifne",
"ifgt",
"ifge",
"iflt",
"ifle",
"ifb",
"ifnb",
];
const conditionalNoop = ["if1", "if2", "ifp1"];
const conditionalComp = ["ifc", "ifnc"];
const conditionalSymbol = ["ifd", "ifnd", "ifmacrod", "ifmacrond"];
const memoryTypes = ["chip", "fast"];
const registerNames = ["sp", "pc", "sr", "ccr", "usp", "vbr"];
module.exports = grammar({
name: "m68k",
word: ($) => $._symbol_chars,
extras: () => [], // handle whitespace manually
conflicts: ($) => [
[$.element_list],
[$._start_line, $._label_colon, $.external_label],
],
inline: ($) => [$._address],
rules: {
source_file: ($) =>
seq(optional($._nl), prec.left(listSep($._element, $._nl))),
element_list: ($) => listSep($._element, $._nl),
_element: ($) =>
choice(
$._definition,
$._statement,
$._standalone_label,
$._standalone_comment,
$._block
),
_standalone_label: ($) => seq(
choice($._label, alias($._name, $.label)), optional($.comment)),
_standalone_comment: ($) => prec(-1, seq(optional($._ws), $.comment)),
_definition: ($) =>
choice(
$.macro_definition,
$.symbol_definition,
$.symbol_assignment,
$.offset_definition,
$.register_definition,
$.register_list_definition
),
_statement: ($) =>
seq(
$._start_line,
choice(
$.instruction,
$.macro_call,
$.conditional_instruction,
$.directive,
$.section,
$.opt,
$.include,
$.include_bin,
$.include_dir,
$.external_definition,
$.external_reference,
$._data
),
optional($._end_line)
),
_block: ($) => choice($.repeat, $.conditional, $.rem, $.end),
_start_line: ($) => choice($._label, $._ws),
//----------------------------------------------------------------------
// Labels:
//----------------------------------------------------------------------
_label: ($) =>
choice(alias($._label_definition, $.label), $.external_label),
_name: ($) => field("name", $._identifier),
_label_definition: ($) =>
choice(
seq($._label_colon, optional($._ws)),
seq($._name, $._ws),
),
// colon required with leading whitespace
_label_colon: ($) => seq(optional($._ws), $._name, ":"),
// double colon makes label externally visible
external_label: ($) => seq(optional($._ws), $._name, "::", optional($._ws)),
//----------------------------------------------------------------------
// Comments:
//----------------------------------------------------------------------
comment: ($) => choice($._comment_star, $._comment_semi),
_comment_semi: () => /;[^\n\r]*/,
_comment_star: () => /\*[^\n\r]*/,
_end_line: ($) =>
choice(
seq(optional($._ws), alias($._comment_semi, $.comment)),
// Start comment must have whitespace to avoid conflict with multiplication operator
// TODO: would be better if whitepace didn't form part of comment pattern
// This would be handled by positional comment if I could get precedence right
alias(/\s+\*.+/, $.comment),
seq($._ws, alias(/[^\r\n]+/, $.comment)) // Positional comment - any chars allowed after statement
),
//----------------------------------------------------------------------
// Mnemonics:
//----------------------------------------------------------------------
// instructions:
_instruction_mnemonic_op: ($) =>
alias(mnemonicChoice(instructions), $.instruction_mnemonic),
_instruction_mnemonic_noop: ($) =>
alias(mnemonicChoice(instructionsNoOperands), $.instruction_mnemonic),
// directives:
_data_constant_mnemonic: ($) =>
alias(mnemonicChoice(["dc", "dr"]), $.directive_mnemonic),
_data_constant_short_mnemonic: ($) =>
alias(mnemonicChoice(["db", "dw", "dl"]), $.directive_mnemonic),
_data_space_mnemonic: ($) =>
alias(mnemonicChoice(["dcb", "blk"]), $.directive_mnemonic),
_data_space_short_mnemonic: ($) =>
alias(caseInsensitive("ds"), $.directive_mnemonic),
_directive_mnemonic_noop: ($) =>
alias(mnemonicChoice(directiveNoOperands), $.directive_mnemonic),
_directive_mnemonic_op: ($) =>
alias(mnemonicChoice(directives), $.directive_mnemonic),
_external_definition_mnemonic: ($) =>
alias(mnemonicChoice(["xdef", "public", "weak"]), $.directive_mnemonic),
_external_reference_mnemonic: ($) =>
alias(mnemonicChoice(["xref", "nref"]), $.directive_mnemonic),
_incbin_mnemonic: ($) =>
alias(caseInsensitive("incbin"), $.directive_mnemonic),
_incdir_mnemonic: ($) =>
alias(caseInsensitive("incdir"), $.directive_mnemonic),
_include_mnemonic: ($) =>
alias(caseInsensitive("include"), $.directive_mnemonic),
_register_definition_mnemonic: ($) =>
alias(mnemonicChoice(["equr", "fequr"]), $.directive_mnemonic),
_register_list_definition_mnemonic: ($) =>
alias(
mnemonicChoice(["equrl", "fequrl", "reg", "freg"]),
$.directive_mnemonic
),
_rs_mnemonic: ($) => alias(caseInsensitive("rs"), $.directive_mnemonic),
_section_mnemonic: ($) =>
alias(caseInsensitive("section"), $.directive_mnemonic),
_symbol_assignment_mnemonic: ($) =>
alias(caseInsensitive("set"), $.directive_mnemonic),
_symbol_definition_mnemonic: ($) =>
alias(mnemonicChoice(["equ", "fequ"]), $.directive_mnemonic),
// control:
_conditional_mnemonic_exp: ($) =>
alias(mnemonicChoice(conditionalExp), $.control_mnemonic),
_conditional_mnemonic_noop: ($) =>
alias(mnemonicChoice(conditionalNoop), $.control_mnemonic),
_conditional_mnemonic_comp: ($) =>
alias(mnemonicChoice(conditionalComp), $.control_mnemonic),
_conditional_mnemonic_symbol: ($) =>
alias(mnemonicChoice(conditionalSymbol), $.control_mnemonic),
_conditional_mnemonic_inline: ($) =>
alias(caseInsensitive("iif"), $.control_mnemonic),
_else_mnemonic: ($) =>
alias(mnemonicChoice(["else", "elseif"]), $.control_mnemonic),
_endif_mnemonic: ($) =>
alias(mnemonicChoice(["endif", "endc"]), $.control_mnemonic),
_macro_mnemonic: ($) => alias(caseInsensitive("macro"), $.control_mnemonic),
_endm_mnemonic: ($) => alias(caseInsensitive("endm"), $.control_mnemonic),
_rem_mnemonic: ($) => alias(caseInsensitive("rem"), $.control_mnemonic),
_erem_mnemonic: ($) => alias(caseInsensitive("erem"), $.control_mnemonic),
_rept_mnemonic: ($) => alias(caseInsensitive("rept"), $.control_mnemonic),
_endr_mnemonic: ($) => alias(caseInsensitive("endr"), $.control_mnemonic),
_end_mnemonic: ($) => alias(caseInsensitive("end"), $.control_mnemonic),
section_type: () => choice(...sectionTypes.map(caseInsensitive)),
memory_type: () => choice(...memoryTypes.map(caseInsensitive)),
opt: ($) =>
seq(
field("mnemonic", alias(caseInsensitive("opt"), $.directive_mnemonic)),
$._ws,
field("options", $.option_list)
),
option_list: ($) => listSep($.option, $._sep),
option: () =>
choice(
/[aAcCdDlLpOoPsStTwWxX][+-]/,
/[oO][a-zA-Z0-9]+[+-]/,
/[lL][0-9]+/,
// TODO: p=<type>[/<type>]
...[
"autopc",
"case",
"chkpc",
"debug",
"symtab",
"type",
"warn",
"xdebug",
"noautopc",
"nocase",
"nochkpc",
"nodebug",
"nosymtab",
"notype",
"nowarn",
"noxdebug",
].map(caseInsensitive)
),
// Instruction:
instruction: ($) =>
prec.right(
choice(
// Instruction with operands
seq(
field("mnemonic", $._instruction_mnemonic_op),
optional(seq(".", field("size", $._size))),
seq($._ws, field("operands", $.operand_list))
),
// Instruction with no operands
seq(
field("mnemonic", $._instruction_mnemonic_noop),
optional(seq(".", field("size", $._size)))
)
)
),
operand_list: ($) => listSep($._operand, $._sep),
_operand: ($) =>
choice($._effective_address, $.register_list, $._expression),
argument_list: ($) => listSep(
choice($.quoted_arg, $._operand),
$._sep),
_argument: ($) =>
prec(1,
choice(
$._effective_address,
$.register_list,
$._numeric_literal,
$.string_literal,
$.unary_expression,
$.binary_expression,
$.parenthesized_expression,
$._identifier),
),
quoted_arg: ($) =>
seq('<', listSep($._argument, $._sep), '>'),
_size: ($) => choice($.size, $.macro_arg),
size: () => /[bwlsdxqBWLSDXQ]/,
//----------------------------------------------------------------------
// Directives:
//----------------------------------------------------------------------
directive: ($) =>
prec.right(
choice(
seq(
field("mnemonic", $._directive_mnemonic_noop),
optional(seq(".", field("size", $._size)))
),
seq(
field("mnemonic", $._directive_mnemonic_op),
optional(seq(".", field("size", $._size))),
$._ws,
field("operands", $.operand_list)
)
)
),
// section
section: ($) =>
choice(
seq(field("type", $.section_type)),
seq(
$._section_mnemonic,
$._ws,
field("name", choice($._identifier, $.string_literal)),
optional(
seq(
$._sep,
field("type", $.section_type),
optional(
seq(
$._sep,
field(
"memory_type",
choice($.memory_type, $._numeric_literal)
)
)
),
optional($._sep)
)
)
)
),
// includes
include: ($) => seq($._include_mnemonic, $._ws, field("path", $.path)),
include_dir: ($) => seq($._incdir_mnemonic, $._ws, field("path", $.path)),
include_bin: ($) =>
seq(
$._incbin_mnemonic,
$._ws,
field("path", $.path),
optional(
seq(
$._sep,
field("offset", $._expression),
optional(seq(",", field("length", $._expression))),
optional($._sep)
)
)
),
path: ($) => choice(/[^"'\s,]+/, $.string_literal),
// data:
_data: ($) => choice($.data_constant, $.data_space),
data_constant: ($) =>
seq(
choice(
seq(
field("mnemonic", $._data_constant_mnemonic),
optional(seq(".", field("size", $._size)))
),
field("mnemonic", $._data_constant_short_mnemonic)
),
$._ws,
field("values", $.expression_list)
),
data_space: ($) =>
choice(
seq(
field("mnemonic", $._data_space_short_mnemonic),
optional(seq(".", field("size", $._size))),
$._ws,
field("length", $._expression)
),
seq(
field("mnemonic", $._data_space_mnemonic),
optional(seq(".", field("size", $._size))),
$._ws,
field("length", $._expression),
optional(seq($._sep, field("fill", $._expression))),
optional($._sep)
)
),
// Macro call:
macro_call: ($) =>
prec.right(
seq(
field("name", $._identifier_nodot),
optional(seq(".", field("qualifier", $._expression))),
optional(seq($._ws, field("arguments", $.argument_list))),
optional($._ws)
)
),
//----------------------------------------------------------------------
// Block / multiline:
//----------------------------------------------------------------------
repeat: ($) =>
seq(
$._start_line,
$._rept_mnemonic,
$._ws,
field("count", $._expression),
optional($._end_line),
$._nl,
optional(seq(field("body", $.element_list), $._nl)),
$._start_line,
$._endr_mnemonic,
optional($._end_line)
),
conditional: ($) =>
seq(
$._start_line,
$._conditional_block_start,
optional(
seq(
field("consequent", $.element_list),
$._nl,
optional(
seq(
$._conditional_block_else,
field("alternate", $.element_list),
$._nl
)
)
)
),
$._conditional_block_end,
optional($._end_line)
),
_conditional_block_else: ($) =>
prec.dynamic(
1,
seq($._start_line, $._else_mnemonic, optional($._end_line), $._nl)
),
_conditional_block_start: ($) =>
seq(
choice(
$._conditional_expression,
$._conditional_noop,
$._conditional_comparison,
$._conditional_symbol
),
optional($._end_line),
$._nl
),
_conditional_block_end: ($) => seq($._start_line, $._endif_mnemonic),
_conditional_expression: ($) =>
seq(
field("mnemonic", $._conditional_mnemonic_exp),
$._ws,
field("test", $._expression)
),
_conditional_noop: ($) =>
seq(field("mnemonic", $._conditional_mnemonic_noop)),
_conditional_comparison: ($) =>
seq(
field("mnemonic", $._conditional_mnemonic_comp),
$._ws,
field("left", $._expression),
$._sep,
field("right", $._expression),
optional($._sep)
),
_conditional_symbol: ($) =>
seq(
field("mnemonic", $._conditional_mnemonic_symbol),
$._ws,
field("test", $._identifier)
),
conditional_instruction: ($) =>
seq(
$._conditional_mnemonic_inline,
$._ws,
field("test", $._expression),
$._ws,
field("consequent", $.instruction)
),
rem: ($) =>
seq(
$._start_line,
$._rem_mnemonic,
optional($._end_line),
$._nl,
alias(repeat(seq(repeat(/[^\n]/), $._nl)), $.comment),
alias(/\s+erem/, $.control_mnemonic),
optional($._end_line)
),
end: ($) =>
prec.right(seq($._start_line, $._end_mnemonic, listSep(/.*/, $._nl))),
macro_definition: ($) =>
prec(
PREC.definition,
seq(
choice(
seq($._label_definition, $._macro_mnemonic),
seq($._ws, $._macro_mnemonic, $._ws, field("name", $.symbol))
),
optional($._end_line),
$._nl,
optional(seq(field("body", $.element_list), $._nl)),
$._start_line,
$._endm_mnemonic,
optional($._end_line)
)
),
//----------------------------------------------------------------------
// Definitions
//----------------------------------------------------------------------
symbol_definition: ($) =>
prec(
PREC.definition,
seq(
choice($._label_definition, $._name),
choice(
seq(
"=",
optional(seq(".", field("size", alias(/[sdxpSDXP]/, $.size)))),
optional($._ws)
),
seq(
field("mnemonic", $._symbol_definition_mnemonic),
optional(seq(".", field("size", alias(/[sdxpSDXP]/, $.size)))),
$._ws
)
),
field("value", $._expression),
optional($._end_line)
)
),
symbol_assignment: ($) =>
prec(
PREC.definition,
seq(
$._label_definition,
$._symbol_assignment_mnemonic,
optional($._ws),
field("value", $._expression),
optional($._end_line)
)
),
offset_definition: ($) =>
prec(
PREC.definition,
seq(
$._label_definition,
$._rs_mnemonic,
optional(seq(".", field("size", $._size))),
$._ws,
field("length", $._expression),
optional($._end_line)
)
),
register_definition: ($) =>
prec(
PREC.definition,
seq(
$._label_definition,
field("mnemonic", $._register_definition_mnemonic),
optional($._ws),
field(
"value",
choice(
$.data_register,
$.address_register,
$.float_register,
$.named_register
)
),
optional($._end_line)
)
),
register_list_definition: ($) =>
prec(
PREC.definition,
seq(
$._label_definition,
field("mnemonic", $._register_list_definition_mnemonic),
optional($._ws),
field("value", choice($.register_list)),
optional($._end_line)
)
),
external_definition: ($) =>
seq(
field("mnemonic", $._external_definition_mnemonic),
$._ws,
field("symbols", $.symbol_list)
),
external_reference: ($) =>
seq(
field("mnemonic", $._external_reference_mnemonic),
$._ws,
field("symbols", $.symbol_list)
),
//----------------------------------------------------------------------
// Effective addresses:
//----------------------------------------------------------------------
_effective_address: ($) =>
choice(
$._register,
$.immediate_value,
$.absolute_value,
$.indirect_address,
$.indirect_address_postinc,
$.indirect_address_predec,
$.offset_address,
$.offset_address_idx
),
immediate_value: ($) => seq("#", field("value", $._expression)),
absolute_value: ($) =>
choice(
seq(field("value", $._expression), ".", field("size", $._size)),
seq("(", field("value", $._expression), ").", field("size", $._size))
),
_register: ($) =>
choice(
$.named_register,
$.data_register,
$.address_register,
$.float_register
),
named_register: () => choice(...registerNames.map(caseInsensitive)),
data_register: () => /[dD][0-7]/,
address_register: () => /[aA][0-7]/,
float_register: () => /[fF][pP][0-7]/,
_address: ($) =>
field(
"register",
choice($.address_register, $.named_register, $._identifier)
),
indirect_address: ($) => prec(2, seq("(", $._address, ")")),
indirect_address_postinc: ($) => seq("(", $._address, ")+"),
indirect_address_predec: ($) => prec(2, seq("-", "(", $._address, ")")),
offset_address: ($) =>
choice(
seq("(", field("offset", $._expression), ",", $._address, ")"),
seq(field("offset", $._expression), "(", $._address, ")")
),
offset_address_idx: ($) =>
prec(
1,
choice(
seq(
"(",
field("offset", choice($._expression, $._identifier)),
",",
$._address,
",",
field("idx", $.idx),
")"
),
seq(
optional(field("offset", $._expression)),
"(",
$._address,
",",
field("idx", $.idx),
")"
)
)
),
idx: ($) =>
seq(
field(
"register",
choice(
$.data_register,
$.address_register,
$.named_register,
$._identifier
)
),
optional(seq(".", field("size", $._size))),
optional(seq("*", field("scale", $._expression)))
),
//----------------------------------------------------------------------
// Register lists:
//----------------------------------------------------------------------
register_list: ($) =>
seq($._register_list_item, repeat(seq("/", $._register_list_item))),
_register_list_item: ($) => prec(-1, choice($.register_range, $._register)),
register_range: ($) =>
seq(
field("from", $._register),
"-",
field("to", choice($._register, $.register_number))
),
register_number: () => /[0-7]/,
//----------------------------------------------------------------------
// Expressions:
//----------------------------------------------------------------------
_expression: ($) =>
choice(
$._numeric_literal,
$.string_literal,
$.unary_expression,
$.binary_expression,
$.parenthesized_expression,
$._identifier
),
expression_list: ($) => listSep($._expression, $._sep),
parenthesized_expression: ($) =>
choice(seq("(", $._expression, ")"), seq("[", $._expression, "]")),
unary_expression: ($) =>
prec(
PREC.unary,
seq(
field("operator", alias(choice("+", "-", "!", "~"), $.operator)),
field("operand", $._expression)
)
),
binary_expression: ($) =>
choice(
...binaryOperators.map((operator, i) =>
prec.left(
i,
seq(
field("left", $._expression),
field("operator", alias(operator, $.operator)),
field("right", $._expression)
)
)
)
),
// Literals
_numeric_literal: ($) =>
choice(
$.hexadecimal_literal,
$.binary_literal,
$.octal_literal,
$.decimal_literal
),
hexadecimal_literal: () => /\$[0-9A-Fa-z]+/,
binary_literal: () => /%[01]+/,
octal_literal: () => /@[0-7]+/,
decimal_literal: () =>
prec.left(choice(/\d+/, seq(optional(/\d+/), ".", /\d+/))),
string_literal: ($) =>
choice(
// Single quoted
seq(
"'",
repeat(
choice(
prec(1, /[^'\n\\]+/), // Normal chars
"''", // Repeat quote escape
$._string_escape_codes
)
),
"'"
),
// Double quoted
seq(
'"',
repeat(choice(prec(1, /[^"\n\\]+/), '""', $._string_escape_codes)),
'"'
)
),
_string_escape_codes: ($) =>
choice(
/\\[\\bfnrt"'e]/, // Single char escape codes
/\\[0-7]+/, // Octal character code
/\\[xX][0-9a-f]+/, // Hex charcter code
$.macro_arg
),
//----------------------------------------------------------------------
// Identifiers
//----------------------------------------------------------------------
_identifier: ($) =>
choice($.symbol, $.interpolated, $.macro_arg, $._builtin),
_identifier_nodot: ($) =>
choice(alias($.symbol_no_dot, $.symbol), $.interpolated, $.macro_arg, $._builtin),
macro_arg: () =>
choice(
/\\[0-9]/,
"\\@",
"\\@!",
"\\@?",
"\\@@",
"\\#",
/\\\?[0-9]/,
"\\.",
"\\+",
"\\-",
/\\\.?[a-zA-Z][a-zA-Z0-9_]*/,
/\\\$\.?[a-zA-Z][a-zA-Z0-9_]*/,
/\\<[^>]+>/
),
// Symbol containing one or more macro args
// e.g. foo\1bar
interpolated: ($) =>
prec.right(
1,
seq(
optional("."),
optional($._symbol_chars),
repeat1(
prec.right(-1,
seq($.macro_arg, optional($._symbol_chars))
),
),
optional("$")
)
),
_builtin: ($) => choice($.reptn, $.carg, $.narg, $.pc),
reptn: () => caseInsensitive("reptn"),
carg: () => caseInsensitive("carg"),
narg: () => caseInsensitive("narg"),
pc: () => "*",
symbol_list: ($) => listSep($.symbol, $._sep),
symbol_no_dot: ($) =>
prec.right(
seq(optional("."), $._symbol_chars, optional("$"))
),
symbol: ($) =>
prec.right(
seq(repeat1(seq(optional("."), $._symbol_chars)), optional("$"))
),
_symbol_chars: () => /([a-zA-Z0-9_%]|(\\?\\\.))+/,
//----------------------------------------------------------------------
// Misc:
//----------------------------------------------------------------------
_sep: () => /\s*,\s*/,
_ws: () => /[ \t]+/,
_nl: () => /([ \t]*(\r\n|\n|\r))+/,
},
});
function listSep(rule, sep = ",") {
return seq(repeat(seq(rule, sep)), rule, optional(sep));
}
function toCaseInsensitive(a) {
var ca = a.charCodeAt(0);
if (ca >= 97 && ca <= 122) return `[${a}${a.toUpperCase()}]`;
if (ca >= 65 && ca <= 90) return `[${a.toLowerCase()}${a}]`;
return a;
}
function caseInsensitive(keyword) {
return new RegExp(keyword.split("").map(toCaseInsensitive).join(""));
}
function mnemonicChoice(options) {
return choice(...options.map(caseInsensitive));
}