// copy constructor
 public MmlResolvedEvent(MmlResolvedEvent other, int tick)
 {
     Operation = other.Operation;
     Arguments = other.Arguments;
     Tick = tick;
 }
        // extraTailArgs is a list of arguments that are passed to the context macro call e.g.
        //   #macro CHORD_A c0e0g
        //   1   CHORD_A4 CHORD_A8 CHORD_A8
        // In this case, 4, 8 and 8 are NOT passed to CHORD_A unless these extraTailArgs are passed
        // and causes unexpected outputs.
        // We can still define macros to take full arguments to the defined sequence of operators
        // (in this case, to 'g'), but that is super annoying and basically impossible unless
        // you know the macro definition details (which almost no one would know).
        void ProcessOperations(MmlResolvedTrack track, MmlResolveContext rctx, List<MmlOperationUse> list, int start, int count, IEnumerable<MmlValueExpr> extraTailArgs)
        {
            int storeIndex = -1;
            List<MmlResolvedEvent> storeCurrentOutput = null, storeDummy = new List<MmlResolvedEvent> ();
            StoredOperations currentStoredOperations = null;
            bool is_string_format = false;

            for (int listIndex = start; listIndex < start + count; listIndex++) {
                var oper = list [listIndex];
                var extraTailArgsIfApplied = listIndex == start + count - 1 ? extraTailArgs : null;

                switch (oper.Name) {
                case "__PRINT": {
                    oper.Arguments [0].Resolve (rctx, MmlDataType.String);
                    DebugPrint.WriteLine (oper.Arguments [0].StringValue);
                    break;
                    }
                case "__LET": {
                    oper.Arguments [0].Resolve (rctx, MmlDataType.String);
                    string name = oper.Arguments [0].StringValue;
                    var variable = (MmlSemanticVariable) source.Variables [name];
                    if (variable == null)
                        throw new MmlException (String.Format ("Target variable not found: {0}", name), location);
                    oper.Arguments [1].Resolve (rctx, variable.Type);
                    rctx.Values [variable] = oper.Arguments [1].ResolvedValue;
                    if (name == "__timeline_position")
                        rctx.TimelinePosition = oper.Arguments [1].IntValue;
                    break;
                    }
                case "__STORE": {
                    oper.Arguments [0].Resolve (rctx, MmlDataType.String);
                    oper.ValidateArguments (rctx, oper.Arguments.Count);
                    string name = oper.Arguments [0].StringValue;
                    var variable = (MmlSemanticVariable) source.Variables [name];
                    if (variable == null)
                        throw new MmlException (String.Format ("Target variable not found: {0}", name), location);
                    if (variable.Type != MmlDataType.Buffer)
                        throw new MmlException (String.Format ("Target variable is not a buffer: {0}", name), location);
                    var sb = (StringBuilder) rctx.EnsureDefaultResolvedVariable (variable);
                    for (int i = 1; i < oper.Arguments.Count; i++)
                        sb.Append (oper.Arguments [i].StringValue);
                    break;
                    }
                case "__FORMAT":
                    is_string_format = true;
                    goto case "__STORE_FORMAT";
                case "__STORE_FORMAT": {
                    oper.Arguments [0].Resolve (rctx, MmlDataType.String);
                    oper.Arguments [1].Resolve (rctx, MmlDataType.String);
                    oper.ValidateArguments (rctx, oper.Arguments.Count);
                    string name = oper.Arguments [0].StringValue;
                    string format = oper.Arguments [1].StringValue;
                    var variable = (MmlSemanticVariable) source.Variables [name];
                    if (variable == null)
                        throw new MmlException (String.Format ("Target variable not found: {0}", name), location);
                    if (is_string_format) {
                        if (variable.Type != MmlDataType.String)
                            throw new MmlException (String.Format ("Target variable is not a string: {0}", name), location);
                    } else {
                        if (variable.Type != MmlDataType.Buffer)
                            throw new MmlException (String.Format ("Target variable is not a buffer: {0}", name), location);
                    }
                    try {
                        string v = string.Format (format, (object []) (from x in oper.Arguments.Skip (2) select (object) x.StringValue).ToArray ());
                        if (is_string_format)
                            rctx.Values [variable] = v;
                        else
                            ((StringBuilder) rctx.EnsureDefaultResolvedVariable (variable)).Append (v);
                    } catch (FormatException ex) {
                        throw new MmlException (String.Format ("Format error while applying '{0}' to '{1}': {2}", format, name, ex.Message), location);
                    }
                    break;
                    }
                case "__APPLY":
                    var oa = oper.Arguments [0];
                    oa.Resolve (rctx, MmlDataType.String);
                    string apparg = oa.StringValue;

                    // add macro argument definitions
                    var tmpop = new MmlOperationUse (apparg, oper.Location);
                    for (int x = 1; x < oper.Arguments.Count; x++)
                        tmpop.Arguments.Add (oper.Arguments [x]);
                    ProcessMacroCall (track, rctx, tmpop, extraTailArgsIfApplied);

                    break;
                case "__MIDI":
                    oper.ValidateArguments (rctx, oper.Arguments.Count);
                    var mop = new MmlResolvedEvent ("MIDI", rctx.TimelinePosition);
                    foreach (var arg in oper.Arguments)
                        mop.Arguments.Add (arg.ByteValue);
                    current_output.Add (mop);
                    if (recordNextAsChord)
                        chord.Add (mop);
                    recordNextAsChord = false;
                    break;
                case "__SYNC_NOFF_WITH_NEXT":
                    recordNextAsChord = true;
                    break;
                case "__ON_MIDI_NOTE_OFF":
                    // handle zero-length note
                    oper.ValidateArguments (rctx, 3, MmlDataType.Number, MmlDataType.Number, MmlDataType.Number);
                    if (oper.Arguments [0].IntValue == 0)
                        // record next note as part of chord
                        recordNextAsChord = true;
                    else {
                        foreach (MmlResolvedEvent cop in chord)
                            cop.Tick += oper.Arguments [0].IntValue;
                        chord.Clear ();
                    }
                    break;
                case "__MIDI_META":
                    oper.ValidateArguments (rctx, oper.Arguments.Count);
                    var mmop = new MmlResolvedEvent ("META", rctx.TimelinePosition);
                    mmop.Arguments.Add (0xFF);
                    foreach (var arg in oper.Arguments)
                        mmop.Arguments.AddRange (arg.ByteArrayValue);
                    current_output.Add (mmop);
                    break;
                case "__SAVE_OPER_BEGIN":
                    oper.ValidateArguments (rctx, 0);
                    if (storeIndex >= 0)
                        throw new MmlException ("__SAVE_OPER_BEGIN works only within a simple list", oper.Location);
                    storeIndex = listIndex + 1;
                    storeCurrentOutput = current_output;
                    current_output = storeDummy;
                    currentStoredOperations = new StoredOperations ();
                    currentStoredOperations.Values = new Dictionary<MmlSemanticVariable, object> (rctx.Values);
                    currentStoredOperations.MacroArguments = (Hashtable) rctx.MacroArguments.Clone ();
                    break;
                case "__SAVE_OPER_END": {
                    oper.ValidateArguments (rctx, 1, MmlDataType.Number);
                    int bufIdx = oper.Arguments [0].IntValue;
                    stored_operations [bufIdx] = currentStoredOperations;
                    currentStoredOperations.Operations =
                        new List<MmlOperationUse> (list.Skip (storeIndex).Take (listIndex - storeIndex - 1));
                    current_output = storeCurrentOutput;
                    storeDummy.Clear ();
                    storeIndex = -1;
                    currentStoredOperations = null;
                    // FIXME: might be better to restore variables
                    break;
                    }
                case "__RESTORE_OPER": {
                    oper.ValidateArguments (rctx, 1, MmlDataType.Number);
                    int bufIdx = oper.Arguments [0].IntValue;
                    var ss = stored_operations [bufIdx];
                    var valuesBak = rctx.Values;
                    var macroArgsBak = rctx.MacroArguments;
                    rctx.Values = ss.Values;
                    rctx.MacroArguments = ss.MacroArguments;
                    // adjust timeline_position (no need to update rctx.TimelinePosition here).
                    rctx.Values [(MmlSemanticVariable) source.Variables ["__timeline_position"]] = rctx.TimelinePosition;
                    ProcessOperations (track, rctx, ss.Operations, 0, ss.Operations.Count, extraTailArgsIfApplied);
                    rctx.Values = valuesBak;
                    rctx.MacroArguments = macroArgsBak;
                    break;
                    }
                case "__LOOP_BEGIN":
            #if !UNHACK_LOOP
                case "[":
            #endif
                    oper.ValidateArguments (rctx, 0);
                    var loop = new Loop (rctx) { BeginAt= new LoopLocation (listIndex, current_output.Count, rctx.TimelinePosition) };
                    rctx.Values = new Dictionary<MmlSemanticVariable,object> (loop.SavedValues.Count);
                    foreach (var p in loop.SavedValues)
                        rctx.Values.Add (p.Key, p.Value);
                    rctx.Loops.Push (loop);
                    current_output = loop.Events;
                    break;
                case "__LOOP_BREAK":
            #if !UNHACK_LOOP
                case "/":
                case ":":
            #endif
                    oper.ValidateArguments (rctx, oper.Arguments.Count);
                    loop = rctx.CurrentLoop;
                    if (loop == null)
                        throw new MmlException ("Loop break operation must be inside a pair of loop start and end", location);
                    if (loop.FirstBreakAt == null)
                        loop.FirstBreakAt = new LoopLocation (listIndex, current_output.Count, rctx.TimelinePosition);
                    foreach (var cl in loop.CurrentBreaks)
                        loop.EndLocations [cl] = new LoopLocation (listIndex, current_output.Count, rctx.TimelinePosition);
                    loop.CurrentBreaks.Clear ();

                    // FIXME: actually this logic does not make sense as now it is defined with fixed-length arguments...
                    if (oper.Arguments.Count == 0) { // default loop break
                        if (loop.Breaks.ContainsKey (-1) && loop.Breaks.Values.All (b => b.Source != listIndex))
                            throw new MmlException ("Default loop break is already defined in current loop", location);
                        loop.Breaks.Add (-1, new LoopLocation (listIndex, current_output.Count, rctx.TimelinePosition));
                        loop.CurrentBreaks.Add (-1);
                    } else {
                        for (int x = 0; x < oper.Arguments.Count; x++) {
                            var numexpr = oper.Arguments [x];
                            var num = numexpr.IntValue - 1; // "1st. loop" for musicians == 0th iteration in code.
                            if (x > 0 && num < 0)
                                break; // after the last argument.
                            loop.CurrentBreaks.Add (num);
                            if (loop.Breaks.ContainsKey (num) && loop.Breaks.Values.All (b => b.Source != listIndex))
                                throw new MmlException (String.Format ("Loop section {0} was already defined in current loop", num), location);
                            // specified loop count is for human users. Here the number is for program, hence -1.
                            loop.Breaks.Add (num, new LoopLocation (listIndex, current_output.Count, rctx.TimelinePosition));
                        }
                    }
                    break;
                case "__LOOP_END":
            #if !UNHACK_LOOP
                case "]":
            #endif
                    oper.ValidateArguments (rctx, 0, MmlDataType.Number);
                    loop = rctx.CurrentLoop;
                    if (loop == null)
                        throw new MmlException ("Loop has not started", location);
                    foreach (var cl in loop.CurrentBreaks)
                        loop.EndLocations [cl] = new LoopLocation (listIndex, current_output.Count, rctx.TimelinePosition);
                    int loopCount;
                    switch (oper.Arguments.Count) {
                    case 0:
                        loopCount = 2;
                        break;
                    case 1:
                        loopCount = oper.Arguments [0].IntValue;
                        break;
                    default:
                        throw new MmlException ("Arguments at loop end exceeded", location);
                    }

                    rctx.Loops.Pop ();
                    var outside = rctx.CurrentLoop;
                    current_output = outside != null ? outside.Events : track.Events;

                    // now expand loop.
                    // - verify that every loop break does not exceed the loop count
                    // - add sequence before the first break
                    // - add sequence for each break. If no explicit break, then use default.
                    foreach (var p in loop.Breaks) {
                        if (p.Key > loopCount)
                            throw new MmlException ("Loop break specified beyond the loop count", list [p.Value.Source].Location);
                    }

                    rctx.Values = loop.SavedValues;

                    int baseTicks;
                    int baseOutputEnd;
                    int tickOffset = 0;
                    if (loop.FirstBreakAt == null) { // w/o break
                        baseTicks = rctx.TimelinePosition - loop.BeginAt.Tick;
                        baseOutputEnd = loop.Events.Count;
                        rctx.TimelinePosition = loop.BeginAt.Tick;

                        // This range of commands actually adds extra argument definitions for loop operation, but it won't hurt.
                        for (int l = 0; l < loopCount; l++)
                            ProcessOperations (track, rctx, list, loop.BeginAt.Source + 1, listIndex  - loop.BeginAt.Source - 1, extraTailArgsIfApplied);
                    } else { // w/ breaks
                        baseTicks = loop.FirstBreakAt.Tick - loop.BeginAt.Tick;
                        baseOutputEnd = loop.FirstBreakAt.Output;

                        rctx.TimelinePosition = loop.BeginAt.Tick;

                        for (int l = 0; l < loopCount; l++) {
                            ProcessOperations (track, rctx, list, loop.BeginAt.Source + 1, loop.FirstBreakAt.Source  - loop.BeginAt.Source - 1, extraTailArgsIfApplied);
                            tickOffset += baseTicks;
                            LoopLocation lb = null;
                            if (!loop.Breaks.TryGetValue (l, out lb)) {
                                if (l + 1 == loopCount)
                                    break; // this is to break the loop at the last iteration.
                                if (!loop.Breaks.TryGetValue (-1, out lb))
                                    throw new MmlException (String.Format ("No corresponding loop break specification for iteration at {0} from the innermost loop", l + 1), list [loop.BeginAt.Source].Location);
                            }
                            if (lb == null) // final break
                                break;
                            LoopLocation elb;
                            if (!loop.EndLocations.TryGetValue (l, out elb))
                                elb = loop.EndLocations [-1];
                            int breakOffset = lb.Tick - loop.BeginAt.Tick + baseTicks;
                            ProcessOperations (track, rctx, list, lb.Source + 1, elb.Source - lb.Source - 1, extraTailArgsIfApplied);
                        }
                    }
                    break;
                default:
                    ProcessMacroCall (track, rctx, oper, extraTailArgsIfApplied);
                    break;
                }
            }
        }