swizzle method

Expression swizzle(
  1. Expression target,
  2. ExpressionToken field
)

.xyz and .rgb, folded on a constant.

Implementation

Expression swizzle(Expression target, ExpressionToken field) {
  if (!target.type.isNumeric) {
    fail('a ${target.type} has no components.', where: field);
  }
  const sets = <String>['xyzw', 'rgba'];
  final indices = <int>[];
  String? chosen;
  for (final letter in field.text.split('')) {
    final set = sets.firstWhere((s) => s.contains(letter), orElse: () => '');
    if (set.isEmpty) {
      fail(
        '"$letter" is not a component: xyzw and rgba are, and one '
        'swizzle does not mix the two.',
        where: field,
      );
    }
    // GLSL refuses `.xg` too, and for the reason it reads badly rather than
    // for one about the hardware: two names for the same component in one
    // expression is somebody having lost track of which vector they are in.
    chosen ??= set;
    if (set != chosen) {
      fail('"${field.text}" mixes xyzw with rgba.', where: field);
    }
    final index = set.indexOf(letter);
    if (index >= target.type.components) {
      fail('a ${target.type} has no "$letter".', where: field);
    }
    indices.add(index);
  }
  if (indices.isEmpty || indices.length > 4) {
    fail('"${field.text}" is not one to four components.', where: field);
  }
  final type = ExpressionType.numeric[indices.length]!.withDimension(
    target.type.dimension,
  );
  if (target is ExpressionConstant) {
    return ExpressionConstant(<double>[
      for (final index in indices) target.value[index],
    ], type);
  }
  return ExpressionSwizzle(target, indices, type);
}