diff --git a/build_helper/precommit.pl b/build_helper/precommit.pl index c4bf7bc..9f94f0c 100644 --- a/build_helper/precommit.pl +++ b/build_helper/precommit.pl @@ -98,22 +98,29 @@ my $branch_suffix = 'master' eq $branch ? '' : '-' . $branch; # get OLD major and minor version number -my $major_version = undef; -my $minor_version = undef; +my $major_version = undef; +my $middle_version = undef; +my $minor_version = undef; open my $fh, '<', $java_file or die "Cannot read $java_file: $!\n"; while (my $line = <$fh>) { if ($line =~ /(\bint\s+VERSION_MAJOR\s*=)\s*(\d+)/) { $major_version = $2; } + elsif ($line =~ /(\bint\s+VERSION_MIDDLE\s*=)\s*(\d+)/) { + $middle_version = $2; + } elsif ($line =~ /(\bint\s+VERSION_MINOR\s*=)\s*(\-?\d+)/) { $minor_version = $2; } - last if defined $major_version && defined $minor_version; + last if defined $major_version && defined $middle_version && defined $minor_version; } close $fh or die "Cannot close $java_file: $!\n"; if (! defined $major_version) { die "Did not find major version in $java_file.\n"; } +if (! defined $middle_version) { + die "Did not find middle version in $java_file.\n"; +} if (! defined $minor_version) { die "Did not find minor version in $java_file.\n"; } @@ -123,7 +130,7 @@ $minor_version++; } my $commit_time = time(); -my $version = $major_version . '.' . $minor_version . $branch_suffix; +my $version = $major_version . '.' . $middle_version . '.' . $minor_version . $branch_suffix; # Build up the commands to replace VERSION_MINOR, BRANCH and COMMIT_TIME in Midica.java. # 1. perl and options and opening single quote (') for the regex diff --git a/midica.jar b/midica.jar index 292c827..6a3d0de 100644 Binary files a/midica.jar and b/midica.jar differ diff --git a/src/org/midica/Midica.java b/src/org/midica/Midica.java index 4793f8f..0f79754 100644 --- a/src/org/midica/Midica.java +++ b/src/org/midica/Midica.java @@ -23,23 +23,26 @@ public class Midica { /** Major version number. This has to be incremented manually. */ - public static final int VERSION_MAJOR = 1; + private static final int VERSION_MAJOR = 1; + + /** Middle version number. This has to be incremented manually. */ + private static final int VERSION_MIDDLE = 3; /** * Minor version number. This is intended to be incremented automatically by precommit.pl. * After switching to a new major version, this has to be set to "-1" manually, so that * precommit.pl starts with "0" again. */ - public static final int VERSION_MINOR = 3; + private static final int VERSION_MINOR = 1; /** UNIX timestamp of the last commit */ - public static final int COMMIT_TIME = 1618087070; + public static final int COMMIT_TIME = 1619630078; /** Branch name. Automatically changed by precommit.pl */ public static final String BRANCH = "master"; /** Full version string. */ - public static final String VERSION = VERSION_MAJOR + "." + VERSION_MINOR + ("master".equals(BRANCH) ? "" : "-" + BRANCH); + public static final String VERSION = VERSION_MAJOR + "." + VERSION_MIDDLE + "." + VERSION_MINOR + ("master".equals(BRANCH) ? "" : "-" + BRANCH); /** Author name */ public static final String AUTHOR = "Jan Trukenmüller"; diff --git a/src/org/midica/config/Dict.java b/src/org/midica/config/Dict.java index c875d63..bd2ff8c 100644 --- a/src/org/midica/config/Dict.java +++ b/src/org/midica/config/Dict.java @@ -1494,6 +1494,7 @@ public class Dict { public static final String ERROR_CHORD_CONTAINS_ALREADY = "error_chord_contains_already"; public static final String ERROR_CHORD_DEF_NOT_ALLOWED_HERE = "error_chord_def_not_allowed_here"; public static final String ERROR_CHORD_NUM_OF_ARGS = "error_chord_num_of_args"; + public static final String ERROR_CHORD_REDUNDANT_SEP = "error_chord_redundant_sep"; public static final String ERROR_CONST_NUM_OF_ARGS = "error_const_num_of_args"; public static final String ERROR_CONST_ALREADY_DEFINED = "error_const_already_defined"; public static final String ERROR_CONST_NAME_EQ_VALUE = "error_const_name_eq_value"; @@ -1529,6 +1530,7 @@ public class Dict { public static final String ERROR_PATTERN_INDEX_INVALID = "error_pattern_index_invalid"; public static final String ERROR_PATTERN_INDEX_TOO_HIGH = "error_pattern_index_too_high"; public static final String ERROR_PATTERN_RECURSION_DEPTH = "error_pattern_recursion_depth"; + public static final String ERROR_PATTERN_UNDEFINED = "error_pattern_undefined"; public static final String ERROR_META_NUM_OF_ARGS = "error_meta_num_of_arts"; public static final String ERROR_META_UNKNOWN_CMD = "error_meta_unknown_cmd"; public static final String ERROR_SOFT_KARAOKE_UNKNOWN_CMD = "error_soft_karaoke_unknown_cmd"; @@ -3034,6 +3036,7 @@ private static void initLanguageEnglish() { set( ERROR_CHORD_CONTAINS_ALREADY, "Note cannot be defined more than once in the same chord: " ); set( ERROR_CHORD_DEF_NOT_ALLOWED_HERE, "a chord definition is not allowed inside a block
maybe you forgot to close the block." ); set( ERROR_CHORD_NUM_OF_ARGS, "wrong number of arguments in CHORD command" ); + set( ERROR_CHORD_REDUNDANT_SEP, "redundant separator in chord definition" ); set( ERROR_CONST_NUM_OF_ARGS, "wrong number of arguments in CONSTANT definition" ); set( ERROR_CONST_ALREADY_DEFINED, "constant already defined: " ); set( ERROR_CONST_NAME_EQ_VALUE, "constant name must be different from it's value: " ); @@ -3069,6 +3072,7 @@ private static void initLanguageEnglish() { set( ERROR_PATTERN_INDEX_INVALID, "pattern index not a number: " ); set( ERROR_PATTERN_INDEX_TOO_HIGH, "pattern index too high: " ); set( ERROR_PATTERN_RECURSION_DEPTH, "Recursion depth in pattern too big." ); + set( ERROR_PATTERN_UNDEFINED, "Pattern not defined: " ); set( ERROR_META_NUM_OF_ARGS, "no arguments allowed in meta command" ); set( ERROR_META_UNKNOWN_CMD, "unknown meta command: " ); set( ERROR_SOFT_KARAOKE_UNKNOWN_CMD, "unknown soft karaoke command: " ); diff --git a/src/org/midica/config/Laf.java b/src/org/midica/config/Laf.java index 5ea9d6b..c857fda 100644 --- a/src/org/midica/config/Laf.java +++ b/src/org/midica/config/Laf.java @@ -106,7 +106,7 @@ public class Laf { public static final Color COLOR_BORDER = new Color( 23, 121, 186 ); private static final Color COLOR_BORDER_LIGHT = new Color( 148, 204, 242 ); private static final Color COLOR_ALERT = new Color( 255, 0, 0 ); - public static final Color COLOR_PANEL = new Color( 238, 238, 238 ); + public static final Color COLOR_PANEL = new Color( 243, 240, 233 ); private static final Color COLOR_WHITE = new Color( 255, 255, 255 ); private static final Color COLOR_BLACK = new Color( 0, 0, 0 ); private static final Color COLOR_INACTIVE = new Color( 228, 228, 228 ); diff --git a/src/org/midica/file/read/MidicaPLParser.java b/src/org/midica/file/read/MidicaPLParser.java index 5b04c41..9c8c662 100644 --- a/src/org/midica/file/read/MidicaPLParser.java +++ b/src/org/midica/file/read/MidicaPLParser.java @@ -243,6 +243,10 @@ public class MidicaPLParser extends SequenceParser { private static Pattern crlfSkPattern = null; private static Pattern sharpPattern = null; private static Pattern flatPattern = null; + private static Pattern optAssignPattern = null; + private static Pattern chordAssignPattern = null; + private static Pattern varAssignPattern = null; + private static Pattern chordSepPattern = null; private static boolean isSoftKaraoke = false; private static boolean isDefineParsRun = false; // parsing run for define commands @@ -636,6 +640,18 @@ private void compilePatterns() { // find sharp(s) or flat(s) in a note name sharpPattern = Pattern.compile("^.+" + Pattern.quote(Config.getConfiguredSharpOrFlat(true)) + ".*"); flatPattern = Pattern.compile("^.+" + Pattern.quote(Config.getConfiguredSharpOrFlat(false)) + ".*"); + + // splitting option name from option value: '=' and/or whitespace(s) + optAssignPattern = Pattern.compile("\\s*" + Pattern.quote(OPT_ASSIGNER) + "\\s*|\\s+"); + + // splitting chord name from chord: '=' and/or whitespace(s) + chordAssignPattern = Pattern.compile("\\s*" + Pattern.quote(CHORD_ASSIGNER) + "\\s*|\\s+"); + + // splitting variable name from assignment: '=' and/or whitespace(s) + varAssignPattern = Pattern.compile("\\s*" + Pattern.quote(VAR_ASSIGNER) + "\\s*|\\s+"); + + // splitting notes of the chord: ',' and/or whitespace(s) + chordSepPattern = Pattern.compile("\\s*" + Pattern.quote(CHORD_SEPARATOR) + "\\s*|\\s+"); } /** @@ -653,19 +669,17 @@ public void parseLine(String line) throws ParseException { line = replaceConstants(line); } - // replace variables? - boolean mustReplaceVars = isDefaultParsRun // only in the default run - && MODE_DEFAULT == currentMode // not inside of functions - && 0 == nestableBlkDepth; // not inside of a block - if (mustReplaceVars) { - String[] tokens = line.split("\\s+", 2); + currentLineContent = line; + + // replace variables in stack traces + // (the real replacement will be later) + if (isDefaultParsRun // only in the default run + && MODE_DEFAULT == currentMode // not inside of functions + && 0 == nestableBlkDepth // not inside of a block + && line.startsWith(VAR_SYMBOL)) { // not for VAR definitions - // inside of VAR definitions the replacement is done later - if (! VAR.equals(tokens[0])) { - line = replaceVariables(line); - } + currentLineContent = replaceVariables(currentLineContent); } - currentLineContent = line; String[] tokens = line.split("\\s+", 3); @@ -847,6 +861,16 @@ private void parsingRun(ArrayList lines) throws ParseException, IOExcept */ private void parseTokens(String[] tokens) throws ParseException { + // replace variables for channel commands + if (isDefaultParsRun && MODE_DEFAULT == currentMode && tokens.length > 2) { + + // channel command? + if (tokens[0].matches("^\\d{1,2}$") || tokens[0].contains(VAR_SYMBOL)) { + tokens[0] = replaceVariables(tokens[0]); // replace channel + tokens[1] = replaceVariables(tokens[1]); // replace note/chord + } + } + // reset if/elsif/else conditions in blocks if (isDefaultParsRun && 0 == nestableBlkDepth @@ -1945,6 +1969,11 @@ else if (BLOCK_CLOSE.equals(cmd)) { throw new ParseException(Dict.get(Dict.ERROR_BLOCK_UNMATCHED_CLOSE)); } + // nothing more needed for functions + if (isFuncParsRun) { + return; + } + // construct options string from tokens StringBuilder optionsStrBuf = new StringBuilder(""); String optionsStr = ""; @@ -1965,8 +1994,10 @@ else if (BLOCK_CLOSE.equals(cmd)) { String tuplet = null; boolean mustCheckChain = isDefaultParsRun && nestableBlkDepth == 0; if (optionsStr.length() > 0) { + ArrayList options = parseOptions(optionsStr, isFake); for (CommandOption opt : options) { + String optName = opt.getName(); if (OPT_QUANTITY.equals(optName)) quantity = opt.getQuantity(); @@ -2151,7 +2182,7 @@ else if (3 == tokens.length) { throw new ParseException(Dict.get(Dict.ERROR_CHORD_NUM_OF_ARGS)); // get and process chord name - String[] chordParts = chordDef.split("[" + Pattern.quote(CHORD_ASSIGNER) + "\\s]+", 2); // chord name and chords can be separated by CHORD_ASSIGNER (e,g, "=") and/or whitespace(s) + String[] chordParts = chordAssignPattern.split(chordDef, 2); // element 0: name; element 1: value if (chordParts.length < 2) { throw new ParseException(Dict.get(Dict.ERROR_CHORD_NUM_OF_ARGS)); } @@ -2169,9 +2200,13 @@ else if (Dict.percussionExists(chordName)) { // get and process chord elements TreeSet chord = new TreeSet<>(); - String[] notes = chordValue.split("[" + CHORD_SEPARATOR + "\\s]+"); // notes of the chord can be separated by CHORD_SEPARATOR (e,g, "=") and/or whitespace(s) + String[] notes = chordSepPattern.split(chordValue, -1); for (String note : notes) { + + if ("".equals(note)) + throw new ParseException(Dict.get(Dict.ERROR_CHORD_REDUNDANT_SEP)); + int noteVal = parseNote(note); if (chord.contains(noteVal)) { throw new ParseException(Dict.get(Dict.ERROR_CHORD_CONTAINS_ALREADY) + note); @@ -2214,8 +2249,10 @@ private void parseCALL(String[] tokens, boolean isFake) throws ParseException { boolean multiple = false; String condIf = null; if (optionString != null && ! "".equals(optionString)) { + ArrayList options = parseOptions(optionString, isFake); for (CommandOption opt : options) { + String optName = opt.getName(); if (OPT_QUANTITY.equals(optName)) quantity = opt.getQuantity(); @@ -2383,11 +2420,11 @@ private void parsePatternCall(String[] tokens, boolean isFake) throws ParseExcep int outerShift = 0; String outerSyllable = null; if (outerOptStr != null) { - ArrayList callOptions = parseOptions(outerOptStr, false); + ArrayList callOptions = parseOptions(outerOptStr, false); for (CommandOption opt : callOptions) { - String optName = opt.getName(); + String optName = opt.getName(); if (OPT_VELOCITY.equals(optName)) { int velocity = opt.getVelocity(); if (! isFake) @@ -2483,7 +2520,13 @@ else if (OPT_SHIFT.equals(optName)) { // replace variables currentLineContent = patternLine; - patternLine = replaceVariables(patternLine); + String[] rawPatLineTokens = whitespace.split(patternLine); + if (BLOCK_OPEN.equals(rawPatLineTokens[0]) || BLOCK_CLOSE.equals(rawPatLineTokens[0])) { + // variables will be replaced later + } + else { + patternLine = replaceVariables(patternLine); + } currentLineContent = patternLine; // special line inside the pattern? @@ -2516,11 +2559,11 @@ else if (OPT_SHIFT.equals(optName)) { Float innerDuration = null; String innerTremolo = null; if (patternTokens.length > 2) { - ArrayList patternOptions = parseOptions(patternTokens[2], false); + ArrayList patternOptions = parseOptions(patternTokens[2], false); for (CommandOption opt : patternOptions) { - String optName = opt.getName(); + String optName = opt.getName(); if (OPT_VELOCITY.equals(optName)) { innerVelocity = opt.getVelocity(); } @@ -2638,6 +2681,10 @@ private Object[] parseParameters(String paramStr) throws ParseException { HashMap paramsNamed = new HashMap<>(); if (paramStr != null && ! "".equals(paramStr)) { + + if (isDefaultParsRun) + paramStr = replaceVariables(paramStr); + paramStr = clean(paramStr); String[] params = paramStr.split("\\s*" + Pattern.quote(PARAM_SEPARATOR) + "\\s*", -1); for (String rawParam : params) { @@ -2736,11 +2783,11 @@ else if (REST.equals(tokens[0])) { // check options if (tokens.length > 2) { - ArrayList patternOptions = parseOptions(tokens[2], true); + ArrayList patternOptions = parseOptions(tokens[2], true); for (CommandOption opt : patternOptions) { - String optName = opt.getName(); + String optName = opt.getName(); if (OPT_VELOCITY.equals(optName) || OPT_DURATION.equals(optName) || OPT_MULTIPLE.equals(optName) || OPT_QUANTITY.equals(optName) || OPT_TREMOLO.equals(optName)) { // ok @@ -2760,7 +2807,7 @@ else if (0 == tokens.length) { } /** - * Ensured that (nested or real) pattern call tokens contain the pattern call + * Ensures that (nested or real) pattern call tokens contain the pattern call * and the options token in the right position. * * For real pattern calls: @@ -2779,8 +2826,9 @@ else if (0 == tokens.length) { * @param tokens tokens to be checked or changed * @param indexOffset pattern call index (**2** for real calls, **1** for nested calls) * @return the corrected tokens, if changed, or the original tokens, if no change was necessary. + * @throws ParseException on syntax errors. */ - private String[] reorganizePatternCallTokens(String[] tokens, int indexOffset) { + private String[] reorganizePatternCallTokens(String[] tokens, int indexOffset) throws ParseException { int iPat = indexOffset; // index containing the pattern call int iOpt = indexOffset + 1; // index containing the options @@ -2797,8 +2845,15 @@ private String[] reorganizePatternCallTokens(String[] tokens, int indexOffset) { String paramString = patCallMatcher.group(3); String options = patCallMatcher.group(4); - // unknown pattern name (probably a duration) - don't change anything + // unknown pattern name? (maybe a length) if (! definedPatternNames.contains(patternName)) { + + // parameters? - not existing pattern name + if (paramString != null) + throw new ParseException(Dict.get(Dict.ERROR_PATTERN_UNDEFINED) + patternName); + + // probably a note length + // don't change anything return tokens; } @@ -3136,7 +3191,7 @@ else if (3 == tokens.length) { } // split definition string by OPT_ASSIGNER (e,g, "=") and/or whitespace(s) - String[] defParts = def.split("[" + Pattern.quote(OPT_ASSIGNER) + "\\s]+", 2); + String[] defParts = def.split("\\s*" + Pattern.quote(OPT_ASSIGNER) + "\\s*|\\s+", 2); if (defParts.length < 2) throw new ParseException(Dict.get(Dict.ERROR_DEFINE_NUM_OF_ARGS)); String cmdId = clean(defParts[0]); @@ -3277,7 +3332,7 @@ private void parseCONST(String[] tokens) throws ParseException { } // CONST with name but without value? - String[] assignParts = tokens[1].split("[" + Pattern.quote(VAR_ASSIGNER) + "\\s]+", 2); // const name and value can be separated by "=" and/or whitespace(s) + String[] assignParts = tokens[1].split("\\s*" + Pattern.quote(VAR_ASSIGNER) + "\\s*|\\s+", 2); // const name and value can be separated by "=" and/or whitespace(s) if (assignParts.length < 2) { throw new ParseException(Dict.get(Dict.ERROR_CONST_NUM_OF_ARGS)); } @@ -3318,7 +3373,7 @@ private void parseVAR(String[] tokens, boolean isFake) throws ParseException { } // VAR with name but without value? - String[] assignParts = tokens[1].split("[" + Pattern.quote(VAR_ASSIGNER) + "\\s]+", 2); // var name and value can be separated by "=" and/or whitespace(s) + String[] assignParts = varAssignPattern.split(tokens[1], 2); // element 0: name; element 1: value if (assignParts.length < 2) { throw new ParseException(Dict.get(Dict.ERROR_VAR_NUM_OF_ARGS)); } @@ -3910,6 +3965,10 @@ private void parseChannelCmd(String[] tokens, boolean isFake) throws ParseExcept if (0 == subTokens.length) throw new ParseException(Dict.get(Dict.ERROR_CH_CMD_NUM_OF_ARGS)); + // replace variables in duration + if (isDefaultParsRun && ! isFake) + subTokens[0] = replaceVariables(subTokens[0]); + // process duration String durationStr = subTokens[0]; int duration; @@ -3943,11 +4002,11 @@ else if (patterns.containsKey(durationStr)) { int shift = 0; String syllable = null; if (2 == subTokens.length) { - ArrayList options = parseOptions(subTokens[1], isFake); + ArrayList options = parseOptions(subTokens[1], isFake); for (CommandOption opt : options) { - String optName = opt.getName(); + String optName = opt.getName(); if (OPT_VELOCITY.equals(optName)) { int velocity = opt.getVelocity(); if (! isFake) @@ -4071,142 +4130,197 @@ else if (currentDuration > tremolo) { /** * Parses the options part of a command. * - * @param optString The options string of the channel command to be parsed. - * @param isFake **true**, if this is called inside a function definition, pattern definition or block. - * @return All options and their values that have been found in the - * provided options string. - * @throws ParseException If the command cannot be parsed. + * The options may still contain unreplaced variables or parameters. + * These are replaced or left as-is. This depends on the current parsing run, + * the given **isFake** parameter, and other criteria. + * + * @param optString The raw options string of the command to be parsed. + * @param isFake **true**, if this is called inside a function definition, pattern definition or block. + * @return All options and their values that have been found in the + * provided options string. + * @throws ParseException if the options cannot be parsed. */ private ArrayList parseOptions(String optString, boolean isFake) throws ParseException { - ArrayList options = new ArrayList<>(); + // split (raw) option string (variables are not yet replaced) + ArrayList rawOptTokens = new ArrayList<>(); String[] optTokens = optString.split(OPT_SEPARATOR, -1); for (String opt : optTokens) { opt = clean(opt); - String[] optParts = opt.split("[" + Pattern.quote(OPT_ASSIGNER) + "\\s]+", 2); // name and value can be separated by OPT_ASSIGNER (e,g, "=") and/or whitespace(s) - if (optParts.length > 2) - throw new ParseException(Dict.get(Dict.ERROR_CANT_PARSE_OPTIONS) + opt); - // value is a variable? - don't check if this is fake - if (isFake && optParts.length > 1 && varPattern.matcher(optParts[1]).find()) - return options; + // separate name and value of one option + String[] optParts = optAssignPattern.split(opt, 2); - // construct name and value - String optName = optParts[0]; - CommandOption optValue = new CommandOption(); - - if (isCondCheckParsRun && ! IF.equals(optName) && ! ELSIF.equals(optName) && ! ELSE.equals(optName)) - continue; + rawOptTokens.add(optParts); + } + + // parse the options + ArrayList options = new ArrayList<>(); + for (String[] rawOptParts : rawOptTokens) { - if (V.equals(optName) || VELOCITY.equals(optName)) { - if (optParts.length < 2 || "".equals(optParts[1])) { - throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); - } - optName = OPT_VELOCITY; - int val = toInt(optParts[1]); - if (val > 127) - throw new ParseException(Dict.get(Dict.ERROR_VEL_NOT_MORE_THAN_127)); - if (val < 1) - throw new ParseException(Dict.get(Dict.ERROR_VEL_NOT_LESS_THAN_1)); - optValue.set(optName, val); - } - else if (D.equals(optName) || DURATION.equals(optName)) { - if (optParts.length < 2 || "".equals(optParts[1])) { - throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); - } - optName = OPT_DURATION; - String[] valueParts = optParts[1].split(Pattern.quote(DURATION_PERCENT), -1); - float val = toFloat(valueParts[0]); - if (valueParts.length > 1) - val /= 100; // percentage --> numeric - if (val <= 0.0) - throw new ParseException(Dict.get(Dict.ERROR_DURATION_MORE_THAN_0)); - optValue.set(optName, val); - } - else if (Q.equals(optName) || QUANTITY.equals(optName)) { - if (optParts.length < 2 || "".equals(optParts[1])) { - throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); - } - optName = OPT_QUANTITY; - optValue.set(optName, toInt(optParts[1], true)); - } - else if (M.equals(optName) || MULTIPLE.equals(optName)) { - optName = OPT_MULTIPLE; - optValue.set(optName, true); - if (optParts.length > 1) - throw new ParseException(Dict.get(Dict.ERROR_OPTION_VAL_NOT_ALLOWED) + optName); - } - else if (L.equals(optName) || LYRICS.equals(optName)) { - if (optParts.length < 2 || "".equals(optParts[1])) { - throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); - } - optName = OPT_LYRICS; - optValue.set(optName, optParts[1]); - } - else if (T.equals(optName) || TUPLET.equals(optName)) { - optName = OPT_TUPLET; - if (1 == optParts.length) { - optValue.set(optName, TRIPLET); + CommandOption opt; + if (isDefaultParsRun) { + if (OPT_IF.equals(rawOptParts[0]) || OPT_ELSIF.equals(rawOptParts[0])) { + opt = parseOption(rawOptParts, isFake); + if (null == opt) + continue; + + String condition = opt.getCondition(); + condition = replaceVariables(condition); + opt.set(opt.getName(), condition); } else { - Pattern pattern = Pattern.compile("^(\\d+)" + Pattern.quote(TUPLET_FOR) + "(\\d+)$"); - Matcher matcher = pattern.matcher(optParts[1]); - if (matcher.matches()) { - String num1 = matcher.group(1); - String num2 = matcher.group(2); - if ("0".equals(num1) || "0".equals(num2)) - throw new ParseException(Dict.get(Dict.ERROR_TUPLET_INVALID) + optParts[1]); - optValue.set(optName, optParts[1]); - } - else { - throw new ParseException(Dict.get(Dict.ERROR_TUPLET_INVALID) + optParts[1]); + if (rawOptParts.length > 1) { + rawOptParts[1] = replaceVariables(rawOptParts[1]); } + opt = parseOption(rawOptParts, isFake); } } - else if (TR.equals(optName) || TREMOLO.equals(optName)) { - if (optParts.length < 2 || "".equals(optParts[1])) { - throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); - } - optName = OPT_TREMOLO; - optValue.setValueString(optParts[1]); - optValue.set(optName, parseDuration(optParts[1])); + else { + opt = parseOption(rawOptParts, isFake); } - else if (S.equals(optName) || SHIFT.equals(optName)) { - if (optParts.length < 2 || "".equals(optParts[1])) { - throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); - } - optName = OPT_SHIFT; - optValue.set(optName, toInt(optParts[1], false)); + + if (opt != null) + options.add(opt); + } + + return options; + } + + /** + * Parses one single command option. + * + * The given optParts may have one or two elements. + * + * The first element is the option name. The second element is the option's value. + * + * @param optParts first element: **name**; second element: **value** (optional) + * @param isFake **true**, if this is called inside a function definition, pattern definition or block. + * @return a parsed option, or **null** if it's too early to parse the option. + * @throws ParseException if the options cannot be parsed. + */ + CommandOption parseOption(String[] optParts, boolean isFake) throws ParseException { + if (optParts.length > 2) + throw new ParseException(Dict.get(Dict.ERROR_CANT_PARSE_OPTIONS) + String.join(" ", optParts)); + + // value is a variable? - don't check if this is fake + if (isFake && optParts.length > 1 && varPattern.matcher(optParts[1]).find()) + return null; + + // construct name and value + String optName = optParts[0]; + CommandOption cmdOpt = new CommandOption(); + + if (isCondCheckParsRun && ! IF.equals(optName) && ! ELSIF.equals(optName) && ! ELSE.equals(optName)) + return null; + + if (V.equals(optName) || VELOCITY.equals(optName)) { + if (optParts.length < 2 || "".equals(optParts[1])) { + throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); + } + optName = OPT_VELOCITY; + int val = toInt(optParts[1]); + if (val > 127) + throw new ParseException(Dict.get(Dict.ERROR_VEL_NOT_MORE_THAN_127)); + if (val < 1) + throw new ParseException(Dict.get(Dict.ERROR_VEL_NOT_LESS_THAN_1)); + cmdOpt.set(optName, val); + } + else if (D.equals(optName) || DURATION.equals(optName)) { + if (optParts.length < 2 || "".equals(optParts[1])) { + throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); + } + optName = OPT_DURATION; + String[] valueParts = optParts[1].split(Pattern.quote(DURATION_PERCENT), -1); + float val = toFloat(valueParts[0]); + if (valueParts.length > 1) + val /= 100; // percentage --> numeric + if (val <= 0.0) + throw new ParseException(Dict.get(Dict.ERROR_DURATION_MORE_THAN_0)); + cmdOpt.set(optName, val); + } + else if (Q.equals(optName) || QUANTITY.equals(optName)) { + if (optParts.length < 2 || "".equals(optParts[1])) { + throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); + } + optName = OPT_QUANTITY; + cmdOpt.set(optName, toInt(optParts[1], true)); + } + else if (M.equals(optName) || MULTIPLE.equals(optName)) { + optName = OPT_MULTIPLE; + cmdOpt.set(optName, true); + if (optParts.length > 1) + throw new ParseException(Dict.get(Dict.ERROR_OPTION_VAL_NOT_ALLOWED) + optName); + } + else if (L.equals(optName) || LYRICS.equals(optName)) { + if (optParts.length < 2 || "".equals(optParts[1])) { + throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); + } + optName = OPT_LYRICS; + cmdOpt.set(optName, optParts[1]); + } + else if (T.equals(optName) || TUPLET.equals(optName)) { + optName = OPT_TUPLET; + if (1 == optParts.length) { + cmdOpt.set(optName, TRIPLET); } - else if (IF.equals(optName)) { - if (isCondCheckParsRun && (optParts.length < 2 || "".equals(optParts[1]))) { - throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); + else { + Pattern pattern = Pattern.compile("^(\\d+)" + Pattern.quote(TUPLET_FOR) + "(\\d+)$"); + Matcher matcher = pattern.matcher(optParts[1]); + if (matcher.matches()) { + String num1 = matcher.group(1); + String num2 = matcher.group(2); + if ("0".equals(num1) || "0".equals(num2)) + throw new ParseException(Dict.get(Dict.ERROR_TUPLET_INVALID) + optParts[1]); + cmdOpt.set(optName, optParts[1]); } - String val = optParts.length >= 2 ? optParts[1] : ""; - optName = OPT_IF; - optValue.set(optName, val); - } - else if (ELSIF.equals(optName)) { - if (isCondCheckParsRun && (optParts.length < 2 || "".equals(optParts[1]))) { - throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); + else { + throw new ParseException(Dict.get(Dict.ERROR_TUPLET_INVALID) + optParts[1]); } - String val = optParts.length >= 2 ? optParts[1] : ""; - optName = OPT_ELSIF; - optValue.set(optName, val); } - else if (ELSE.equals(optName)) { - optName = OPT_ELSE; - optValue.set(optName, true); - if (optParts.length > 1) - throw new ParseException(Dict.get(Dict.ERROR_OPTION_VAL_NOT_ALLOWED) + optName); + } + else if (TR.equals(optName) || TREMOLO.equals(optName)) { + if (optParts.length < 2 || "".equals(optParts[1])) { + throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); } - else { - throw new ParseException(Dict.get(Dict.ERROR_UNKNOWN_OPTION) + optName); + optName = OPT_TREMOLO; + cmdOpt.setValueString(optParts[1]); + cmdOpt.set(optName, parseDuration(optParts[1])); + } + else if (S.equals(optName) || SHIFT.equals(optName)) { + if (optParts.length < 2 || "".equals(optParts[1])) { + throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); } - options.add(optValue); + optName = OPT_SHIFT; + cmdOpt.set(optName, toInt(optParts[1], false)); + } + else if (IF.equals(optName)) { + if (isCondCheckParsRun && (optParts.length < 2 || "".equals(optParts[1]))) { + throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); + } + String val = optParts.length >= 2 ? optParts[1] : ""; + optName = OPT_IF; + cmdOpt.set(optName, val); + } + else if (ELSIF.equals(optName)) { + if (isCondCheckParsRun && (optParts.length < 2 || "".equals(optParts[1]))) { + throw new ParseException(Dict.get(Dict.ERROR_OPTION_NEEDS_VAL) + optName); + } + String val = optParts.length >= 2 ? optParts[1] : ""; + optName = OPT_ELSIF; + cmdOpt.set(optName, val); + } + else if (ELSE.equals(optName)) { + optName = OPT_ELSE; + cmdOpt.set(optName, true); + if (optParts.length > 1) + throw new ParseException(Dict.get(Dict.ERROR_OPTION_VAL_NOT_ALLOWED) + optName); + } + else { + throw new ParseException(Dict.get(Dict.ERROR_UNKNOWN_OPTION) + optName); } - return options; + return cmdOpt; } /** @@ -4370,8 +4484,8 @@ private String addMultiple(String original) { return subTokens[0] + " " + MULTIPLE; } - // cut away trailing whitespaces and option separators (e.g. ',') - String cleanedOptString = optStr.replaceFirst("[" + Pattern.quote(OPT_SEPARATOR) + "\\s]+$", ""); + // cut away trailing whitespaces + String cleanedOptString = optStr.replaceFirst("\\s+$", ""); // append the option MULTIPLE subTokens[1] = cleanedOptString + OPT_SEPARATOR + MULTIPLE; diff --git a/test/org/midica/file/read/MidicaPLParserTest.java b/test/org/midica/file/read/MidicaPLParserTest.java index 9536ef2..10c5299 100644 --- a/test/org/midica/file/read/MidicaPLParserTest.java +++ b/test/org/midica/file/read/MidicaPLParserTest.java @@ -784,6 +784,55 @@ void testParseFilesWorking() throws ParseException { assertEquals( 23040, instruments.get(5).getCurrentTicks() ); } + + parse(getWorkingFile("condition")); + messages = getMessagesByStatus("91"); + { + int i = 0; + + // CALL try_1(/32, ...) + assertEquals( "0/1/91/f+2 / 64", messages.get(i++).toString() ); + assertEquals( "60/1/91/f+2 / 64", messages.get(i++).toString() ); + assertEquals( "120/1/91/a+2 / 64", messages.get(i++).toString() ); + + // CALL try_2(/32, ...) + assertEquals( "180/1/91/f+3 / 64", messages.get(i++).toString() ); + assertEquals( "240/1/91/f+3 / 64", messages.get(i++).toString() ); + assertEquals( "300/1/91/a+3 / 64", messages.get(i++).toString() ); + + // call pattern pat_try_1(/16, ...) + assertEquals( "360/1/91/f+4 / 64", messages.get(i++).toString() ); + assertEquals( "480/1/91/f+4 / 64", messages.get(i++).toString() ); + assertEquals( "600/1/91/a+3 / 64", messages.get(i++).toString() ); + + // call pattern pat_try_2(/16, ...) + assertEquals( "720/1/91/f+4 / 64", messages.get(i++).toString() ); + assertEquals( "840/1/91/f+4 / 64", messages.get(i++).toString() ); + assertEquals( "960/1/91/a+3 / 64", messages.get(i++).toString() ); + + // CALL try_3(/32, ...) + assertEquals( "1080/1/91/f+2 / 64", messages.get(i++).toString() ); + assertEquals( "1140/1/91/f+2 / 64", messages.get(i++).toString() ); + assertEquals( "1200/1/91/a+2 / 64", messages.get(i++).toString() ); + + // CALL try_4(/32, ...) + assertEquals( "1260/1/91/f+3 / 64", messages.get(i++).toString() ); + assertEquals( "1320/1/91/f+3 / 64", messages.get(i++).toString() ); + assertEquals( "1380/1/91/a+3 / 64", messages.get(i++).toString() ); + + // call pattern pat_try_3(/16, ...) + assertEquals( "1440/1/91/f+4 / 64", messages.get(i++).toString() ); + assertEquals( "1560/1/91/f+4 / 64", messages.get(i++).toString() ); + assertEquals( "1680/1/91/a+3 / 64", messages.get(i++).toString() ); + + // call pattern pat_try_4(/16, ...) + assertEquals( "1800/1/91/f+4 / 64", messages.get(i++).toString() ); + assertEquals( "1920/1/91/f+4 / 64", messages.get(i++).toString() ); + assertEquals( "2040/1/91/a+3 / 64", messages.get(i++).toString() ); + } + + parse(getWorkingFile("condition-2")); + assertEquals( "2:ELSE,4:ELSE,\n1:IF,3:IF,\n2:ELSE,4:ELSE,\n", getLyrics() ); } /** @@ -999,6 +1048,21 @@ void testParseFilesFailing() { assertEquals( "CHORD test c,d,c", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_CHORD_CONTAINS_ALREADY)) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("chord-separator-double")) ); + assertEquals( 5, e.getLineNumber() ); + assertEquals( "CHORD crd=c,d,,e", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_CHORD_REDUNDANT_SEP)) ); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("chord-separator-leading")) ); + assertEquals( 5, e.getLineNumber() ); + assertEquals( "CHORD crd = ,c,d,e", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_CHORD_REDUNDANT_SEP)) ); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("chord-separator-trailing")) ); + assertEquals( 5, e.getLineNumber() ); + assertEquals( "CHORD crd = c,d,e,", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_CHORD_REDUNDANT_SEP)) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("call-with-invalid-option")) ); assertEquals( 6, e.getLineNumber() ); assertEquals( "CALL test v=50", e.getLineContent() ); @@ -1144,6 +1208,11 @@ void testParseFilesFailing() { assertEquals( "CHORD test c d 128", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_NOTE_TOO_BIG) + "128") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("chord-assigner-double")) ); + assertEquals( 5, e.getLineNumber() ); + assertEquals( "CHORD crd==c,d,e", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_UNKNOWN_NOTE) + "=c") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("channel-cmd-missing-param")) ); assertEquals( 3, e.getLineNumber() ); assertEquals( "0 c", e.getLineContent() ); @@ -1180,7 +1249,7 @@ void testParseFilesFailing() { assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_PARAM_OUTSIDE_FUNCTION) + "$[1]") ); e = assertThrows( ParseException.class, () -> parse(getFailingFile("param-i-outside-function-nested")) ); - assertEquals( 5, e.getLineNumber() ); + assertEquals( 4, e.getLineNumber() ); assertEquals( "$[1]", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_PARAM_OUTSIDE_FUNCTION) + "$[1]") ); @@ -1190,7 +1259,7 @@ void testParseFilesFailing() { assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_PARAM_OUTSIDE_FUNCTION) + "${x}") ); e = assertThrows( ParseException.class, () -> parse(getFailingFile("param-n-outside-function-nested")) ); - assertEquals( 5, e.getLineNumber() ); + assertEquals( 4, e.getLineNumber() ); assertEquals( "${x}", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_PARAM_OUTSIDE_FUNCTION) + "${x}") ); @@ -1330,6 +1399,11 @@ void testParseFilesFailing() { assertEquals( "{ if $x in 1;2;;5", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_COND_EMPTY_ELEM_IN_IN_LIST)) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("opt-assigner-double")) ); + assertEquals( 5, e.getLineNumber() ); + assertEquals( "0 c /4 v==100", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_NOT_AN_INTEGER) + "=100") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("opt-without-value-velocity")) ); assertEquals( 3, e.getLineNumber() ); assertEquals( "0 c /4 v", e.getLineContent() ); @@ -1480,6 +1554,11 @@ void testParseFilesFailing() { assertEquals( "-1 c /4", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_UNKNOWN_CMD) + "-1") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("channel-negative-2")) ); + assertEquals( 3, e.getLineNumber() ); + assertEquals( "-1 c /4", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_UNKNOWN_CMD) + "-1") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("channel-too-high")) ); assertEquals( 3, e.getLineNumber() ); assertEquals( "16 c /4", e.getLineContent() ); @@ -1515,6 +1594,11 @@ void testParseFilesFailing() { assertEquals( "CALL test(a=b=c)", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_CALL_PARAM_MORE_ASSIGNERS)) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("call-param-named-more-assigners-2")) ); + assertEquals( 5, e.getLineNumber() ); + assertEquals( "CALL f(vel==102)", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_CALL_PARAM_MORE_ASSIGNERS)) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("include-directory")) ); assertEquals( 1, e.getLineNumber() ); assertEquals( "INCLUDE inc/", e.getLineContent() ); @@ -1525,6 +1609,11 @@ void testParseFilesFailing() { assertEquals( "INCLUDE", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_FILE_NUM_OF_ARGS)) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("const-assigner-double")) ); + assertEquals( 7, e.getLineNumber() ); + assertEquals( "0 c /4 v==103", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_NOT_AN_INTEGER) + "=103") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("include-too-many-args")) ); assertEquals( 1, e.getLineNumber() ); assertEquals( "INCLUDE inc/instruments.midica inc/instruments.midica", e.getLineContent() ); @@ -1564,6 +1653,16 @@ void testParseFilesFailing() { assertEquals( "DEFINE CHORD crd crd", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_DEFINE_NUM_OF_ARGS)) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("define-assigner-double")) ); + assertEquals( 5, e.getLineNumber() ); + assertEquals( "DEFINE CHORD == CRD", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_DEFINE_NUM_OF_ARGS)) ); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("define-assigner-double-2")) ); + assertEquals( 8, e.getLineNumber() ); + assertEquals( "CRD crd c,d,e", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_UNKNOWN_CMD) + "CRD") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("const-without-args")) ); assertEquals( 3, e.getLineNumber() ); assertEquals( "CONST", e.getLineContent() ); @@ -1594,6 +1693,11 @@ void testParseFilesFailing() { assertEquals( "VAR", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_VAR_NUM_OF_ARGS)) ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("var-assigner-double")) ); + assertEquals( 7, e.getLineNumber() ); + assertEquals( "0 c /4 v=$x", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_NOT_AN_INTEGER) + "=100") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("var-not-enough-args")) ); assertEquals( 3, e.getLineNumber() ); assertEquals( "VAR $x", e.getLineContent() ); @@ -1714,6 +1818,26 @@ void testParseFilesFailing() { assertEquals( "0 /1 s=1", e.getLineContent() ); assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_PATTERN_INVALID_INNER_OPT) + "shift") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("pattern-with-if")) ); + assertEquals( 3, e.getLineNumber() ); + assertEquals( "0 /1 if y == x", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_PATTERN_INVALID_INNER_OPT) + "if") ); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("pattern-with-if-2")) ); + assertEquals( 3, e.getLineNumber() ); + assertEquals( "0 pat_b(x, y, z) if y == x", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_PATTERN_INVALID_INNER_OPT) + "if") ); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("pattern-undefined")) ); + assertEquals( 6, e.getLineNumber() ); + assertEquals( "0 not_existing_pattern( x, y, z ) v = 120 , d = 80%", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_PATTERN_UNDEFINED) + "not_existing_pattern") ); + + e = assertThrows( ParseException.class, () -> parse(getFailingFile("pattern-undefined-2")) ); + assertEquals( 3, e.getLineNumber() ); + assertEquals( "0 /42", e.getLineContent() ); + assertTrue( e.getMessage().startsWith(Dict.get(Dict.ERROR_NOTE_LENGTH_INVALID) + "/42") ); + e = assertThrows( ParseException.class, () -> parse(getFailingFile("pattern-call-index-wrong")) ); assertEquals( 5, e.getLineNumber() ); assertEquals( "1.2 /4", e.getLineContent() ); diff --git a/test/org/midica/testfiles/failing/call-param-named-more-assigners-2.midica b/test/org/midica/testfiles/failing/call-param-named-more-assigners-2.midica new file mode 100755 index 0000000..be54d7f --- /dev/null +++ b/test/org/midica/testfiles/failing/call-param-named-more-assigners-2.midica @@ -0,0 +1,9 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +CALL f(vel==102) + +FUNCTION f + 0 c /4 v=${vel} +END diff --git a/test/org/midica/testfiles/failing/channel-negative-2.midica b/test/org/midica/testfiles/failing/channel-negative-2.midica new file mode 100644 index 0000000..e7a28af --- /dev/null +++ b/test/org/midica/testfiles/failing/channel-negative-2.midica @@ -0,0 +1,3 @@ +INCLUDE inc/instruments.midica + +-1 c /4 diff --git a/test/org/midica/testfiles/failing/chord-assigner-double.midica b/test/org/midica/testfiles/failing/chord-assigner-double.midica new file mode 100755 index 0000000..157406b --- /dev/null +++ b/test/org/midica/testfiles/failing/chord-assigner-double.midica @@ -0,0 +1,7 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +CHORD crd==c,d,e + +0 crd /4 v=101 diff --git a/test/org/midica/testfiles/failing/chord-separator-double.midica b/test/org/midica/testfiles/failing/chord-separator-double.midica new file mode 100755 index 0000000..c43a63c --- /dev/null +++ b/test/org/midica/testfiles/failing/chord-separator-double.midica @@ -0,0 +1,7 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +CHORD crd=c,d,,e + +0 crd /4 v=101 diff --git a/test/org/midica/testfiles/failing/chord-separator-leading.midica b/test/org/midica/testfiles/failing/chord-separator-leading.midica new file mode 100755 index 0000000..12edf8c --- /dev/null +++ b/test/org/midica/testfiles/failing/chord-separator-leading.midica @@ -0,0 +1,7 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +CHORD crd = ,c,d,e + +0 crd /4 v=101 diff --git a/test/org/midica/testfiles/failing/chord-separator-trailing.midica b/test/org/midica/testfiles/failing/chord-separator-trailing.midica new file mode 100755 index 0000000..e19622c --- /dev/null +++ b/test/org/midica/testfiles/failing/chord-separator-trailing.midica @@ -0,0 +1,7 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +CHORD crd = c,d,e, + +0 crd /4 v=101 diff --git a/test/org/midica/testfiles/failing/const-assigner-double.midica b/test/org/midica/testfiles/failing/const-assigner-double.midica new file mode 100755 index 0000000..865e68b --- /dev/null +++ b/test/org/midica/testfiles/failing/const-assigner-double.midica @@ -0,0 +1,7 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +CONST $x==103 + +0 c /4 v=$x diff --git a/test/org/midica/testfiles/failing/define-assigner-double-2.midica b/test/org/midica/testfiles/failing/define-assigner-double-2.midica new file mode 100755 index 0000000..c10c15a --- /dev/null +++ b/test/org/midica/testfiles/failing/define-assigner-double-2.midica @@ -0,0 +1,10 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +DEFINE CHORD==CRD + + +CRD crd c,d,e + +0 crd /4 v=99 diff --git a/test/org/midica/testfiles/failing/define-assigner-double.midica b/test/org/midica/testfiles/failing/define-assigner-double.midica new file mode 100755 index 0000000..a0f90e0 --- /dev/null +++ b/test/org/midica/testfiles/failing/define-assigner-double.midica @@ -0,0 +1,10 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +DEFINE CHORD == CRD + + +CRD crd c,d,e + +0 crd /4 v=99 diff --git a/test/org/midica/testfiles/failing/opt-assigner-double.midica b/test/org/midica/testfiles/failing/opt-assigner-double.midica new file mode 100755 index 0000000..2459523 --- /dev/null +++ b/test/org/midica/testfiles/failing/opt-assigner-double.midica @@ -0,0 +1,5 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +0 c /4 v==100 diff --git a/test/org/midica/testfiles/failing/pattern-undefined-2.midica b/test/org/midica/testfiles/failing/pattern-undefined-2.midica new file mode 100644 index 0000000..72a203c --- /dev/null +++ b/test/org/midica/testfiles/failing/pattern-undefined-2.midica @@ -0,0 +1,7 @@ +INCLUDE inc/instruments.midica + +0 c,d,e pat_a( y ) + +PATTERN pat_a + 0 /42 +END diff --git a/test/org/midica/testfiles/failing/pattern-undefined.midica b/test/org/midica/testfiles/failing/pattern-undefined.midica new file mode 100644 index 0000000..0e6f46a --- /dev/null +++ b/test/org/midica/testfiles/failing/pattern-undefined.midica @@ -0,0 +1,7 @@ +INCLUDE inc/instruments.midica + +0 c,d,e pat_a( y ) + +PATTERN pat_a + 0 not_existing_pattern( x, y, z ) v = 120 , d = 80% +END diff --git a/test/org/midica/testfiles/failing/pattern-with-if-2.midica b/test/org/midica/testfiles/failing/pattern-with-if-2.midica new file mode 100644 index 0000000..f1ab0e9 --- /dev/null +++ b/test/org/midica/testfiles/failing/pattern-with-if-2.midica @@ -0,0 +1,11 @@ +INCLUDE inc/instruments.midica + +0 c,d,e pat_a( y ) + +PATTERN pat_a + 0 pat_b( x, y, z ) if $[0] == x +END + +PATTERN pat_b + 0 /1 +END diff --git a/test/org/midica/testfiles/failing/pattern-with-if.midica b/test/org/midica/testfiles/failing/pattern-with-if.midica new file mode 100644 index 0000000..9a9df55 --- /dev/null +++ b/test/org/midica/testfiles/failing/pattern-with-if.midica @@ -0,0 +1,7 @@ +INCLUDE inc/instruments.midica + +0 c,d,e pat_a( y ) + +PATTERN pat_a + 0 /1 if $[0] == x +END diff --git a/test/org/midica/testfiles/failing/var-assigner-double.midica b/test/org/midica/testfiles/failing/var-assigner-double.midica new file mode 100755 index 0000000..7fd42a0 --- /dev/null +++ b/test/org/midica/testfiles/failing/var-assigner-double.midica @@ -0,0 +1,7 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +VAR $x==100 + +0 c /4 v=$x diff --git a/test/org/midica/testfiles/working/condition-2.midica b/test/org/midica/testfiles/working/condition-2.midica new file mode 100644 index 0000000..b126158 --- /dev/null +++ b/test/org/midica/testfiles/working/condition-2.midica @@ -0,0 +1,22 @@ +INSTRUMENTS + 0 PERCUSSIVE_ORGAN PERCUSSIVE_ORGAN +END + +CALL x() +CALL x(IF) +CALL x(UNKNOWN) + +FUNCTION x + CALL y( 1:IF\c ) if $[0] == IF + CALL y( 2:ELSE\c ) if $[0] != IF + CALL y( 3:IF\c ) if=$[0]==IF + CALL y( 4:ELSE\c ) if=$[0]!=IF + 0 - /4 l=\r +END + +FUNCTION y + 0 f /4 l=$[0] +END + + +* tempo 95 diff --git a/test/org/midica/testfiles/working/condition.midica b/test/org/midica/testfiles/working/condition.midica new file mode 100644 index 0000000..e1a0b72 --- /dev/null +++ b/test/org/midica/testfiles/working/condition.midica @@ -0,0 +1,131 @@ +INCLUDE inc/instruments.midica + +CHORD crd a+3 f+4 + +// call function with base-level if/else blocks +CALL try_1(/32) +CALL try_1(/32, ELSE) +CALL try_1(/32, IF) + +// call function with nested if/else blocks +CALL try_2(/32) +CALL try_2(/32, ELSE) +CALL try_2(/32, IF) + +// call pattern with base-level if/else blocks +1 crd pat_try_1(/16) +1 crd pat_try_1(/16, ELSE) +1 crd pat_try_1(/16, IF) + +// call pattern with nested if/else blocks +1 crd pat_try_2(/16) +1 crd pat_try_2(/16, ELSE) +1 crd pat_try_2(/16, IF) + +// same as above but with "if=$[1]==IF" instead of "if $[1] == IF" +CALL try_3(/32) +CALL try_3(/32, ELSE) +CALL try_3(/32, IF) + +CALL try_4(/32) +CALL try_4(/32, ELSE) +CALL try_4(/32, IF) + +1 crd pat_try_3(/16) +1 crd pat_try_3(/16, ELSE) +1 crd pat_try_3(/16, IF) + +1 crd pat_try_4(/16) +1 crd pat_try_4(/16, ELSE) +1 crd pat_try_4(/16, IF) + + +FUNCTION try_1 + { if $[1] == IF + 1 a+2 $[0] l=IF_ + } + { else + 1 f+2 $[0] l=ELSE_ + } +END + +FUNCTION try_2 + { + { if $[1] == IF + 1 a+3 $[0] l=IF_ + } + { else + 1 f+3 $[0] l=ELSE_ + } + } +END + +PATTERN pat_try_1 + + { if $[1] == IF + 0 $[0] m + } m + + { else + 1 $[0] m + } m + - $[0] +END + +PATTERN pat_try_2 + { + { if $[1] == IF + 0 $[0] m + } m + + { else + 1 $[0] m + } m + - $[0] + } +END + +FUNCTION try_3 + { if=$[1]==IF + 1 a+2 $[0] l=IF_ + } + { else + 1 f+2 $[0] l=ELSE_ + } +END + +FUNCTION try_4 + { + { if=$[1]==IF + 1 a+3 $[0] l=IF_ + } + { else + 1 f+3 $[0] l=ELSE_ + } + } +END + +PATTERN pat_try_3 + + { if=$[1]==IF + 0 $[0] m + } m + + { else + 1 $[0] m + } m + - $[0] +END + +PATTERN pat_try_4 + { + { if=$[1]==IF + 0 $[0] m + } m + + { else + 1 $[0] m + } m + - $[0] + } +END diff --git a/test/org/midica/testfiles/working/inc/definitions.midica b/test/org/midica/testfiles/working/inc/definitions.midica index 02cd8a8..d944cca 100644 --- a/test/org/midica/testfiles/working/inc/definitions.midica +++ b/test/org/midica/testfiles/working/inc/definitions.midica @@ -1,5 +1,5 @@ -DEFINE DEFINE def -def INCLUDE incl_f +DEFINE DEFINE=def +def INCLUDE = incl_f def INSTRUMENTS instruments[ def END ] def FUNCTION procedure[ diff --git a/test/org/midica/testfiles/working/var.midica b/test/org/midica/testfiles/working/var.midica index 06e0879..268185e 100644 --- a/test/org/midica/testfiles/working/var.midica +++ b/test/org/midica/testfiles/working/var.midica @@ -1,10 +1,10 @@ INCLUDE inc/instruments.midica CONST $cent= cent -VAR $fa = 1 // 1 -VAR $fb = 2 // 2 -VAR $fc = 3 // 0 -VAR $fd = $fb$fc // 23 +VAR $fa = 1 // 1 + VAR $fb = 2 // 2 +VAR $fc = 3 // 0 +VAR $fd = $fb$fc // 23 VAR $forte = $fa$fd // 123 VAR $piano= 30 // 30 VAR $mezzoforte =80 // 80