diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 38d120fa7e..4e0bc6cb78 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -49,11 +49,11 @@ def gen_formats(fmt): with fmt.indented( "fn from(inst: &'a InstructionData) -> InstructionFormat {", '}'): - with fmt.indented('match *inst {', '}'): - for f in InstructionFormat.all_formats: - fmt.line(('InstructionData::{} {{ .. }} => ' + - 'InstructionFormat::{},') - .format(f.name, f.name)) + m = srcgen.Match('*inst') + for f in InstructionFormat.all_formats: + m.arm('InstructionData::' + f.name, ['..'], + 'InstructionFormat::' + f.name) + fmt.match(m) fmt.line() @@ -74,33 +74,32 @@ def gen_arguments_method(fmt, is_mut): 'pool: &\'a {m}ir::ValueListPool) -> ' '&{m}[Value] {{' .format(f=method, m=mut), '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - n = 'InstructionData::' + f.name + m = srcgen.Match('*self') + for f in InstructionFormat.all_formats: + n = 'InstructionData::' + f.name - # Formats with a value list put all of their arguments in the - # list. We don't split them up, just return it all as variable - # arguments. (I expect the distinction to go away). - if f.has_value_list: - arg = ''.format(mut) - fmt.line( - '{} {{ ref {}args, .. }} => args.{}(pool),' - .format(n, mut, as_slice)) - continue + # Formats with a value list put all of their arguments in the + # list. We don't split them up, just return it all as variable + # arguments. (I expect the distinction to go away). + if f.has_value_list: + m.arm(n, ['ref {}args'.format(mut), '..'], + 'args.{}(pool)'.format(as_slice)) + continue - # Fixed args. - if f.num_value_operands == 0: - arg = '&{}[]'.format(mut) - capture = '' - elif f.num_value_operands == 1: - capture = 'ref {}arg, '.format(mut) - arg = '{}(arg)'.format(rslice) - else: - capture = 'ref {}args, '.format(mut) - arg = 'args' - fmt.line( - '{} {{ {}.. }} => {},' - .format(n, capture, arg)) + # Fixed args. + fields = [] + if f.num_value_operands == 0: + arg = '&{}[]'.format(mut) + elif f.num_value_operands == 1: + fields.append('ref {}arg'.format(mut)) + arg = '{}(arg)'.format(rslice) + else: + args = 'args_arity{}'.format(f.num_value_operands) + fields.append('args: ref {}{}'.format(mut, args)) + arg = args + fields.append('..') + m.arm(n, fields, arg) + fmt.match(m) def gen_instruction_data(fmt): @@ -155,39 +154,37 @@ def gen_instruction_data_impl(fmt): with fmt.indented('impl InstructionData {', '}'): fmt.doc_comment('Get the opcode of this instruction.') with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - fmt.line( - 'InstructionData::{} {{ opcode, .. }} => opcode,' - .format(f.name)) + m = srcgen.Match('*self') + for f in InstructionFormat.all_formats: + m.arm('InstructionData::' + f.name, ['opcode', '..'], + 'opcode') + fmt.match(m) fmt.line() fmt.doc_comment('Get the controlling type variable operand.') with fmt.indented( 'pub fn typevar_operand(&self, pool: &ir::ValueListPool) -> ' 'Option {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - n = 'InstructionData::' + f.name - if f.typevar_operand is None: - fmt.line(n + ' { .. } => None,') - elif f.has_value_list: - # We keep all arguments in a value list. - i = f.typevar_operand - fmt.line( - '{} {{ ref args, .. }} => ' - 'args.get({}, pool),'.format(n, i)) - elif f.num_value_operands == 1: - # We have a single value operand called 'arg'. - fmt.line(n + ' { arg, .. } => Some(arg),') - else: - # We have multiple value operands and an array `args`. - # Which `args` index to use? - i = f.typevar_operand - fmt.line( - n + - ' {{ ref args, .. }} => Some(args[{}]),' - .format(i)) + m = srcgen.Match('*self') + for f in InstructionFormat.all_formats: + n = 'InstructionData::' + f.name + if f.typevar_operand is None: + m.arm(n, ['..'], 'None') + elif f.has_value_list: + # We keep all arguments in a value list. + i = f.typevar_operand + m.arm(n, ['ref args', '..'], + 'args.get({}, pool)'.format(i)) + elif f.num_value_operands == 1: + # We have a single value operand called 'arg'. + m.arm(n, ['arg', '..'], 'Some(arg)') + else: + # We have multiple value operands and an array `args`. + # Which `args` index to use? + args = 'args_arity{}'.format(f.num_value_operands) + m.arm(n, ['args: ref {}'.format(args), '..'], + 'Some({}[{}])'.format(args, f.typevar_operand)) + fmt.match(m) fmt.line() fmt.doc_comment( @@ -216,13 +213,13 @@ def gen_instruction_data_impl(fmt): with fmt.indented( 'pub fn take_value_list(&mut self) -> Option {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - n = 'InstructionData::' + f.name - if f.has_value_list: - fmt.line( - n + ' { ref mut args, .. } => Some(args.take()),') - fmt.line('_ => None,') + m = srcgen.Match('*self') + for f in InstructionFormat.all_formats: + n = 'InstructionData::' + f.name + if f.has_value_list: + m.arm(n, ['ref mut args', '..'], 'Some(args.take())') + m.arm('_', [], 'None') + fmt.match(m) fmt.line() fmt.doc_comment( @@ -307,14 +304,12 @@ def gen_opcodes(groups, fmt): fmt.doc_comment(Instruction.ATTRIBS[attr]) with fmt.indented('pub fn {}(self) -> bool {{' .format(attr), '}'): - with fmt.indented('match self {', '}'): - for i in instrs: - if getattr(i, attr): - fmt.format( - 'Opcode::{} => true,', - i.camel_name, i.name) - - fmt.line('_ => false,') + m = srcgen.Match('self') + for i in instrs: + if getattr(i, attr): + m.arm('Opcode::' + i.camel_name, [], 'true') + m.arm('_', [], 'false') + fmt.match(m) fmt.line() fmt.line() @@ -331,9 +326,10 @@ def gen_opcodes(groups, fmt): # Generate a private opcode_name function. with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'): - with fmt.indented('match opc {', '}'): - for i in instrs: - fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) + m = srcgen.Match('opc') + for i in instrs: + m.arm('Opcode::' + i.camel_name, [], '"{}"'.format(i.name)) + fmt.match(m) fmt.line() # Generate an opcode hash table for looking up opcodes by name. diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 1560157915..b5f0de3488 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -57,12 +57,11 @@ def gen_getter(setting, sgrp, fmt): ty = camel_case(setting.name) proto = 'pub fn {}(&self) -> {}'.format(setting.name, ty) with fmt.indented(proto + ' {', '}'): - with fmt.indented( - 'match self.bytes[{}] {{' - .format(setting.byte_offset), '}'): - for i, v in enumerate(setting.values): - fmt.line('{} => {}::{},'.format(i, ty, camel_case(v))) - fmt.line('_ => panic!("Invalid enum value"),') + m = srcgen.Match('self.bytes[{}]'.format(setting.byte_offset)) + for i, v in enumerate(setting.values): + m.arm(str(i), [], '{}::{}'.format(ty, camel_case(v))) + m.arm('_', [], 'panic!("Invalid enum value")') + fmt.match(m) else: raise AssertionError("Unknown setting kind") diff --git a/lib/cretonne/meta/srcgen.py b/lib/cretonne/meta/srcgen.py index 9761160487..2201d997d5 100644 --- a/lib/cretonne/meta/srcgen.py +++ b/lib/cretonne/meta/srcgen.py @@ -8,9 +8,10 @@ source code. from __future__ import absolute_import import sys import os +import collections try: - from typing import Any, List # noqa + from typing import Any, List, Set, Tuple # noqa except ImportError: pass @@ -146,6 +147,52 @@ class Formatter(object): for l in parse_multiline(s): self.line('/// ' + l if l else '///') + def match(self, m): + # type: (Match) -> None + """ + Add a match expression. + + Example: + + >>> f = Formatter() + >>> m = Match('x') + >>> m.arm('Orange', ['a', 'b'], 'some body') + >>> m.arm('Yellow', ['a', 'b'], 'some body') + >>> m.arm('Green', ['a', 'b'], 'different body') + >>> m.arm('Blue', ['x', 'y'], 'some body') + >>> f.match(m) + >>> f.writelines() + match x { + Orange { a, b } | + Yellow { a, b } => { + some body + } + Green { a, b } => { + different body + } + Blue { x, y } => { + some body + } + } + + """ + with self.indented('match {} {{'.format(m.expr), '}'): + for (fields, body), names in m.arms.items(): + with self.indented('', '}'): + names_left = len(names) + for name in names.keys(): + fields_str = ', '.join(fields) + if len(fields) != 0: + fields_str = '{{ {} }} '.format(fields_str) + names_left -= 1 + if names_left > 0: + suffix = '|' + else: + suffix = '=> {' + self.outdented_line(name + ' ' + fields_str + suffix) + if names_left == 0: + self.multi_line(body) + def _indent(s): # type: (str) -> int @@ -195,3 +242,36 @@ def parse_multiline(s): while trimmed and not trimmed[0]: trimmed.pop(0) return trimmed + + +class Match(object): + """ + Match formatting class. + + Match objects collect all the information needed to emit a Rust `match` + expression, automatically deduplicating overlapping identical arms. + + Example: + + >>> m = Match('x') + >>> m.arm('Orange', ['a', 'b'], 'some body') + >>> m.arm('Yellow', ['a', 'b'], 'some body') + >>> m.arm('Green', ['a', 'b'], 'different body') + >>> m.arm('Blue', ['x', 'y'], 'some body') + >>> assert(len(m.arms) == 3) + + Note that this class is ignorant of Rust types, and considers two fields + with the same name to be equivalent. + """ + + def __init__(self, expr): + # type: (str) -> None + self.expr = expr + self.arms = collections.OrderedDict() # type: collections.OrderedDict[Tuple[Tuple[str, ...], str], collections.OrderedDict[str, None]] # noqa + + def arm(self, name, fields, body): + # type: (str, List[str], str) -> None + key = (tuple(fields), body) + if key not in self.arms: + self.arms[key] = collections.OrderedDict() + self.arms[key][name] = None