All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.legendofdragoon.scripting.Lexer Maven / Gradle / Ivy

There is a newer version: 0.5.6
Show newest version
package org.legendofdragoon.scripting;

import org.legendofdragoon.scripting.meta.Meta;
import org.legendofdragoon.scripting.tokens.Data;
import org.legendofdragoon.scripting.tokens.Entry;
import org.legendofdragoon.scripting.tokens.Entrypoint;
import org.legendofdragoon.scripting.tokens.LodString;
import org.legendofdragoon.scripting.tokens.Op;
import org.legendofdragoon.scripting.tokens.Param;
import org.legendofdragoon.scripting.tokens.PointerTable;
import org.legendofdragoon.scripting.tokens.Script;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Lexer {
  public static final String NUMBER_SUBPATTERN = "0x[a-f\\d]{1,8}|\\d{1,10}";
  public static final Pattern LINE_PATTERN = Pattern.compile("^\\s*?(?:[a-f0-9]+\\s+)?([a-z]\\w*?)(?:\\s+(.+))?$", Pattern.CASE_INSENSITIVE);
  public static final Pattern NUMBER_PATTERN = Pattern.compile("^(?:" + NUMBER_SUBPATTERN + ")$", Pattern.CASE_INSENSITIVE);
  public static final Pattern LABEL_PATTERN = Pattern.compile("^(\\w+):$", Pattern.CASE_INSENSITIVE);
  public static final Pattern LABEL_PARAM_PATTERN = Pattern.compile("^:(\\w+)$", Pattern.CASE_INSENSITIVE);
  public static final Pattern CALL_PATTERN = Pattern.compile("^[a-z_]\\w*::[a-z_]\\w*$", Pattern.CASE_INSENSITIVE);
  public static final Pattern STRING_PATTERN = Pattern.compile("^str\\[(.*?)]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern OPERATOR_PATTERN = Pattern.compile("^(<=|<|==|!=|>|>=|&|!&)$", Pattern.CASE_INSENSITIVE);

  public static final Pattern STORAGE_PATTERN = Pattern.compile("^stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern OTHER_OTHER_STORAGE_PATTERN = Pattern.compile("^stor\\s*?\\[\\s*?stor\\s*?\\[\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?,\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?,\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern OTHER_STORAGE_OFFSET_PATTERN = Pattern.compile("^stor\\s*?\\[\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?,\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?\\+\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern GAMEVAR_1_PATTERN = Pattern.compile("^var\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern GAMEVAR_2_PATTERN = Pattern.compile("^var\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?\\+\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern GAMEVAR_ARRAY_1_PATTERN = Pattern.compile("^var\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?\\[\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern GAMEVAR_ARRAY_2_PATTERN = Pattern.compile("^var\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?\\+\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]\\s*?\\[\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern INLINE_1_MATCHER = Pattern.compile("^inl\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + "|:\\w+)\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern INLINE_2_MATCHER = Pattern.compile("^inl\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + "|:\\w+)\\s*?\\[\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]\\s*?]$", Pattern.CASE_INSENSITIVE);

  public static final Pattern GAMEVAR_3_PATTERN = Pattern.compile("^var\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?\\+\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]$");

  public static final Pattern INLINE_3_MATCHER = Pattern.compile("^inl\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + "|:\\w+)\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + "|:\\w+)\\s*?\\[\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]\\s*?]\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern CONTROL_PATTERN = Pattern.compile("^<\\s*?([a-z]+)(?:\\s*?=\\s*?(" + NUMBER_SUBPATTERN + "))?\\s*?>$", Pattern.CASE_INSENSITIVE);

  public static final Pattern GAMEVAR_ARRAY_3_PATTERN = Pattern.compile("^var\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern GAMEVAR_ARRAY_4_PATTERN = Pattern.compile("^var\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?\\s*?\\+\\s*?stor\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]$", Pattern.CASE_INSENSITIVE);

  public static final Pattern INLINE_6_PATTERN = Pattern.compile("^inl\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + "|:\\w+)\\s*?\\+\\s*?inl\\s*?\\[(" + NUMBER_SUBPATTERN + "|:\\w+)\\s*?\\+\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]\\s*?]$", Pattern.CASE_INSENSITIVE);

  public static final Pattern REG_PATTERN = Pattern.compile("^reg\\s*?\\[\\s*?(" + NUMBER_SUBPATTERN + ")\\s*?]$", Pattern.CASE_INSENSITIVE);
  public static final Pattern ID_PATTERN = Pattern.compile("^id\\s*?\\[\\s*?(.*?:.*?)\\s*?]$", Pattern.CASE_INSENSITIVE);

  private final Meta meta;

  public Lexer(final Meta meta) {
    this.meta = meta;
  }

  public Script lex(final String source) {
    final List lines = source.lines().map(this::removeComment).map(String::strip).filter(Predicate.not(String::isBlank)).toList();

    final List entries = new ArrayList<>();
    final Map labels = new HashMap<>();
    final Set tables = new HashSet<>();

    for(final String line : lines) {
      final int address = entries.size() * 0x4;

      final Matcher labelMatcher = LABEL_PATTERN.matcher(line);
      if(labelMatcher.matches()) {
        labels.put(labelMatcher.group(1), address);
        continue;
      }

      final Entry entry = this.lexLine(address, line);
      entries.add(entry);

      if(entry instanceof final Op op) {
        if(op.type == OpType.GOSUB_TABLE || op.type == OpType.JMP_TABLE) {
          tables.add(op.params[1].label);
        }

        for(final Param param : op.params) {
          if(param.type.isInlineTable()) {
            tables.add(param.label);
          }

          for(int i = 0; i < param.type.getWidth(param); i++) {
            entries.add(param);
          }
        }
      } else if(entry instanceof final LodString string) {
        for(int i = 1; i < (string.chars.length + 1) / 2; i++) {
          entries.add(entry);
        }
      }
    }

    // Rebind inline parameters to their label's address
    for(final Entry entry : entries) {
      if(entry instanceof final Op op) {
        for(final Param param : op.params) {
          if(param.label != null) {
            if(!labels.containsKey(param.label)) {
              throw new RuntimeException("Missing label " + param.label);
            }

            final int address = labels.get(param.label);

            switch(param.type) {
              case INLINE_1, INLINE_2, INLINE_TABLE_1, INLINE_TABLE_3 -> param.rawValues[0] |= (address - op.address) / 0x4 & 0xffff;
              case INLINE_TABLE_2, INLINE_3, INLINE_TABLE_4 -> throw new RuntimeException("Need to implement label bindings for " + param.type);
            }

            if((op.type == OpType.GOSUB_TABLE || op.type == OpType.JMP_TABLE) && param.type.isInlineTable()) {
              final int tableOffset = labels.get(param.label) / 4;

              for(int entryOffset = tableOffset; entryOffset < entries.size(); entryOffset++) {
                final int finalEntryOffset = entryOffset;
                if(entryOffset != tableOffset && labels.entrySet().stream().filter(e -> e.getValue() == finalEntryOffset * 0x4).map(Map.Entry::getKey).anyMatch(tables::contains)) {
                  break;
                }

                if(entries.get(entryOffset) instanceof final PointerTable table) {
                  tables.add(table.labels[0]);
                } else {
                  break;
                }
              }
            }
          }
        }
      }
    }

    final List tableOffsets = tables.stream().map(key -> labels.get(key) / 0x4).distinct().sorted(Comparator.reverseOrder()).toList();
    int maxOffset = entries.size(); // Used to know when to end jump tables that run into each other

    // Fix jump table addresses (note: these addresses are sorted from last to first)
    for(final int tableOffset : tableOffsets) {
      final List newLabels = new ArrayList<>();
      int offset = tableOffset;

      while(offset < maxOffset && entries.get(offset) instanceof final PointerTable table) {
        newLabels.add(table.labels[0]);
        offset++;
      }

      entries.set(tableOffset, new PointerTable(tableOffset * 0x4, newLabels.toArray(String[]::new)));

      for(int i = 1; i < newLabels.size(); i++) {
        entries.set(tableOffset + i, new Data((tableOffset + i) * 0x4, 0));
      }

      maxOffset = tableOffset;
    }

    final Script script = new Script(entries.size());
    entries.toArray(script.entries);

    for(final Map.Entry entry : labels.entrySet()) {
      script.labels.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey());
    }

    return script;
  }

  private String removeComment(final String line) {
    final int pos = line.indexOf(';');

    if(pos == -1) {
      return line;
    }

    return line.substring(0, pos);
  }

  private Entry lexLine(final int address, final String line) {
    final Matcher lineMatcher = LINE_PATTERN.matcher(line);
    if(lineMatcher.matches()) {
      final String command = lineMatcher.group(1);
      final String paramsStr = lineMatcher.group(2);

      try {
        if("entrypoint".equalsIgnoreCase(command)) {
          if(!LABEL_PARAM_PATTERN.matcher(paramsStr).matches()) {
            throw new RuntimeException("Invalid entrypoint label " + paramsStr);
          }

          return new Entrypoint(address, paramsStr.substring(1));
        } else if("data".equalsIgnoreCase(command)) {
          final Matcher stringMatcher = STRING_PATTERN.matcher(paramsStr);
          if(stringMatcher.matches()) {
            return LodString.fromString(address, stringMatcher.group(1));
          }

          return new Data(address, this.parseInt(paramsStr));
        } else if("rel".equalsIgnoreCase(command)) {
          if(!LABEL_PARAM_PATTERN.matcher(paramsStr).matches()) {
            throw new RuntimeException("Invalid relative pointer label " + paramsStr);
          }

          return new PointerTable(address, new String[] { paramsStr.substring(1) });
        } else {
          final OpType opType = OpType.byName(command);

          if(opType != null) {
            final int headerParam;
            Param[] params;

            if(paramsStr != null) {
              params = this.parseParams(address, address + 0x4, opType, paramsStr);

              if(opType.headerParamName == null) {
                headerParam = 0;
              } else {
                headerParam = params[0].rawValues[0];
                params = Arrays.copyOfRange(params, 1, params.length);
              }
            } else {
              headerParam = 0;
              params = new Param[0];
            }

            final Op op = new Op(address, opType, headerParam, params.length);
            System.arraycopy(params, 0, op.params, 0, params.length);
            return op;
          }
        }
      } catch(final NumberFormatException e) {
        System.err.println(e.getMessage());
      }
    }

    throw new RuntimeException("Invalid line \"" + line + '"');
  }

  private Param[] parseParams(final int opAddress, int address, final OpType opType, final String paramsString) {
    final String[] paramStrings = this.splitParameters(paramsString);
    final Param[] params = new Param[paramStrings.length];
    int headerParam = 0;

    for(int i = 0; i < params.length; i++) {
      final Param param = this.parseParam(opAddress, address, opType, headerParam, i - (opType.headerParamName != null ? 1 : 0), paramStrings[i]);
      params[i] = param;

      // If we have a header param, the first param returns will be a pseudo-param that doesn't advance the address since it's part of the header
      if(i != 0 || opType.headerParamName == null) {
        address += param.type.getWidth(paramStrings[i]) * 0x4;
      } else {
        headerParam = param.rawValues[0];
      }
    }

    return params;
  }

  private Param parseParam(final int opAddress, final int address, final OpType opType, final int headerParam, final int paramIndex, String paramString) {
    // Convert call function refs to ints
    if(CALL_PATTERN.matcher(paramString).matches()) {
      boolean found = false;
      for(int i = 0; i < this.meta.methods.length; i++) {
        if(this.meta.methods[i].name.equalsIgnoreCase(paramString)) {
          paramString = Integer.toString(i);
          found = true;
          break;
        }
      }

      if(!found) {
        throw new UnknownCallException("Unknown call " + paramString);
      }
    }

    // Immediates
    try {
      final int value = this.parseInt(paramString);

      if((value & 0xff00_0000) == 0) {
        return new Param(address, ParameterType.IMMEDIATE, new int[] { value }, OptionalInt.of(value), null);
      } else {
        return new Param(address, ParameterType.NEXT_IMMEDIATE, new int[] { this.packParam(ParameterType.NEXT_IMMEDIATE), value }, OptionalInt.of(value), null);
      }
    } catch(final NumberFormatException ignored) { }

    Matcher matcher;
    if(paramIndex == -1 && (opType == OpType.WAIT_CMP || opType == OpType.WAIT_CMP_0 || opType == OpType.JMP_CMP || opType == OpType.JMP_CMP_0) && (matcher = OPERATOR_PATTERN.matcher(paramString)).matches()) {
      final int operatorIndex = switch(matcher.group(1)) {
        case "<=" -> 0;
        case "<" -> 1;
        case "==" -> 2;
        case "!=" -> 3;
        case ">" -> 4;
        case ">=" -> 5;
        case "&" -> 6;
        case "!&" -> 7;
        default -> throw new RuntimeException("Unknown operator " + matcher.group(1));
      };

      return new Param(address, ParameterType.IMMEDIATE, new int[] { operatorIndex }, OptionalInt.of(operatorIndex), null);
    }

    if(paramIndex != -1 && opType == OpType.CALL) {
      final String enumClass = this.meta.methods[headerParam].params[paramIndex].type;
      if(this.meta.enums.containsKey(enumClass)) {
        final String[] enumValues = this.meta.enums.get(enumClass);

        for(int i = 0; i < enumValues.length; i++) {
          if(enumValues[i].equalsIgnoreCase(paramString)) {
            return new Param(address, ParameterType.IMMEDIATE, new int[] { i }, OptionalInt.of(i), null);
          }
        }

        throw new RuntimeException("Unknown " + enumClass + " value " + paramString);
      }
    }

    if((matcher = STORAGE_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      return new Param(address, ParameterType.STORAGE, new int[] { this.packParam(ParameterType.STORAGE, p0) }, OptionalInt.empty(), null);
    }

    if((matcher = OTHER_OTHER_STORAGE_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      final int p1 = this.parseInt(matcher.group(2));
      final int p2 = this.parseInt(matcher.group(3));
      return new Param(address, ParameterType.OTHER_OTHER_STORAGE, new int[] { this.packParam(ParameterType.OTHER_OTHER_STORAGE, p0, p1, p2) }, OptionalInt.empty(), null);
    }

    if((matcher = OTHER_STORAGE_OFFSET_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      final int p1 = this.parseInt(matcher.group(2));
      final int p2 = this.parseInt(matcher.group(3));
      return new Param(address, ParameterType.OTHER_STORAGE_OFFSET, new int[] { this.packParam(ParameterType.OTHER_STORAGE_OFFSET, p0, p1, p2) }, OptionalInt.empty(), null);
    }

    if((matcher = GAMEVAR_1_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      return new Param(address, ParameterType.GAMEVAR_1, new int[] { this.packParam(ParameterType.GAMEVAR_1, p0) }, OptionalInt.empty(), null);
    }

    if((matcher = GAMEVAR_2_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      final int p1 = this.parseInt(matcher.group(2));
      return new Param(address, ParameterType.GAMEVAR_2, new int[] { this.packParam(ParameterType.GAMEVAR_1, p0, p1) }, OptionalInt.empty(), null);
    }

    if((matcher = GAMEVAR_ARRAY_1_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      final int p1 = this.parseInt(matcher.group(2));
      return new Param(address, ParameterType.GAMEVAR_ARRAY_1, new int[] { this.packParam(ParameterType.GAMEVAR_ARRAY_1, p0, p1) }, OptionalInt.empty(), null);
    }

    if((matcher = GAMEVAR_ARRAY_2_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      final int p1 = this.parseInt(matcher.group(2));
      final int p2 = this.parseInt(matcher.group(3));
      return new Param(address, ParameterType.GAMEVAR_ARRAY_2, new int[] { this.packParam(ParameterType.GAMEVAR_ARRAY_2, p0, p1, p2) }, OptionalInt.empty(), null);
    }

    if((matcher = INLINE_1_MATCHER.matcher(paramString)).matches()) {
      final String val = matcher.group(1);

      final int inline;
      final String label;
      if(LABEL_PARAM_PATTERN.matcher(val).matches()) {
        inline = this.packParam(ParameterType.INLINE_1);
        label = val.substring(1);
      } else {
        final int value = this.parseInt(val);
        final int p0 = (value - opAddress) / 0x4;
        inline = this.packParam(ParameterType.INLINE_1) | p0 & 0xffff;
        label = null;
      }

      return new Param(address, ParameterType.INLINE_1, new int[] { inline }, OptionalInt.empty(), label);
    }

    if((matcher = INLINE_2_MATCHER.matcher(paramString)).matches()) {
      final String val = matcher.group(1);

      final int inline;
      final String label;
      if(LABEL_PARAM_PATTERN.matcher(val).matches()) {
        final int p2 = this.parseInt(matcher.group(2));
        inline = this.packParam(ParameterType.INLINE_2, 0, 0, p2);
        label = val.substring(1);
      } else {
        final int value = this.parseInt(val);
        final int p0 = (value - opAddress) / 0x4;
        final int p2 = this.parseInt(matcher.group(2));
        inline = this.packParam(ParameterType.INLINE_2, 0, 0, p2) | p0 & 0xffff;
        label = null;
      }

      return new Param(address, ParameterType.INLINE_2, new int[] { inline }, OptionalInt.empty(), label);
    }

    if((matcher = GAMEVAR_3_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      final int p1 = this.parseInt(matcher.group(2));
      return new Param(address, ParameterType.GAMEVAR_3, new int[] { this.packParam(ParameterType.GAMEVAR_3, p0, p1) }, OptionalInt.empty(), null);
    }

    if((matcher = INLINE_3_MATCHER.matcher(paramString)).matches()) {
      if(!matcher.group(1).equalsIgnoreCase(matcher.group(2))) {
        throw new RuntimeException("Invalid INLINE_3 def, addresses must match (" + matcher.group(1) + '/' + matcher.group(2) + ')');
      }

      final String val = matcher.group(1);

      final int inline;
      final String label;
      if(LABEL_PARAM_PATTERN.matcher(val).matches()) {
        final int p2 = this.parseInt(matcher.group(3));
        inline = this.packParam(ParameterType.INLINE_TABLE_1, 0, 0, p2);
        label = val.substring(1);
      } else {
        final int value = this.parseInt(val);
        final int p0 = (value - opAddress) / 0x4;
        final int p2 = this.parseInt(matcher.group(3));
        inline = this.packParam(ParameterType.INLINE_TABLE_1, 0, 0, p2) | p0 & 0xffff;
        label = null;
      }

      return new Param(address, ParameterType.INLINE_TABLE_1, new int[] { inline }, OptionalInt.empty(), label);
    }

    // INLINE_4
    // OTHER_STORAGE

    if((matcher = GAMEVAR_ARRAY_3_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      final int p1 = this.parseInt(matcher.group(2));
      return new Param(address, ParameterType.GAMEVAR_ARRAY_3, new int[] { this.packParam(ParameterType.GAMEVAR_ARRAY_3, p0, p1) }, OptionalInt.empty(), null);
    }

    if((matcher = GAMEVAR_ARRAY_4_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      final int p1 = this.parseInt(matcher.group(2));
      final int p2 = this.parseInt(matcher.group(3));
      return new Param(address, ParameterType.GAMEVAR_ARRAY_4, new int[] { this.packParam(ParameterType.GAMEVAR_ARRAY_4, p0, p1, p2) }, OptionalInt.empty(), null);
    }

    // GAMEVAR_ARRAY_5
    // _12
    // INLINE_5

    if((matcher = INLINE_6_PATTERN.matcher(paramString)).matches()) {
      if(!matcher.group(1).equalsIgnoreCase(matcher.group(2))) {
        throw new RuntimeException("Invalid INLINE_6 def, addresses must match (" + matcher.group(1) + '/' + matcher.group(2) + ')');
      }

      final String val = matcher.group(1);

      final int inline;
      final String label;
      if(LABEL_PARAM_PATTERN.matcher(val).matches()) {
        final int p2 = this.parseInt(matcher.group(3));
        inline = this.packParam(ParameterType.INLINE_TABLE_3, 0, 0, p2);
        label = val.substring(1);
      } else {
        final int value = this.parseInt(val);
        final int p0 = (value - opAddress) / 0x4;
        final int p2 = this.parseInt(matcher.group(3));
        inline = this.packParam(ParameterType.INLINE_TABLE_3, 0, 0, p2) | p0 & 0xffff;
        label = null;
      }

      return new Param(address, ParameterType.INLINE_TABLE_3, new int[] { inline }, OptionalInt.empty(), label);
    }

    // _15
    // _16
    // INLINE_7

    if((matcher = ID_PATTERN.matcher(paramString)).matches()) {
      final String id = matcher.group(1);
      final int[] packed = new int[ParameterType.ID.getWidth(id)];
      packed[0] = ParameterType.ID.opcode << 24 | id.length() << 16;

      for(int i = 0; i < id.length(); i++) {
        packed[1 + i / 4] |= (id.charAt(i) & 0xff) << i % 4 * 8;
      }

      return new Param(address, ParameterType.ID, packed, OptionalInt.empty(), null);
    }

    if((matcher = REG_PATTERN.matcher(paramString)).matches()) {
      final int p0 = this.parseInt(matcher.group(1));
      return new Param(address, ParameterType.REG, new int[] { this.packParam(ParameterType.REG, p0) }, OptionalInt.empty(), null);
    }

    throw new RuntimeException("Unknown param " + paramString);
  }

  private String[] splitParameters(final String parametersString) {
    final List commas = new ArrayList<>();
    int bracketCount = 0;

    for(int i = 0; i < parametersString.length(); i++) {
      switch(parametersString.substring(i, i + 1)) {
        case "[" -> bracketCount++;

        case "]" -> {
          bracketCount--;

          if(bracketCount < 0) {
            throw new RuntimeException("Invalid parameters " + parametersString);
          }
        }

        case "," -> {
          if(bracketCount == 0) {
            commas.add(i);
          }
        }
      }
    }

    commas.add(parametersString.length());

    final String[] params = new String[commas.size()];
    int start = 0;

    for(int i = 0; i < params.length; i++) {
      params[i] = parametersString.substring(start, commas.get(i)).strip();
      start = commas.get(i) + 1;
    }

    return params;
  }

  private int parseInt(final String val) {
    if(NUMBER_PATTERN.matcher(val).matches()) {
      if(val.startsWith("0x")) {
        return Integer.parseUnsignedInt(val.substring(2), 16);
      }

      return Integer.parseInt(val);
    }

    throw new NumberFormatException("Invalid number " + val);
  }

  private int packParam(final ParameterType type, final int p0, final int p1, final int p2) {
    return type.opcode << 24 | (p2 & 0xff) << 16 | (p1 & 0xff) << 8 | p0 & 0xff;
  }

  private int packParam(final ParameterType type, final int p0, final int p1) {
    return this.packParam(type, p0, p1, 0);
  }

  private int packParam(final ParameterType type, final int p0) {
    return this.packParam(type, p0, 0, 0);
  }

  private int packParam(final ParameterType type) {
    return this.packParam(type, 0, 0, 0);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy