/// <summary> /// /// </summary> /// <param name="cs"></param> public ActorScriptSection(CustomSection cs) { var stream = new MemoryStream((byte[])cs.Content); var reader = new Reader(stream); var previousSection = ActorScriptSubsection.Labels; var preSectionOffset = reader.Offset; while (reader.TryReadVarUInt7(out var id)) //At points where TryRead is used, the stream can safely end. { if (id != 0 && (ActorScriptSubsection)id < previousSection) { throw new ModuleLoadException($"Sections out of order; section {(ActorScriptSubsection)id} encounterd after {previousSection}.", preSectionOffset); } var payloadLength = reader.ReadVarUInt32(); switch ((ActorScriptSubsection)id) { case ActorScriptSubsection.Labels: { var count = reader.ReadVarUInt32(); Labels = new LabelMap((int)count); for (int i = 0; i < count; i++) { var index = reader.ReadVarUInt32(); var nameLength = reader.ReadVarUInt32(); var name = reader.ReadString(nameLength); Labels.Add(index, name); } } break; } previousSection = (ActorScriptSubsection)id; } }
public void CanLogBytes() { _labels.Add(new Label("Start", 0x1000)); _labels.Add(new Label("MySubroutine", 0x1012)); ushort address = 0x1000; ushort size = 50; var bytes = _addressMap.ReadBlock(address, (ushort)(address + size - 1)); _logFormatter.LogBytes(address, bytes); var output = _logFormatter.ToString(); Console.WriteLine(""); // Clear the hanging line Debug.WriteLine(output); Console.WriteLine(output); Assert.IsTrue(output.Contains("[1012] MySubroutine:")); }
/// <summary> /// Adjusts the label map so that only local variables start with an underscore ('_'). /// This is necessary for assemblers like 64tass that use a leading underscore to /// indicate that a label should be local. /// /// This may be called even if label localization is disabled. In that case we just /// create an empty label map and populate as needed. /// /// Only call this if underscores are used to indicate local labels. /// </summary> public void MaskLeadingUnderscores() { bool allGlobal = false; if (LabelMap == null) { allGlobal = true; LabelMap = new Dictionary <string, string>(); } // Throw out the original local label generation. LabelMap.Clear(); // Use this to test for uniqueness. We add all labels here as we go, not just the // ones being remapped. For each label we either add the original or the localized // form. SortedList <string, string> allLabels = new SortedList <string, string>(); for (int i = 0; i < mProject.FileDataLength; i++) { Symbol sym = mProject.GetAnattrib(i).Symbol; if (sym == null) { // No label at this offset. continue; } string newLabel; if (allGlobal || mGlobalFlags[i]) { // Global symbol. Don't let it start with '_'. if (sym.Label.StartsWith("_")) { // There's an underscore here that was added by the user. Stick some // other character in front. newLabel = "X" + sym.Label; } else { // No change needed. newLabel = sym.Label; } } else { // Local symbol. if (sym.Label.StartsWith("_")) { // The original starts with one or more underscores. Adding another // will create a "__" label, which is reserved in 64tass. newLabel = "_X" + sym.Label; } else { newLabel = "_" + sym.Label; } } // Make sure it's unique. string uniqueLabel = newLabel; int uval = 1; while (allLabels.ContainsKey(uniqueLabel)) { uniqueLabel = newLabel + uval.ToString(); } allLabels.Add(uniqueLabel, uniqueLabel); // If it's different, add it to the label map. if (sym.Label != uniqueLabel) { LabelMap.Add(sym.Label, uniqueLabel); } } Debug.WriteLine("UMAP: allcount=" + allLabels.Count + " mapcount=" + LabelMap.Count); }
/// <summary> /// Remaps labels that match opcode names. Updated names will be added to LabelMap. /// This should be run after localization and underscore concealment have finished. /// </summary> /// <remarks> /// Most assemblers don't like it if you create a label with the same name as an /// opcode, e.g. "jmp LSR" doesn't work. We can use the label map to work around /// the issue. /// /// Most assemblers regard mnemonics as case-insensitive, even if labels are /// case-sensitive, so we want to remap both "lsr" and "LSR". /// /// This doesn't really have anything to do with label localization other than that /// we're updating the label remap table. /// </remarks> public void FixOpcodeLabels() { if (LabelMap == null) { LabelMap = new Dictionary <string, string>(); } // Create a searchable list of opcode names using the current CPU definition. // (All tested assemblers that failed on opcode names only did so for names // that were part of the current definition, e.g. "TSB" was accepted as a label // when the CPU was set to 6502.) Dictionary <string, Asm65.OpDef> opnames = new Dictionary <string, Asm65.OpDef>(); Asm65.CpuDef cpuDef = mProject.CpuDef; for (int i = 0; i < 256; i++) { Asm65.OpDef op = cpuDef.GetOpDef(i); // There may be multiple entries with the same name (e.g. "NOP"). That's fine. opnames[op.Mnemonic.ToUpperInvariant()] = op; } // Create a list of all labels, for uniqueness testing. If a label has been // remapped, we add the remapped entry. // (All tested assemblers that failed on opcode names only did so for names // in their non-localized form. While "LSR" failed, "@LSR", "_LSR", ".LSR", etc. // were accepted. So if it was remapped by the localizer, we don't need to // worry about it.) SortedList <string, string> allLabels = new SortedList <string, string>(); for (int i = 0; i < mProject.FileDataLength; i++) { Symbol sym = mProject.GetAnattrib(i).Symbol; if (sym == null) { continue; } LabelMap.TryGetValue(sym.Label, out string mapLabel); if (mapLabel != null) { allLabels.Add(mapLabel, mapLabel); } else { allLabels.Add(sym.Label, sym.Label); } } // Now run through the list of labels, looking for any that match opcode // mnemonics. for (int i = 0; i < mProject.FileDataLength; i++) { Symbol sym = mProject.GetAnattrib(i).Symbol; if (sym == null) { // No label at this offset. continue; } string cmpLabel = sym.Label; if (LabelMap.TryGetValue(sym.Label, out string mapLabel)) { cmpLabel = mapLabel; } if (opnames.ContainsKey(cmpLabel.ToUpperInvariant())) { //Debug.WriteLine("Remapping label (op mnemonic): " + sym.Label); int uval = 0; string uniqueLabel; do { uval++; uniqueLabel = cmpLabel + "_" + uval.ToString(); } while (allLabels.ContainsKey(uniqueLabel)); allLabels.Add(uniqueLabel, uniqueLabel); LabelMap.Add(sym.Label, uniqueLabel); } } if (LabelMap.Count == 0) { // didn't do anything, lose the table LabelMap = null; } }
/// <summary> /// スクリプトコードを構文解析して,実行可能 /// </summary> /// <returns>The parse.</returns> /// <param name="code">Code.</param> /// <param name="echoCommand">Echo command.</param> public static INCode Parse(string code, string echoCommand = "say") { // スクリプトをステートメントに区切る var statements = code.SplitLine(); // 汎用文字列バッファ var buffer = new StringBuilder(); // 文字列リテラルかどうか var isQuotation = false; // 解析用トークン var token = Token.SpriteTag; // 一時保存変数の定義 string spTag, comName; var args = new List <string>(); // ステートメント var statementList = new List <INStatement>(); // ラベル一覧 var labels = new LabelMap(); // 行番号 var line = 0; foreach (var statement in statements) { // 一時保存変数やフラグの初期化 spTag = comName = ""; args?.Clear(); token = Token.SpriteTag; isQuotation = false; // ここからステートメントの解析 for (var i = 0; i < statement.Length; i++) { // 現在パースしている文字 var chr = statement[i]; // chrの1つ前またはnull文字 var cm1 = (i > 0) ? statement[i - 1] : '\0'; // chrの1つ後またはnull文字 var cp1 = (i < statement.Length - 1) ? statement[i + 1] : '\0'; // リテラル外において,空白などは無視される if ((!isQuotation) && (char.IsControl(chr) || char.IsSeparator(chr) || char.IsWhiteSpace(chr))) { continue; } // コメント文 if ((!isQuotation) && chr == '/' && cp1 == '/') { break; } // 解析処理 switch (token) { // スプライトタグ case Token.SpriteTag: // 現在地がプレフィックスであれば抜ける if (chr == COMMAND_PREFIX || chr == MESSAGE_PREFIX || chr == LABEL_PREFIX) { // スプライトタグをバッファの文字列に設定する spTag = buffer.ToString(); // トークンの切り替え token = Token.Prefix; // バッファの掃除 buffer.Clear(); // 次の段階でプレフィックスのチェックをするためiを前にずらす (for文によるiの増加に合わせる) i--; // 次にすっ飛ばす continue; } //読み取った文字をバッファに入れる buffer.Append(chr); break; // プレフィックスの解析 case Token.Prefix: switch (chr) { case COMMAND_PREFIX: // コマンドの解析に飛ぶ token = Token.Name; continue; case MESSAGE_PREFIX: // メッセージコマンドということにして comName = echoCommand; // 引数解析にすっ飛ばす token = Token.Arguments; continue; case LABEL_PREFIX: // コマンドではない処理を行う token = Token.LabelName; continue; default: throw new ParseStatementException($"Invalid prefix {(chr != '\0' ? chr.ToString() : "")}", line, i); } // コマンド名の解析 case Token.Name: //読み取った文字をバッファに入れる buffer.Append(chr); // 区切り文字が登場したら if (cp1 == COMMAND_SEPARATOR) { // コマンド名を設定して comName = buffer.ToString(); // バッファをお掃除して buffer.Clear(); // 区切り文字の分カーソルを進めて i++; // トークンを切り替えて token = Token.Arguments; // イッテヨシ continue; } break; // 引数の構文解析 case Token.Arguments: switch (chr) { // エスケープシーケンスの処理 case '\\': switch (cp1) { // 改行 case 'n': buffer = buffer.Append('\n'); break; // ダブルクォート case '"': buffer = buffer.Append('"'); break; // バックスラッシュ case '\\': buffer = buffer.Append('\\'); break; // 予期しないやつ default: throw new ParseStatementException($@"Invalid escape sequence \{(cp1 != '\0' ? cp1.ToString() : "")}", line, i); } // エスケープ文字分飛ばす i++; break; // ダブルクォート case '"': // ダブルクォートで包んだ文字列は,空白や,の影響を受けない isQuotation = !isQuotation; break; // その他の文字 default: // 引数の区切り if ((chr == ',' && !isQuotation)) { args?.Add(buffer.ToString()); buffer.Clear(); } // 読み取った引数の文字をバッファに入れる else { buffer.Append(chr); } break; } break; // ラベル名 case Token.LabelName: // 念のためステートメントとして登録できないようコマンド名を初期化 comName = ""; buffer.Append(chr); break; // 異常なトークン(プログラムのバグでない限りありえない) default: throw new ParseStatementException($@"(Bug!!) Unknown token ""{token}""", line, i); } } // バッファが空でない場合,データが残っているので加味 if (!string.IsNullOrEmpty(buffer.ToString())) { switch (token) { case Token.Name: comName = buffer.ToString(); break; case Token.LabelName: labels.Add(buffer.ToString(), line); break; default: args.Add(buffer.ToString()); break; } buffer.Clear(); } // コマンド名がカラッポ = ラベルやコメント行や空行なのでスルー if (string.IsNullOrEmpty(comName)) { continue; } statementList.Add(new NStatement(comName, spTag, args.ToArray())); line++; } return(new NCode(labels, statementList.ToArray())); }