mirror of
https://github.com/fadden/6502bench.git
synced 2025-04-21 16:39:06 +00:00
Initial file commit
This commit is contained in:
parent
469eb49c4a
commit
2c6212404d
312
.gitignore
vendored
Normal file
312
.gitignore
vendored
Normal file
@ -0,0 +1,312 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# 6502bench-specific stuff
|
||||
SourceGen/SourceGen-settings
|
||||
SourceGen/PluginDll
|
||||
DIST_Debug
|
||||
DIST_release
|
||||
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
92
Asm65/Address.cs
Normal file
92
Asm65/Address.cs
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Asm65 {
|
||||
public static class Address {
|
||||
/// <summary>
|
||||
/// Converts a 16- or 24-bit address to a string.
|
||||
/// </summary>
|
||||
/// <param name="addr"></param>
|
||||
/// <returns></returns>
|
||||
public static string AddressToString(int addr, bool always24) {
|
||||
if (!always24 && addr < 65536) {
|
||||
return addr.ToString("x4");
|
||||
} else {
|
||||
return (addr >> 16).ToString("x2") + "/" + (addr & 0xffff).ToString("x4");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses and validates a 16- or 24-bit address, expressed in hexadecimal. Bits
|
||||
/// 16-23 may be specified with a slash.
|
||||
///
|
||||
/// The following all evaluate to the same thing: 1000, $1000, 0x1000, 00/1000.
|
||||
/// </summary>
|
||||
/// <param name="addrStr">String to validate.</param>
|
||||
/// <param name="max">Maximum valid address value.</param>
|
||||
/// <param name="addr">Integer form.</param>
|
||||
/// <returns>True if the address is valid.</returns>
|
||||
public static bool ParseAddress(string addrStr, int max, out int addr) {
|
||||
string trimStr = addrStr.Trim(); // strip whitespace
|
||||
if (trimStr.Length < 1) {
|
||||
addr = -1;
|
||||
return false;
|
||||
}
|
||||
if (trimStr[0] == '$') {
|
||||
trimStr = trimStr.Remove(0, 1);
|
||||
}
|
||||
|
||||
int slashIndex = trimStr.IndexOf('/');
|
||||
try {
|
||||
if (slashIndex < 0) {
|
||||
addr = Convert.ToInt32(trimStr, 16);
|
||||
if (addr < 0 || addr > max) {
|
||||
Debug.WriteLine("Simple value out of range");
|
||||
addr = -1;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
string[] splitStr = trimStr.Split('/');
|
||||
if (splitStr.Length == 2) {
|
||||
int addr1 = Convert.ToInt32(splitStr[0], 16);
|
||||
int addr2 = Convert.ToInt32(splitStr[1], 16);
|
||||
addr = (addr1 << 16) | addr2;
|
||||
// Check components separately to catch overflow.
|
||||
if (addr1 < 0 || addr1 > 255 || addr2 < 0 || addr2 > 65535 ||
|
||||
addr > max) {
|
||||
Debug.WriteLine("Slash value out of range");
|
||||
addr = -1;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
addr = -1;
|
||||
}
|
||||
}
|
||||
} catch (Exception) {
|
||||
// Thrown from Convert.ToInt32
|
||||
//Debug.WriteLine("ValidateAddress: conversion of '" + addrStr + "' failed: " +
|
||||
// ex.Message);
|
||||
addr = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
//Debug.WriteLine("Conv " + addrStr + " --> " + addr.ToString("x6"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
26
Asm65/Asm65.csproj
Normal file
26
Asm65/Asm65.csproj
Normal file
@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CommonUtil\CommonUtil.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
1184
Asm65/CpuDef.cs
Normal file
1184
Asm65/CpuDef.cs
Normal file
File diff suppressed because it is too large
Load Diff
819
Asm65/Formatter.cs
Normal file
819
Asm65/Formatter.cs
Normal file
@ -0,0 +1,819 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using AddressMode = Asm65.OpDef.AddressMode;
|
||||
|
||||
namespace Asm65 {
|
||||
/// <summary>
|
||||
/// Functions used for formatting bits of 65xx code into human-readable form.
|
||||
///
|
||||
/// There are a variety of ways to format a given thing, based on personal preference
|
||||
/// (e.g. whether opcodes are upper- or lower-case) and assembler syntax requirements.
|
||||
///
|
||||
/// The functions in this class serve two purposes: (1) produce consistent output
|
||||
/// throughout the program; (2) cache format strings and other components to reduce
|
||||
/// string manipulation overhead. Note the caching is per-Formatter, so it's best to
|
||||
/// create just one and share it around.
|
||||
///
|
||||
/// The configuration of a Formatter may not be altered once created. This is important
|
||||
/// in situations where we compute output size in one pass and generate it in another,
|
||||
/// because it guarantees that a given Formatter object will produce the same number of
|
||||
/// lines of output.
|
||||
///
|
||||
/// NOTE: if the CpuDef changes, the cached values in the Formatter will become invalid.
|
||||
/// Discard the Formatter and create a new one. (This could be fixed by keying off of
|
||||
/// the OpDef instead of OpDef.Opcode, but that's less convenient.)
|
||||
/// </summary>
|
||||
public class Formatter {
|
||||
/// <summary>
|
||||
/// Various format configuration options. Fill one of these out and pass it to
|
||||
/// the Formatter constructor.
|
||||
/// </summary>
|
||||
public struct FormatConfig {
|
||||
// alpha case for some case-insensitive items
|
||||
public bool mUpperHexDigits; // display hex values in upper case?
|
||||
public bool mUpperOpcodes; // display opcodes in upper case?
|
||||
public bool mUpperPseudoOpcodes; // display pseudo-opcodes in upper case?
|
||||
public bool mUpperOperandA; // display acc operand in upper case?
|
||||
public bool mUpperOperandS; // display stack operand in upper case?
|
||||
public bool mUpperOperandXY; // display index register operand in upper case?
|
||||
public bool mAddSpaceLongComment; // insert space after delimiter for long comments?
|
||||
|
||||
// functional changes to assembly output
|
||||
public bool mSuppressHexNotation; // omit '$' before hex digits
|
||||
|
||||
public bool mAllowHighAsciiCharConst; // can we do high-ASCII character constants?
|
||||
// (this might need to be generalized)
|
||||
|
||||
public string mForceAbsOpcodeSuffix; // these may be null or empty
|
||||
public string mForceAbsOperandPrefix;
|
||||
public string mForceLongOpcodeSuffix;
|
||||
public string mForceLongOperandPrefix;
|
||||
|
||||
public string mEndOfLineCommentDelimiter; // usually ';'
|
||||
public string mFullLineCommentDelimiterBase; // usually ';' or '*', WITHOUT extra space
|
||||
public string mBoxLineCommentDelimiter; // usually blank or ';'
|
||||
|
||||
// miscellaneous
|
||||
public bool mHexDumpAsciiOnly; // disallow non-ASCII chars in hex dumps?
|
||||
|
||||
public enum CharConvMode { Unknown = 0, PlainAscii, HighLowAscii };
|
||||
public CharConvMode mHexDumpCharConvMode; // character conversion mode for dumps
|
||||
|
||||
public enum ExpressionMode { Unknown = 0, Simple, Merlin };
|
||||
public ExpressionMode mExpressionMode; // symbol rendering mode
|
||||
|
||||
// Deserialization helper.
|
||||
public static ExpressionMode ParseExpressionMode(string str) {
|
||||
ExpressionMode em = ExpressionMode.Simple;
|
||||
if (!string.IsNullOrEmpty(str)) {
|
||||
if (Enum.TryParse<ExpressionMode>(str, out ExpressionMode pem)) {
|
||||
em = pem;
|
||||
}
|
||||
}
|
||||
return em;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly char[] sHexCharsLower = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
private static readonly char[] sHexCharsUpper = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Formatter configuration options. Fixed at construction time.
|
||||
/// </summary>
|
||||
private FormatConfig mFormatConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Get a copy of the format config.
|
||||
/// </summary>
|
||||
public FormatConfig Config { get { return mFormatConfig; } }
|
||||
|
||||
// Bits and pieces.
|
||||
char mHexFmtChar;
|
||||
string mHexPrefix;
|
||||
char mAccChar;
|
||||
char mXregChar;
|
||||
char mYregChar;
|
||||
char mSregChar;
|
||||
|
||||
// Format string for offsets.
|
||||
private string mOffset20Format;
|
||||
|
||||
// Format strings for addresses.
|
||||
private string mAddrFormatNoBank;
|
||||
private string mAddrFormatWithBank;
|
||||
|
||||
// Generated opcode strings. The index is the bitwise OR of the opcode value and
|
||||
// the disambiguation value. In most cases this just helps us avoid calling
|
||||
// ToUpper incessantly.
|
||||
private Dictionary<int, string> mOpcodeStrings = new Dictionary<int, string>();
|
||||
|
||||
// Generated pseudo-opcode strings.
|
||||
private Dictionary<string, string> mPseudoOpStrings = new Dictionary<string, string>();
|
||||
|
||||
// Generated format strings for operands. The index is the bitwise OR of the
|
||||
// address mode and the disambiguation value.
|
||||
private Dictionary<int, string> mOperandFormats = new Dictionary<int, string>();
|
||||
|
||||
// Generated format strings for bytes.
|
||||
private const int MAX_BYTE_DUMP = 4;
|
||||
private string[] mByteDumpFormats = new string[MAX_BYTE_DUMP];
|
||||
|
||||
// Generated format strings for hex values.
|
||||
private string[] mHexValueFormats = new string[4];
|
||||
|
||||
private string mFullLineCommentDelimiterPlus;
|
||||
|
||||
// Buffer to use when generating hex dump lines.
|
||||
private char[] mHexDumpBuffer;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A 16-character array with 0-9a-f, for hex conversions. The letters will be
|
||||
/// upper or lower case, per the format config.
|
||||
/// </summary>
|
||||
public char[] HexDigits {
|
||||
get {
|
||||
return mFormatConfig.mUpperHexDigits ? sHexCharsUpper : sHexCharsLower;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String to put between the operand and the end-of-line comment.
|
||||
/// </summary>
|
||||
public string EndOfLineCommentDelimiter {
|
||||
get { return mFormatConfig.mEndOfLineCommentDelimiter; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String to put at the start of a line with a full-line comment.
|
||||
/// </summary>
|
||||
public string FullLineCommentDelimiter {
|
||||
get { return mFullLineCommentDelimiterPlus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String to put at the start of a line that has a box comment. This is usually
|
||||
/// blank, as it's only needed if the assembler doesn't recognize the box character
|
||||
/// as a comment.
|
||||
/// </summary>
|
||||
public string BoxLineCommentDelimiter {
|
||||
get { return mFormatConfig.mBoxLineCommentDelimiter; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When formatting a symbol with an offset, if this flag is set, generate code that
|
||||
/// assumes the assembler applies the adjustment, then shifts the result. If not,
|
||||
/// assume the assembler shifts the operand before applying the adjustment.
|
||||
/// </summary>
|
||||
public FormatConfig.ExpressionMode ExpressionMode {
|
||||
get { return mFormatConfig.mExpressionMode; }
|
||||
}
|
||||
|
||||
|
||||
public Formatter(FormatConfig config) {
|
||||
mFormatConfig = config;
|
||||
if (mFormatConfig.mEndOfLineCommentDelimiter == null) {
|
||||
mFormatConfig.mEndOfLineCommentDelimiter = string.Empty;
|
||||
}
|
||||
if (mFormatConfig.mFullLineCommentDelimiterBase == null) {
|
||||
mFormatConfig.mFullLineCommentDelimiterBase = string.Empty;
|
||||
}
|
||||
if (mFormatConfig.mBoxLineCommentDelimiter == null) {
|
||||
mFormatConfig.mBoxLineCommentDelimiter = string.Empty;
|
||||
}
|
||||
|
||||
if (mFormatConfig.mAddSpaceLongComment) {
|
||||
mFullLineCommentDelimiterPlus = mFormatConfig.mFullLineCommentDelimiterBase + " ";
|
||||
} else {
|
||||
mFullLineCommentDelimiterPlus = mFormatConfig.mFullLineCommentDelimiterBase;
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
// Prep the static parts of the hex dump buffer.
|
||||
mHexDumpBuffer = new char[73];
|
||||
for (int i = 0; i < mHexDumpBuffer.Length; i++) {
|
||||
mHexDumpBuffer[i] = ' ';
|
||||
}
|
||||
mHexDumpBuffer[6] = ':';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the pieces we use to build format strings.
|
||||
/// </summary>
|
||||
private void Reset() {
|
||||
// Clear old data. (No longer needed.)
|
||||
//mAddrFormatNoBank = mAddrFormatWithBank = null;
|
||||
//mOffset20Format = null;
|
||||
//mOpcodeStrings.Clear();
|
||||
//mPseudoOpStrings.Clear();
|
||||
//mOperandFormats.Clear();
|
||||
//for (int i = 0; i < MAX_BYTE_DUMP; i++) {
|
||||
// mByteDumpFormats[i] = null;
|
||||
//}
|
||||
|
||||
if (mFormatConfig.mUpperHexDigits) {
|
||||
mHexFmtChar = 'X';
|
||||
} else {
|
||||
mHexFmtChar = 'x';
|
||||
}
|
||||
if (mFormatConfig.mSuppressHexNotation) {
|
||||
mHexPrefix = "";
|
||||
} else {
|
||||
mHexPrefix = "$";
|
||||
}
|
||||
if (mFormatConfig.mUpperOperandA) {
|
||||
mAccChar = 'A';
|
||||
} else {
|
||||
mAccChar = 'a';
|
||||
}
|
||||
if (mFormatConfig.mUpperOperandXY) {
|
||||
mXregChar = 'X';
|
||||
mYregChar = 'Y';
|
||||
} else {
|
||||
mXregChar = 'x';
|
||||
mYregChar = 'y';
|
||||
}
|
||||
if (mFormatConfig.mUpperOperandS) {
|
||||
mSregChar = 'S';
|
||||
} else {
|
||||
mSregChar = 's';
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a 24-bit offset value as hex.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset to format.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatOffset24(int offset) {
|
||||
if (string.IsNullOrEmpty(mOffset20Format)) {
|
||||
mOffset20Format = "+{0:" + mHexFmtChar + "6}";
|
||||
}
|
||||
return string.Format(mOffset20Format, offset & 0x0fffff);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value in hexadecimal. The width is padded with zeroes to make the
|
||||
/// length even (so it'll be $00, $0100, $010000, etc.) If minDigits is nonzero,
|
||||
/// additional zeroes may be added.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to format, up to 32 bits.</param>
|
||||
/// <param name="minDigits">Minimum width, in printed digits (e.g. 4 is "0000").</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatHexValue(int value, int minDigits) {
|
||||
int width = minDigits > 2 ? minDigits : 2;
|
||||
if (width < 8 && value > 0xffffff) {
|
||||
width = 8;
|
||||
} else if (width < 6 && value > 0xffff) {
|
||||
width = 6;
|
||||
} else if (width < 4 && value > 0xff) {
|
||||
width = 4;
|
||||
}
|
||||
int index = (width / 2) - 1;
|
||||
if (mHexValueFormats[index] == null) {
|
||||
mHexValueFormats[index] = mHexFmtChar + width.ToString();
|
||||
}
|
||||
return mHexPrefix + value.ToString(mHexValueFormats[index]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a value as a number in the specified base.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to format.</param>
|
||||
/// <param name="numBase">Numeric base (2, 10, or 16).</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatValueInBase(int value, int numBase) {
|
||||
switch (numBase) {
|
||||
case 2:
|
||||
return FormatBinaryValue(value, 8);
|
||||
case 10:
|
||||
return FormatDecimalValue(value);
|
||||
case 16:
|
||||
return FormatHexValue(value, 2);
|
||||
default:
|
||||
Debug.Assert(false);
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value as decimal.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to convert.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatDecimalValue(int value) {
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value in binary, padding with zeroes so the length is a multiple of 8.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to convert.</param>
|
||||
/// <param name="minDigits">Minimum width, in printed digits. Will be rounded up to
|
||||
/// a multiple of 8.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatBinaryValue(int value, int minDigits) {
|
||||
string binaryStr = Convert.ToString(value, 2);
|
||||
int desiredWidth = ((binaryStr.Length + 7) / 8) * 8;
|
||||
if (desiredWidth < minDigits) {
|
||||
desiredWidth = ((minDigits + 7) / 8) * 8;
|
||||
}
|
||||
return '%' + binaryStr.PadLeft(desiredWidth, '0');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value as an ASCII character, surrounded by quotes. Must be a valid
|
||||
/// low- or high-ASCII value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to format.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
private string FormatAsciiChar(int value) {
|
||||
Debug.Assert(CommonUtil.TextUtil.IsHiLoAscii(value));
|
||||
char ch = (char)(value & 0x7f);
|
||||
bool hiAscii = ((value & 0x80) != 0);
|
||||
|
||||
StringBuilder sb;
|
||||
int method = -1;
|
||||
switch (method) {
|
||||
case 0:
|
||||
default:
|
||||
// Convention is from Merlin: single quote for low-ASCII, double-quote
|
||||
// for high-ASCII. Add a backslash if we're quoting the delimiter.
|
||||
sb = new StringBuilder(4);
|
||||
char quoteChar = ((value & 0x80) == 0) ? '\'' : '"';
|
||||
sb.Append(quoteChar);
|
||||
if (quoteChar == ch) {
|
||||
sb.Append('\\');
|
||||
}
|
||||
sb.Append(ch);
|
||||
sb.Append(quoteChar);
|
||||
break;
|
||||
case 1:
|
||||
// Convention is similar to Merlin, but with curly-quotes so it doesn't
|
||||
// look weird when quoting ' or ".
|
||||
sb = new StringBuilder(3);
|
||||
sb.Append(hiAscii ? '\u201c' : '\u2018');
|
||||
sb.Append(ch);
|
||||
sb.Append(hiAscii ? '\u201d' : '\u2019');
|
||||
break;
|
||||
case 2:
|
||||
// Always use apostrophe, but follow it with an up-arrow to indicate
|
||||
// that it's high-ASCII.
|
||||
sb = new StringBuilder(4);
|
||||
sb.Append("'");
|
||||
sb.Append(ch);
|
||||
sb.Append("'");
|
||||
if (hiAscii) {
|
||||
sb.Append('\u21e1'); // UPWARDS DASHED ARROW
|
||||
//sb.Append('\u2912'); // UPWARDS ARROW TO BAR
|
||||
}
|
||||
break;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value as an ASCII character, if possible, or as a hex value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to format.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatAsciiOrHex(int value) {
|
||||
bool hiAscii = ((value & 0x80) != 0);
|
||||
if (hiAscii && !mFormatConfig.mAllowHighAsciiCharConst) {
|
||||
return FormatHexValue(value, 2);
|
||||
} else if (CommonUtil.TextUtil.IsHiLoAscii(value)) {
|
||||
return FormatAsciiChar(value);
|
||||
} else {
|
||||
return FormatHexValue(value, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a 16- or 24-bit address value. This is intended for the left column
|
||||
/// of something (hex dump, code listing), not as an operand.
|
||||
/// </summary>
|
||||
/// <param name="address">Address to format.</param>
|
||||
/// <param name="showBank">Set to true for CPUs with 24-bit address spaces.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatAddress(int address, bool showBank) {
|
||||
if (mAddrFormatNoBank == null) {
|
||||
mAddrFormatNoBank = "{0:" + mHexFmtChar + "4}";
|
||||
mAddrFormatWithBank = "{0:" + mHexFmtChar + "2}/{1:" + mHexFmtChar + "4}";
|
||||
}
|
||||
if (showBank) {
|
||||
return string.Format(mAddrFormatWithBank, address >> 16, address & 0xffff);
|
||||
} else {
|
||||
return string.Format(mAddrFormatNoBank, address & 0xffff);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats an adjustment, as "+decimal" or "-decimal". If no adjustment
|
||||
/// is required, an empty string is returned.
|
||||
/// </summary>
|
||||
/// <param name="adjValue">Adjustment value.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatAdjustment(int adjValue) {
|
||||
if (adjValue == 0) {
|
||||
return string.Empty;
|
||||
}
|
||||
// This formats in decimal with a leading '+' or '-'. To avoid adding a plus
|
||||
// on zero, we'd use "+#;-#;0", but we took care of the zero case above.
|
||||
return adjValue.ToString("+0;-#");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the instruction opcode mnemonic, and caches the result.
|
||||
///
|
||||
/// It may be necessary to modify the mnemonic for some assemblers, e.g. LDA from a
|
||||
/// 24-bit address might need to be LDAL, even if the high byte is nonzero.
|
||||
/// </summary>
|
||||
/// <param name="op">Opcode to format</param>
|
||||
/// <param name="wdis">Width disambiguation specifier.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatOpcode(OpDef op, OpDef.WidthDisambiguation wdis) {
|
||||
// TODO(someday): using op.Opcode as the key is a bad idea, as the operation may
|
||||
// not be the same on different CPUs. We currently rely on the caller to discard
|
||||
// the Formatter when the CPU definition changes. We'd be better off keying off of
|
||||
// the OpDef object and factoring wdis in some other way.
|
||||
int key = op.Opcode | ((int)wdis << 8);
|
||||
if (!mOpcodeStrings.TryGetValue(key, out string opcodeStr)) {
|
||||
// Not found, generate value.
|
||||
opcodeStr = FormatMnemonic(op.Mnemonic, wdis);
|
||||
|
||||
// Memoize.
|
||||
mOpcodeStrings[key] = opcodeStr;
|
||||
}
|
||||
return opcodeStr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the string as an opcode mnemonic.
|
||||
///
|
||||
/// It may be necessary to modify the mnemonic for some assemblers, e.g. LDA from a
|
||||
/// 24-bit address might need to be LDAL, even if the high byte is nonzero.
|
||||
/// </summary>
|
||||
/// <param name="mnemonic">Instruction mnemonic string.</param>
|
||||
/// <param name="wdis">Width disambiguation specifier.</param>
|
||||
/// <returns></returns>
|
||||
public string FormatMnemonic(string mnemonic, OpDef.WidthDisambiguation wdis) {
|
||||
string opcodeStr = mnemonic;
|
||||
if (wdis == OpDef.WidthDisambiguation.ForceAbs) {
|
||||
if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOpcodeSuffix)) {
|
||||
opcodeStr += mFormatConfig.mForceAbsOpcodeSuffix;
|
||||
}
|
||||
} else if (wdis == OpDef.WidthDisambiguation.ForceLong ||
|
||||
wdis == OpDef.WidthDisambiguation.ForceLongMaybe) {
|
||||
if (!string.IsNullOrEmpty(mFormatConfig.mForceLongOpcodeSuffix)) {
|
||||
opcodeStr += mFormatConfig.mForceLongOpcodeSuffix;
|
||||
}
|
||||
} else {
|
||||
Debug.Assert(wdis == OpDef.WidthDisambiguation.None);
|
||||
}
|
||||
if (mFormatConfig.mUpperOpcodes) {
|
||||
opcodeStr = opcodeStr.ToUpperInvariant();
|
||||
}
|
||||
return opcodeStr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an operand format.
|
||||
/// </summary>
|
||||
/// <param name="addrMode">Addressing mode.</param>
|
||||
/// <returns>Format string.</returns>
|
||||
private string GenerateOperandFormat(OpDef.AddressMode addrMode,
|
||||
OpDef.WidthDisambiguation wdis) {
|
||||
string fmt;
|
||||
string wdisStr = string.Empty;
|
||||
|
||||
if (wdis == OpDef.WidthDisambiguation.ForceAbs) {
|
||||
if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOperandPrefix)) {
|
||||
wdisStr = mFormatConfig.mForceAbsOperandPrefix;
|
||||
}
|
||||
} else if (wdis == OpDef.WidthDisambiguation.ForceLong) {
|
||||
if (!string.IsNullOrEmpty(mFormatConfig.mForceLongOperandPrefix)) {
|
||||
wdisStr = mFormatConfig.mForceLongOperandPrefix;
|
||||
}
|
||||
} else if (wdis == OpDef.WidthDisambiguation.ForceLongMaybe) {
|
||||
// Don't add a width disambiguator to an operand that is unambiguously long.
|
||||
} else {
|
||||
Debug.Assert(wdis == OpDef.WidthDisambiguation.None);
|
||||
}
|
||||
|
||||
switch (addrMode) {
|
||||
case AddressMode.Abs:
|
||||
case AddressMode.AbsLong:
|
||||
case AddressMode.BlockMove:
|
||||
case AddressMode.StackAbs:
|
||||
case AddressMode.DP:
|
||||
case AddressMode.PCRel:
|
||||
case AddressMode.PCRelLong: // BRL
|
||||
case AddressMode.StackInt: // BRK/COP
|
||||
case AddressMode.StackPCRelLong: // PER
|
||||
case AddressMode.WDM:
|
||||
fmt = wdisStr + "{0}";
|
||||
break;
|
||||
case AddressMode.AbsIndexX:
|
||||
case AddressMode.AbsIndexXLong:
|
||||
case AddressMode.DPIndexX:
|
||||
fmt = wdisStr + "{0}," + mXregChar;
|
||||
break;
|
||||
case AddressMode.DPIndexY:
|
||||
case AddressMode.AbsIndexY:
|
||||
fmt = wdisStr + "{0}," + mYregChar;
|
||||
break;
|
||||
case AddressMode.AbsIndexXInd:
|
||||
case AddressMode.DPIndexXInd:
|
||||
fmt = wdisStr + "({0}," + mXregChar + ")";
|
||||
break;
|
||||
case AddressMode.AbsInd:
|
||||
case AddressMode.DPInd:
|
||||
case AddressMode.StackDPInd: // PEI
|
||||
fmt = "({0})";
|
||||
break;
|
||||
case AddressMode.AbsIndLong:
|
||||
case AddressMode.DPIndLong:
|
||||
// IIgs monitor uses "()" for AbsIndLong, E&L says "[]". Assemblers
|
||||
// seem to expect the latter.
|
||||
fmt = "[{0}]";
|
||||
break;
|
||||
case AddressMode.Acc:
|
||||
fmt = "" + mAccChar;
|
||||
break;
|
||||
case AddressMode.DPIndIndexY:
|
||||
fmt = "({0})," + mYregChar;
|
||||
break;
|
||||
case AddressMode.DPIndIndexYLong:
|
||||
fmt = "[{0}]," + mYregChar;
|
||||
break;
|
||||
case AddressMode.Imm:
|
||||
case AddressMode.ImmLongA:
|
||||
case AddressMode.ImmLongXY:
|
||||
fmt = "#{0}";
|
||||
break;
|
||||
case AddressMode.Implied:
|
||||
case AddressMode.StackPull:
|
||||
case AddressMode.StackPush:
|
||||
case AddressMode.StackRTI:
|
||||
case AddressMode.StackRTL:
|
||||
case AddressMode.StackRTS:
|
||||
fmt = "";
|
||||
break;
|
||||
case AddressMode.StackRel:
|
||||
fmt = "{0}," + mSregChar;
|
||||
break;
|
||||
case AddressMode.StackRelIndIndexY:
|
||||
fmt = "({0}," + mSregChar + ")," + mYregChar;
|
||||
break;
|
||||
|
||||
case AddressMode.Unknown:
|
||||
default:
|
||||
Debug.Assert(false);
|
||||
fmt = "???";
|
||||
break;
|
||||
}
|
||||
|
||||
return fmt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a pseudo-opcode.
|
||||
/// </summary>
|
||||
/// <param name="opstr">Pseudo-op string to format.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatPseudoOp(string opstr) {
|
||||
if (!mPseudoOpStrings.TryGetValue(opstr, out string result)) {
|
||||
if (mFormatConfig.mUpperPseudoOpcodes) {
|
||||
result = mPseudoOpStrings[opstr] = opstr.ToUpperInvariant();
|
||||
} else {
|
||||
result = mPseudoOpStrings[opstr] = opstr;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the instruction operand.
|
||||
/// </summary>
|
||||
/// <param name="op">Opcode definition (needed for address mode).</param>
|
||||
/// <param name="contents">Label or numeric operand value.</param>
|
||||
/// <param name="wdis">Width disambiguation value.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatOperand(OpDef op, string contents, OpDef.WidthDisambiguation wdis) {
|
||||
Debug.Assert(((int)op.AddrMode & 0xff) == (int) op.AddrMode);
|
||||
int key = (int) op.AddrMode | ((int)wdis << 8);
|
||||
|
||||
if (!mOperandFormats.TryGetValue(key, out string format)) {
|
||||
format = mOperandFormats[key] = GenerateOperandFormat(op.AddrMode, wdis);
|
||||
}
|
||||
return string.Format(format, contents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a format string for N hex bytes.
|
||||
/// </summary>
|
||||
/// <param name="len">Number of bytes to handle in the format.</param>
|
||||
private void GenerateByteFormat(int len) {
|
||||
Debug.Assert(len <= MAX_BYTE_DUMP);
|
||||
|
||||
StringBuilder sb = new StringBuilder(len * 6);
|
||||
for (int i = 0; i < len; i++) {
|
||||
//. e.g. "{0:x2}"
|
||||
sb.Append("{" + i + ":" + mHexFmtChar + "2}");
|
||||
}
|
||||
mByteDumpFormats[len - 1] = sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats 1-4 bytes as hex values.
|
||||
/// </summary>
|
||||
/// <param name="data">Data source.</param>
|
||||
/// <param name="offset">Start offset within data array.</param>
|
||||
/// <param name="length">Number of bytes to print. Fewer than this many may
|
||||
/// actually appear.</param>
|
||||
/// <returns>Formatted data string.</returns>
|
||||
public string FormatBytes(byte[] data, int offset, int length) {
|
||||
Debug.Assert(length > 0);
|
||||
int printLen = length < MAX_BYTE_DUMP ? length : MAX_BYTE_DUMP;
|
||||
if (string.IsNullOrEmpty(mByteDumpFormats[printLen - 1])) {
|
||||
GenerateByteFormat(printLen);
|
||||
}
|
||||
string format = mByteDumpFormats[printLen - 1];
|
||||
string result;
|
||||
|
||||
// The alternative is to allocate a temporary object[] and copy the integers
|
||||
// into it, which requires boxing. We know we're only printing 1-4 bytes, so
|
||||
// it's easier to just handle each case individually.
|
||||
switch (printLen) {
|
||||
case 1:
|
||||
result = string.Format(format, data[offset]);
|
||||
break;
|
||||
case 2:
|
||||
result = string.Format(format, data[offset], data[offset + 1]);
|
||||
break;
|
||||
case 3:
|
||||
result = string.Format(format,
|
||||
data[offset], data[offset + 1], data[offset + 2]);
|
||||
break;
|
||||
case 4:
|
||||
result = string.Format(format,
|
||||
data[offset], data[offset + 1], data[offset + 2], data[offset + 3]);
|
||||
break;
|
||||
default:
|
||||
result = "INTERNAL ERROR";
|
||||
break;
|
||||
}
|
||||
if (length > printLen) {
|
||||
result += "...";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats an end-of-line comment, prepending an end-of-line comment delimiter.
|
||||
/// </summary>
|
||||
/// <param name="comment">Comment string; may be empty.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatEolComment(string comment) {
|
||||
if (string.IsNullOrEmpty(comment) ||
|
||||
string.IsNullOrEmpty(mFormatConfig.mEndOfLineCommentDelimiter)) {
|
||||
return comment;
|
||||
} else {
|
||||
return mFormatConfig.mEndOfLineCommentDelimiter + comment;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a collection of bytes as a dense hex string.
|
||||
/// </summary>
|
||||
/// <param name="data">Data source.</param>
|
||||
/// <param name="offset">Start offset within data array.</param>
|
||||
/// <param name="length">Number of bytes to print.</param>
|
||||
/// <returns>Formatted data string.</returns>
|
||||
public string FormatDenseHex(byte[] data, int offset, int length) {
|
||||
char[] hexChars = mFormatConfig.mUpperHexDigits ? sHexCharsUpper : sHexCharsLower;
|
||||
char[] text = new char[length * 2];
|
||||
for (int i = 0; i < length; i++) {
|
||||
byte val = data[offset + i];
|
||||
text[i * 2] = hexChars[val >> 4];
|
||||
text[i * 2 + 1] = hexChars[val & 0x0f];
|
||||
}
|
||||
return new string(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats up to 16 bytes of data into a single line hex dump, in this format:
|
||||
/// <pre>012345: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef</pre>
|
||||
/// </summary>
|
||||
/// <param name="data">Reference to data.</param>
|
||||
/// <param name="offset">Start offset.</param>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string FormatHexDump(byte[] data, int offset) {
|
||||
FormatHexDumpCommon(data, offset);
|
||||
// this is the only allocation
|
||||
return new string(mHexDumpBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats up to 16 bytes of data into a single line hex dump. The output is
|
||||
/// appended to the StringBuilder.
|
||||
/// </summary>
|
||||
/// <param name="data">Reference to data.</param>
|
||||
/// <param name="offset">Start offset.</param>
|
||||
/// <param name="sb">StringBuilder that receives output.</param>
|
||||
public void FormatHexDump(byte[] data, int offset, StringBuilder sb) {
|
||||
FormatHexDumpCommon(data, offset);
|
||||
sb.Append(mHexDumpBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats up to 16 bytes of data into mHexDumpBuffer.
|
||||
/// </summary>
|
||||
private void FormatHexDumpCommon(byte[] data, int offset) {
|
||||
Debug.Assert(offset >= 0 && offset < data.Length);
|
||||
Debug.Assert(data.Length < (1 << 24));
|
||||
const int dataCol = 8;
|
||||
const int asciiCol = 57;
|
||||
|
||||
char[] hexChars = mFormatConfig.mUpperHexDigits ? sHexCharsUpper : sHexCharsLower;
|
||||
char[] outBuf = mHexDumpBuffer;
|
||||
|
||||
// address field
|
||||
int addr = offset;
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
outBuf[i] = hexChars[addr & 0x0f];
|
||||
addr >>= 4;
|
||||
}
|
||||
|
||||
// hex digits and characters
|
||||
int length = Math.Min(16, data.Length - offset);
|
||||
int index;
|
||||
for (index = 0; index < length; index++) {
|
||||
byte val = data[offset + index];
|
||||
outBuf[dataCol + index * 3] = hexChars[val >> 4];
|
||||
outBuf[dataCol + index * 3 + 1] = hexChars[val & 0x0f];
|
||||
outBuf[asciiCol + index] = CharConv(val);
|
||||
}
|
||||
|
||||
// for partial line, clear out previous contents
|
||||
for (; index < 16; index++) {
|
||||
outBuf[dataCol + index * 3] =
|
||||
outBuf[dataCol + index * 3 + 1] =
|
||||
outBuf[asciiCol + index] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a byte into printable form according to the current hex dump
|
||||
/// character conversion mode.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to convert.</param>
|
||||
/// <returns>Printable character.</returns>
|
||||
private char CharConv(byte val) {
|
||||
char ch;
|
||||
if (mFormatConfig.mHexDumpCharConvMode == FormatConfig.CharConvMode.HighLowAscii) {
|
||||
ch = (char)(val & 0x7f);
|
||||
} else {
|
||||
ch = (char)val;
|
||||
}
|
||||
if (CommonUtil.TextUtil.IsPrintableAscii(ch)) {
|
||||
return ch;
|
||||
} else if (mFormatConfig.mHexDumpAsciiOnly) {
|
||||
return '.';
|
||||
} else {
|
||||
// These values makes the hex dump ListView freak out.
|
||||
//if (ch < 0x20) {
|
||||
// return (char)(ch + '\u2400'); // Unicode "control pictures" block
|
||||
//}
|
||||
//return '\ufffd'; // Unicode "replacement character"
|
||||
|
||||
//return '\u00bf'; // INVERTED QUESTION MARK
|
||||
return '\u00b7'; // MIDDLE DOT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
Asm65/Helper.cs
Normal file
56
Asm65/Helper.cs
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Asm65 {
|
||||
/// <summary>
|
||||
/// Small utility functions.
|
||||
/// </summary>
|
||||
public static class Helper {
|
||||
/// <summary>
|
||||
/// Computes the target address of an 8-bit relative branch instruction.
|
||||
/// </summary>
|
||||
/// <param name="addr">24-bit address of branch instruction opcode.</param>
|
||||
/// <param name="branchOffset">Branch operand.</param>
|
||||
/// <returns>Target address.</returns>
|
||||
public static int RelOffset8(int addr, sbyte branchOffset) {
|
||||
// Branch is relative to the start of the following instruction, so add 2.
|
||||
// Branches wrap around the current bank (in both directions), and the target
|
||||
// is in the same bank as source addr.
|
||||
Debug.Assert(addr >= 0 && addr <= 0xffffff);
|
||||
int target = (addr + 2 + branchOffset) & 0xffff;
|
||||
target |= addr & 0x7fff0000;
|
||||
return target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the target address of a 16-bit relative branch instruction.
|
||||
/// </summary>
|
||||
/// <param name="addr">24-bit address of branch instruction opcode.</param>
|
||||
/// <param name="branchOffset">Branch operand.</param>
|
||||
/// <returns>Target address.</returns>
|
||||
public static int RelOffset16(int addr, short branchOffset) {
|
||||
// Branch is relative to the start of the following instruction, so add 3.
|
||||
// Branches wrap around the current bank (in both directions), and the target
|
||||
// is in the same bank as source addr.
|
||||
Debug.Assert(addr >= 0 && addr <= 0xffffff);
|
||||
int target = (addr + 3 + branchOffset) & 0xffff;
|
||||
target |= addr & 0x7fff0000;
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
78
Asm65/Label.cs
Normal file
78
Asm65/Label.cs
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Asm65 {
|
||||
/// <summary>
|
||||
/// Utility classes for working with labels.
|
||||
///
|
||||
/// The decision of whether to treat labels as case-sensitive or case-insensitive is
|
||||
/// encapsulated here. All code should be case-preserving, but the comparison method
|
||||
/// and "normal form" are defined here.
|
||||
/// </summary>
|
||||
public class Label {
|
||||
// Arbitrary choice for SourceGen. Different assemblers have different limits.
|
||||
public const int MAX_LABEL_LEN = 32;
|
||||
|
||||
public const bool LABELS_CASE_SENSITIVE = true;
|
||||
|
||||
/// <summary>
|
||||
/// String comparer to use when comparing labels.
|
||||
///
|
||||
/// We may want case-insensitive string compares, and we want the "invariant culture"
|
||||
/// version for consistent results across users in multiple locales. (The labels are
|
||||
/// expected to be ASCII strings, so the latter isn't crucial unless we change the
|
||||
/// allowed set.)
|
||||
/// </summary>
|
||||
public static readonly StringComparer LABEL_COMPARER = LABELS_CASE_SENSITIVE ?
|
||||
StringComparer.InvariantCulture :
|
||||
StringComparer.InvariantCultureIgnoreCase;
|
||||
|
||||
/// <summary>
|
||||
/// Regex pattern for a valid label.
|
||||
///
|
||||
/// ASCII-only, starts with letter or underscore, followed by at least
|
||||
/// one alphanumeric or underscore. Some assemblers may allow single-letter
|
||||
/// labels, but I don't want to risk confusion with A/S/X/Y. So either we
|
||||
/// reserve those, or we just mandate a two-character minimum.
|
||||
/// </summary>
|
||||
private static string sValidLabelPattern = @"^[a-zA-Z_][a-zA-Z0-9_]+$";
|
||||
private static Regex sValidLabelCharRegex = new Regex(sValidLabelPattern);
|
||||
|
||||
/// <summary>
|
||||
/// Validates a label, confirming that it is correctly formed.
|
||||
/// </summary>
|
||||
/// <param name="label">Label to validate.</param>
|
||||
/// <returns>True if the label is correctly formed.</returns>
|
||||
public static bool ValidateLabel(string label) {
|
||||
if (label.Length > MAX_LABEL_LEN) {
|
||||
return false;
|
||||
}
|
||||
MatchCollection matches = sValidLabelCharRegex.Matches(label);
|
||||
return matches.Count == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns "normal form" of label. This matches LABEL_COMPARER behavior.
|
||||
/// </summary>
|
||||
/// <param name="label">Label to transform.</param>
|
||||
/// <returns>Transformed label.</returns>
|
||||
public static string ToNormal(string label) {
|
||||
return LABELS_CASE_SENSITIVE ? label : label.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
}
|
60
Asm65/Number.cs
Normal file
60
Asm65/Number.cs
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Asm65 {
|
||||
public class Number {
|
||||
/// <summary>
|
||||
/// Parses an integer in a variety of formats (hex, decimal, binary).
|
||||
///
|
||||
/// Trim whitespace before calling here.
|
||||
/// </summary>
|
||||
/// <param name="str">String to parse.</param>
|
||||
/// <param name="val">Integer value of string.</param>
|
||||
/// <param name="intBase">What base the string was in (2, 10, or 16).</param>
|
||||
/// <returns>True if the parsing was successful.</returns>
|
||||
public static bool TryParseInt(string str, out int val, out int intBase) {
|
||||
if (string.IsNullOrEmpty(str)) {
|
||||
val = intBase = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str[0] == '$') {
|
||||
intBase = 16;
|
||||
str = str.Substring(1); // Convert functions don't like '$'
|
||||
} else if (str.Length > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
|
||||
intBase = 16;
|
||||
} else if (str[0] == '%') {
|
||||
intBase = 2;
|
||||
str = str.Substring(1); // Convert functions don't like '%'
|
||||
} else {
|
||||
intBase = 10; // try it as decimal
|
||||
}
|
||||
|
||||
try {
|
||||
val = Convert.ToInt32(str, intBase);
|
||||
//Debug.WriteLine("GOT " + val + " - " + intBase);
|
||||
} catch (Exception) {
|
||||
//Debug.WriteLine("TryParseInt failed on '" + str + "': " + ex.Message);
|
||||
val = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
3225
Asm65/OpDef.cs
Normal file
3225
Asm65/OpDef.cs
Normal file
File diff suppressed because it is too large
Load Diff
738
Asm65/OpDescription.cs
Normal file
738
Asm65/OpDescription.cs
Normal file
@ -0,0 +1,738 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Asm65 {
|
||||
/// <summary>
|
||||
/// Human-readable text describing instructions.
|
||||
///
|
||||
/// The expectation is that the long description will include information about all
|
||||
/// address modes and any differences in behavior between CPUs. So there will be one
|
||||
/// entry per instruction mnemonic, and one global table for all CPUs, rather than one
|
||||
/// entry per opcode and one instance per CpuDef.
|
||||
///
|
||||
/// There may, however, be different instances for different Cultures. Also, the 65816
|
||||
/// traditionally splits JSR/JSL and JMP/JML, so in that case there will be two entries
|
||||
/// for the same instruction.
|
||||
/// </summary>
|
||||
public class OpDescription {
|
||||
private Dictionary<string, string> mShortDescriptions;
|
||||
|
||||
private Dictionary<string, string> mLongDescriptions;
|
||||
|
||||
private Dictionary<OpDef.AddressMode, string> mAddressModeDescriptions;
|
||||
|
||||
private Dictionary<OpDef.CycleMod, string> mCycleModDescriptions;
|
||||
|
||||
private OpDescription(Dictionary<string, string> sd, Dictionary<string, string> ld,
|
||||
Dictionary<OpDef.AddressMode, string> am, Dictionary<OpDef.CycleMod, string> cm) {
|
||||
mShortDescriptions = sd;
|
||||
mLongDescriptions = ld;
|
||||
mAddressModeDescriptions = am;
|
||||
mCycleModDescriptions = cm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an OpDescription instance for the requested region.
|
||||
/// </summary>
|
||||
/// <param name="region">TBD</param>
|
||||
public static OpDescription GetOpDescription(string region) {
|
||||
// ignoring region for now
|
||||
return new OpDescription(sShort_enUS, sLong_enUS, sAddrMode_enUS, sCycleMod_enUS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Short description of instruction, e.g. "Load Accumulator".
|
||||
/// </summary>
|
||||
/// <param name="mnemonic">Instruction mnemonic.</param>
|
||||
/// <returns>Short description string, or empty string if not found.</returns>
|
||||
public string GetShortDescription(string mnemonic) {
|
||||
if (mShortDescriptions.TryGetValue(mnemonic, out string desc)) {
|
||||
return desc;
|
||||
} else {
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Long description of instruction. May span multiple lines, with embedded CRLF at
|
||||
/// paragraph breaks.
|
||||
/// </summary>
|
||||
/// <param name="mnemonic">Instruction mnemonic.</param>
|
||||
/// <returns>Long description string, or empty string if not found.</returns>
|
||||
public string GetLongDescription(string mnemonic) {
|
||||
if (mLongDescriptions.TryGetValue(mnemonic, out string desc)) {
|
||||
return desc;
|
||||
} else {
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Address mode short description.
|
||||
/// </summary>
|
||||
/// <param name="addrMode">Address mode to look up.</param>
|
||||
/// <returns>Description string, or an empty string for instructions
|
||||
/// with implied address modes.</returns>
|
||||
public string GetAddressModeDescription(OpDef.AddressMode addrMode) {
|
||||
if (mAddressModeDescriptions.TryGetValue(addrMode, out string desc)) {
|
||||
return desc;
|
||||
} else {
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cycle modifier description.
|
||||
/// </summary>
|
||||
/// <param name="modBit">A single-bit item from the CycleMod enum.</param>
|
||||
/// <returns>Description string, or question marks if not found.</returns>
|
||||
public string GetCycleModDescription(OpDef.CycleMod modBit) {
|
||||
if (mCycleModDescriptions.TryGetValue(modBit, out string desc)) {
|
||||
return desc;
|
||||
} else {
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Short descriptions, USA English.
|
||||
///
|
||||
/// Text is adapted from instruction summaries in Eyes & Lichty, which are slightly
|
||||
/// shorter than those in the CPU data sheet.
|
||||
/// </summary>
|
||||
private static Dictionary<string, string> sShort_enUS = new Dictionary<string, string>() {
|
||||
{ OpName.ADC, "Add With Carry" },
|
||||
{ OpName.AND, "AND Accumulator With Memory" },
|
||||
{ OpName.ASL, "Shift Memory or Accumulator Left" },
|
||||
{ OpName.BCC, "Branch If Carry Clear" },
|
||||
{ OpName.BCS, "Branch If Carry Set" },
|
||||
{ OpName.BEQ, "Branch If Equal" },
|
||||
{ OpName.BIT, "Test Memory Against Accumulator" },
|
||||
{ OpName.BMI, "Branch If Minus" },
|
||||
{ OpName.BNE, "Branch If Not Equal" },
|
||||
{ OpName.BPL, "Branch If Plus" },
|
||||
{ OpName.BRA, "Branch Always" },
|
||||
{ OpName.BRK, "Software Break" },
|
||||
{ OpName.BRL, "Branch Always Long" },
|
||||
{ OpName.BVC, "Branch If Overflow Clear" },
|
||||
{ OpName.BVS, "Branch If Overflow Set" },
|
||||
{ OpName.CLC, "Clear Carry Flag" },
|
||||
{ OpName.CLD, "Clear Decimal Flag" },
|
||||
{ OpName.CLI, "Clear Interrupt Disable Flag" },
|
||||
{ OpName.CLV, "Clear Overflow Flag" },
|
||||
{ OpName.CMP, "Compare Accumulator With Memory" },
|
||||
{ OpName.COP, "Co-Processor" },
|
||||
{ OpName.CPX, "Compare Index X With Memory" },
|
||||
{ OpName.CPY, "Compare Index Y With Memory" },
|
||||
{ OpName.DEC, "Decrement Accumulator" },
|
||||
{ OpName.DEX, "Decrement Index X" },
|
||||
{ OpName.DEY, "Decrement Index Y" },
|
||||
{ OpName.EOR, "XOR Accumulator With Memory" },
|
||||
{ OpName.INC, "Increment Accumulator" },
|
||||
{ OpName.INX, "Increment Index X" },
|
||||
{ OpName.INY, "Increment Index Y" },
|
||||
{ OpName.JMP, "Jump" },
|
||||
{ OpName.JSL, "Jump to Subroutine Long" },
|
||||
{ OpName.JSR, "Jump to Subroutine" },
|
||||
{ OpName.LDA, "Load Accumulator from Memory" },
|
||||
{ OpName.LDX, "Load Index X from Memory" },
|
||||
{ OpName.LDY, "Load Index Y from Memory" },
|
||||
{ OpName.LSR, "Logical Shift Memory or Accumulator Right" },
|
||||
{ OpName.MVN, "Block Move Next" },
|
||||
{ OpName.MVP, "Block Move Previous" },
|
||||
{ OpName.NOP, "No Operation" },
|
||||
{ OpName.ORA, "OR Accumulator With Memory" },
|
||||
{ OpName.PEA, "Push Effective Absolute Address" },
|
||||
{ OpName.PEI, "Push Effective Indirect Address" },
|
||||
{ OpName.PER, "Push Effective Relative Indirect Address" },
|
||||
{ OpName.PHA, "Push Accumulator" },
|
||||
{ OpName.PHB, "Push Data Bank Register" },
|
||||
{ OpName.PHD, "Push Direct Page Register" },
|
||||
{ OpName.PHK, "Push Program Bank Register" },
|
||||
{ OpName.PHP, "Push Processor Status Register" },
|
||||
{ OpName.PHX, "Push Index Register X" },
|
||||
{ OpName.PHY, "Push Index Register Y" },
|
||||
{ OpName.PLA, "Pull Accumulator" },
|
||||
{ OpName.PLB, "Pull Data Bank Register" },
|
||||
{ OpName.PLD, "Pull Direct Page Register" },
|
||||
{ OpName.PLP, "Pull Processor Status Register" },
|
||||
{ OpName.PLX, "Pull Index Register X" },
|
||||
{ OpName.PLY, "Pull Index Register Y" },
|
||||
{ OpName.REP, "Reset Status Bits" },
|
||||
{ OpName.ROL, "Rotate Memory or Accumulator Left" },
|
||||
{ OpName.ROR, "Rotate Memory or Accumulator Right" },
|
||||
{ OpName.RTI, "Return from Interrupt" },
|
||||
{ OpName.RTL, "Return from Subroutine Long" },
|
||||
{ OpName.RTS, "Return from Subroutine" },
|
||||
{ OpName.SBC, "Subtract With Borrow" },
|
||||
{ OpName.SEC, "Set Carry Flag" },
|
||||
{ OpName.SED, "Set Decimal Flag" },
|
||||
{ OpName.SEI, "Set Interrupt Disable Flag" },
|
||||
{ OpName.SEP, "Set Status Bits" },
|
||||
{ OpName.STA, "Store Accumulator to Memory" },
|
||||
{ OpName.STP, "Stop Processor" },
|
||||
{ OpName.STX, "Store Index X to Memory" },
|
||||
{ OpName.STY, "Store Index Y to Memory" },
|
||||
{ OpName.STZ, "Store Zero to Memory" },
|
||||
{ OpName.TAX, "Transfer Accumulator to Index X" },
|
||||
{ OpName.TAY, "Transfer Accumulator to Index Y" },
|
||||
{ OpName.TCD, "Transfer 16-Bit Accumulator to Direct Page Register" },
|
||||
{ OpName.TCS, "Transfer Accumulator to Stack Pointer" },
|
||||
{ OpName.TDC, "Transfer Direct Page Register to 16-Bit Accumulator" },
|
||||
{ OpName.TRB, "Test and Reset Memory Bits" },
|
||||
{ OpName.TSB, "Test and Set Memory Bits" },
|
||||
{ OpName.TSC, "Transfer Stack Pointer to 16-Bit Accumulator" },
|
||||
{ OpName.TSX, "Transfer Stack Pointer to Index X" },
|
||||
{ OpName.TXA, "Transfer Index X to Accumulator" },
|
||||
{ OpName.TXS, "Transfer Index X to Stack Pointer" },
|
||||
{ OpName.TXY, "Transfer Index X to Index Y" },
|
||||
{ OpName.TYA, "Transfer Index Y to Accumulator" },
|
||||
{ OpName.TYX, "Transfer Index Y to Index X" },
|
||||
{ OpName.WAI, "Wait for Interrupt" },
|
||||
{ OpName.WDM, "Future Expansion" },
|
||||
{ OpName.XBA, "Exchange Accumulator B and A" },
|
||||
{ OpName.XCE, "Exchange Carry and Emulation Bits" },
|
||||
|
||||
// MOS 6502 undocumented ops
|
||||
{ OpName.ANC, "AND Accumulator With Value and Set Carry" },
|
||||
{ OpName.ANE, "Transfer Index X to Accumulator and AND" },
|
||||
{ OpName.ARR, "AND and Rotate Right" },
|
||||
{ OpName.ASR, "AND and Shift Right" },
|
||||
{ OpName.DCP, "Decrement and Compare" },
|
||||
{ OpName.DOP, "Double-Byte NOP" },
|
||||
{ OpName.HLT, "Halt CPU" },
|
||||
{ OpName.ISB, "Increment and Subtract" },
|
||||
{ OpName.LAE, "Load Acc, X, and Stack Pointer with Memory AND Stack Pointer" },
|
||||
{ OpName.LAX, "Load Accumulator and Index X" },
|
||||
{ OpName.LXA, "OR, AND, and Transfer to X" },
|
||||
{ OpName.RLA, "Rotate Left and AND" },
|
||||
{ OpName.RRA, "Rotate Right and Add" },
|
||||
{ OpName.SAX, "Store Accumulator AND Index X" }, // AXS
|
||||
{ OpName.SBX, "AND Acc With Index X, Subtract, and Store in X" }, // SAX
|
||||
{ OpName.SHA, "AND Acc With Index X and High Byte, and Store" }, // AXA
|
||||
{ OpName.SHS, "AND Acc with Index X, Transfer to Stack, AND High Byte" }, // TAS
|
||||
{ OpName.SHX, "AND Acc With Index X and High Byte, and Store" }, // XAS
|
||||
{ OpName.SHY, "AND Acc With Index Y and High Byte, and Store" }, // SAY
|
||||
{ OpName.SLO, "Shift Left and OR" },
|
||||
{ OpName.SRE, "Shift right and EOR" },
|
||||
{ OpName.TOP, "Triple-Byte NOP" },
|
||||
|
||||
// WDC 65C02 undocumented
|
||||
{ OpName.LDD, "Load and Discard" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Long descriptions, USA English.
|
||||
/// </summary>
|
||||
private static Dictionary<string, string> sLong_enUS = new Dictionary<string, string>() {
|
||||
{ OpName.ADC,
|
||||
"Adds the accumulator and a value in memory, storing the result in the " +
|
||||
"accumulator. Adds one if the carry is set."
|
||||
},
|
||||
{ OpName.AND,
|
||||
"Performs a bitwise AND of the accumulator with a value in memory, storing " +
|
||||
"the result in the accumulator."
|
||||
},
|
||||
{ OpName.ASL,
|
||||
"Shifts memory or the accumulator one bit left. The low bit is set to zero, " +
|
||||
"and the carry flag receives the high bit."
|
||||
},
|
||||
{ OpName.BCC,
|
||||
"Branches to a relative address if the processor carry flag (C) is zero. " +
|
||||
"Sometimes referred to as Branch If Less Than, or BLT."
|
||||
},
|
||||
{ OpName.BCS,
|
||||
"Branches to a relative address if the processor carry flag (C) is one. " +
|
||||
"Sometimes referred to as Branch If Greater Than or Equal, or BGE."
|
||||
},
|
||||
{ OpName.BEQ,
|
||||
"Branches to a relative address if the processor zero flag (Z) is one."
|
||||
},
|
||||
{ OpName.BIT,
|
||||
"Sets processor flags based on the result of two operations. The N and V flags " +
|
||||
"are set according to bits 7 and 6, and the Z flag is set based on an AND of " +
|
||||
"the accumulator and memory. However, when used with immediate addressing, " +
|
||||
"the N and V flags are not affected."
|
||||
},
|
||||
{ OpName.BMI,
|
||||
"Branches to a relative address if the processor negative flag (N) is one."
|
||||
},
|
||||
{ OpName.BNE,
|
||||
"Branches to a relative address if the processor zero flag (Z) is zero."
|
||||
},
|
||||
{ OpName.BPL,
|
||||
"Branches to a relative address if the processor negative flag (N) is zero."
|
||||
},
|
||||
{ OpName.BRA,
|
||||
"Branches to a relative address."
|
||||
},
|
||||
{ OpName.BRK,
|
||||
"Pushes state onto the stack, and jumps to the software break vector at " +
|
||||
"$fffe-ffff. While this is technically a single-byte instruction, the " +
|
||||
"program counter pushed onto the stack is incremented by two."
|
||||
},
|
||||
{ OpName.BRL,
|
||||
"Branches to a long relative address."
|
||||
},
|
||||
{ OpName.BVC,
|
||||
"Branches to a relative address if the processor overflow flag (V) is zero."
|
||||
},
|
||||
{ OpName.BVS,
|
||||
"Branches to a relative address if the processor overflow flag (V) is one."
|
||||
},
|
||||
{ OpName.CLC,
|
||||
"Sets the processor carry flag (C) to zero."
|
||||
},
|
||||
{ OpName.CLD,
|
||||
"Sets the processor decimal flag (D) to zero."
|
||||
},
|
||||
{ OpName.CLI,
|
||||
"Sets the processor interrupt disable flag (I) to zero."
|
||||
},
|
||||
{ OpName.CLV,
|
||||
"Sets the processor overflow flag (V) to zero."
|
||||
},
|
||||
{ OpName.CMP,
|
||||
"Subtracts the value specified by the operand from the contents of the " +
|
||||
"accumulator. Sets the carry, zero, and negative flags, but does not alter " +
|
||||
"memory or the accumulator."
|
||||
},
|
||||
{ OpName.COP,
|
||||
"Pushes state onto the stack, and jumps to the software interrupt vector at " +
|
||||
"$fff4-fff5."
|
||||
},
|
||||
{ OpName.CPX,
|
||||
"Subtracts the value specified by the operand from the contents of the " +
|
||||
"X register. Sets the carry, zero, and negative flags, but does not alter " +
|
||||
"memory or the X register."
|
||||
},
|
||||
{ OpName.CPY,
|
||||
"Subtracts the value specified by the operand from the contents of the " +
|
||||
"Y register. Sets the carry, zero, and negative flags, but does not alter " +
|
||||
"memory or the Y register."
|
||||
},
|
||||
{ OpName.DEC,
|
||||
"Decrements the contents of the location specified by the operand by one."
|
||||
},
|
||||
{ OpName.DEX,
|
||||
"Decrements the X register by one."
|
||||
},
|
||||
{ OpName.DEY,
|
||||
"Decrements the Y register by one."
|
||||
},
|
||||
{ OpName.EOR,
|
||||
"Performs a bitwise EOR of the accumulator with a value in memory, storing " +
|
||||
"the result in the accumulator."
|
||||
},
|
||||
{ OpName.INC,
|
||||
"Increments the contents of the location specified by the operand by one."
|
||||
},
|
||||
{ OpName.INX,
|
||||
"Increments the X register by one."
|
||||
},
|
||||
{ OpName.INY,
|
||||
"Increments the Y register by one."
|
||||
},
|
||||
{ OpName.JML,
|
||||
"Branches to a long absolute address."
|
||||
},
|
||||
{ OpName.JMP,
|
||||
"Branches to an absolute address."
|
||||
},
|
||||
{ OpName.JSL,
|
||||
"Branches to a long absolute address after pushing the current address onto " +
|
||||
"the stack. The value pushed is the address of the last operand byte."
|
||||
},
|
||||
{ OpName.JSR,
|
||||
"Branches to an absolute address after pushing the current address onto " +
|
||||
"the stack. The value pushed is the address of the last operand byte."
|
||||
},
|
||||
{ OpName.LDA,
|
||||
"Loads the accumulator from memory."
|
||||
},
|
||||
{ OpName.LDX,
|
||||
"Loads the X register from memory."
|
||||
},
|
||||
{ OpName.LDY,
|
||||
"Loads the Y register from memory."
|
||||
},
|
||||
{ OpName.LSR,
|
||||
"Shifts memory or the accumulator one bit right. The high bit is set to zero, " +
|
||||
"and the carry flag receives the low bit."
|
||||
},
|
||||
{ OpName.MVN,
|
||||
"Moves a block of memory, starting from a low address and incrementing. " +
|
||||
"The source and destination addresses are in the X and Y registers, " +
|
||||
"respectively. The accumulator holds the number of bytes to move minus 1, " +
|
||||
"and the source and destination banks are specified by the operands."
|
||||
},
|
||||
{ OpName.MVP,
|
||||
"Moves a block of memory, starting from a high address and decrementing. " +
|
||||
"The source and destination addresses are in the X and Y registers, " +
|
||||
"respectively. The accumulator holds the number of bytes to move minus 1, " +
|
||||
"and the source and destination banks are specified by the operands."
|
||||
},
|
||||
{ OpName.NOP,
|
||||
"No operation."
|
||||
},
|
||||
{ OpName.ORA,
|
||||
"Performs a bitwise OR of the accumulator with a value in memory, storing " +
|
||||
"the result in the accumulator."
|
||||
},
|
||||
{ OpName.PEA,
|
||||
"Pushes the 16-bit operand onto the stack. This always pushes two bytes, " +
|
||||
"regardless of the M/X processor flags."
|
||||
},
|
||||
{ OpName.PEI,
|
||||
"Pushes a 16-bit value from the direct page onto the stack."
|
||||
},
|
||||
{ OpName.PER,
|
||||
"Converts a relative offset to an absolute address, and pushes it onto the stack."
|
||||
},
|
||||
{ OpName.PHA,
|
||||
"Pushes the accumulator onto the stack."
|
||||
},
|
||||
{ OpName.PHB,
|
||||
"Pushes the data bank register onto the stack."
|
||||
},
|
||||
{ OpName.PHD,
|
||||
"Pushes the direct page register onto the stack."
|
||||
},
|
||||
{ OpName.PHK,
|
||||
"Pushes the program bank register onto the stack."
|
||||
},
|
||||
{ OpName.PHP,
|
||||
"Pushes the processor status register onto the stack."
|
||||
},
|
||||
{ OpName.PHX,
|
||||
"Pushes the X register onto the stack."
|
||||
},
|
||||
{ OpName.PHY,
|
||||
"Pushes the Y register onto the stack."
|
||||
},
|
||||
{ OpName.PLA,
|
||||
"Pulls the accumulator off of the stack."
|
||||
},
|
||||
{ OpName.PLB,
|
||||
"Pulls the data bank register off of the stack."
|
||||
},
|
||||
{ OpName.PLD,
|
||||
"Pulls the direct page register off of the stack."
|
||||
},
|
||||
{ OpName.PLP,
|
||||
"Pulls the processor status register off of the stack."
|
||||
},
|
||||
{ OpName.PLX,
|
||||
"Pulls the X register off of the stack."
|
||||
},
|
||||
{ OpName.PLY,
|
||||
"Pulls the Y register off of the stack."
|
||||
},
|
||||
{ OpName.REP,
|
||||
"Sets specific bits in the processor status register to zero."
|
||||
},
|
||||
{ OpName.ROL,
|
||||
"Rotates memory or the accumulator one bit left. The low bit is set to the " +
|
||||
"carry flag, and the carry flag receives the high bit."
|
||||
},
|
||||
{ OpName.ROR,
|
||||
"Rotates memory or the accumulator one bit right. The high bit is set to the " +
|
||||
"carry flag, and the carry flag receives the low bit."
|
||||
},
|
||||
{ OpName.RTI,
|
||||
"Pulls the status register and return address from the stack, and jumps " +
|
||||
"to the exact address pulled (note this is different from RTL/RTS)."
|
||||
},
|
||||
{ OpName.RTL,
|
||||
"Pulls the 24-bit return address from the stack, increments it, and jumps to it."
|
||||
},
|
||||
{ OpName.RTS,
|
||||
"Pulls the 16-bit return address from the stack, increments it, and jumps to it."
|
||||
},
|
||||
{ OpName.SBC,
|
||||
"Subtracts the value specified by the operand from the contents of the " +
|
||||
"accumulator, and leaves the result in the accumulator. Sets the carry, " +
|
||||
"zero, and negative flags."
|
||||
},
|
||||
{ OpName.SEC,
|
||||
"Sets the processor carry flag (C) to one."
|
||||
},
|
||||
{ OpName.SED,
|
||||
"Sets the processor decimal flag (D) to one."
|
||||
},
|
||||
{ OpName.SEI,
|
||||
"Sets the processor interrupt disable flag (I) to one."
|
||||
},
|
||||
{ OpName.SEP,
|
||||
"Sets specific bits in the processor status register to one."
|
||||
},
|
||||
{ OpName.STA,
|
||||
"Stores the value in the accumulator into memory."
|
||||
},
|
||||
{ OpName.STP,
|
||||
"Stops the processor until a CPU reset occurs."
|
||||
},
|
||||
{ OpName.STX,
|
||||
"Stores the value in the X register into memory."
|
||||
},
|
||||
{ OpName.STY,
|
||||
"Stores the value in the Y register into memory."
|
||||
},
|
||||
{ OpName.STZ,
|
||||
"Stores zero into memory."
|
||||
},
|
||||
{ OpName.TAX,
|
||||
"Transfers the contents of the accumulator to the X register."
|
||||
},
|
||||
{ OpName.TAY,
|
||||
"Transfers the contents of the accumulator to the Y register."
|
||||
},
|
||||
{ OpName.TCD,
|
||||
"Transfers the 16-bit accumulator to the direct page register."
|
||||
},
|
||||
{ OpName.TCS,
|
||||
"Transfers the 16-bit accumulator to the stack pointer register."
|
||||
},
|
||||
{ OpName.TDC,
|
||||
"Transfers the direct page register to the 16-bit accumulator."
|
||||
},
|
||||
{ OpName.TRB,
|
||||
"Logically ANDs the complement of the value in the accumulator with a value " +
|
||||
"in memory, and stores it in memory. This can be used to clear specific bits " +
|
||||
"in memory."
|
||||
},
|
||||
{ OpName.TSB,
|
||||
"Logically ORs the value in the accumulator with a value in memory, and " +
|
||||
"stores it in memory. This can be used to set specific bits in memory."
|
||||
},
|
||||
{ OpName.TSC,
|
||||
"Transfers the stack pointer register to the 16-bit accumulator."
|
||||
},
|
||||
{ OpName.TSX,
|
||||
"Transfers the stack pointer register to the X register."
|
||||
},
|
||||
{ OpName.TXA,
|
||||
"Transfers the X register to the accumulator."
|
||||
},
|
||||
{ OpName.TXS,
|
||||
"Transfers the X register to the stack pointer register."
|
||||
},
|
||||
{ OpName.TXY,
|
||||
"Transfers the X register to the Y register."
|
||||
},
|
||||
{ OpName.TYA,
|
||||
"Transfers the Y register to the accumulator."
|
||||
},
|
||||
{ OpName.TYX,
|
||||
"Transfers the Y register to the X register."
|
||||
},
|
||||
{ OpName.WAI,
|
||||
"Stalls the processor until an interrupt is received. If the interrupt " +
|
||||
"disable flag (I) is set to one, execution will continue with the next " +
|
||||
"instruction rather than calling through an interrupt vector."
|
||||
},
|
||||
{ OpName.WDM,
|
||||
"Reserved for future expansion. (Behaves as a two-byte NOP.)"
|
||||
},
|
||||
{ OpName.XBA,
|
||||
"Swaps the high and low bytes in the 16-bit accumulator. Sometimes referred " +
|
||||
"to as SWA."
|
||||
},
|
||||
{ OpName.XCE,
|
||||
"Exchanges carry and emulation bits."
|
||||
},
|
||||
|
||||
//
|
||||
// 6502 undocumented instructions.
|
||||
//
|
||||
// References:
|
||||
// http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes
|
||||
// http://nesdev.com/undocumented_opcodes.txt
|
||||
//
|
||||
{ OpName.ANC,
|
||||
"AND byte with accumulator. If result is negative then carry is set." +
|
||||
"\r\n\r\nAlt mnemonic: AAC"
|
||||
},
|
||||
{ OpName.ANE,
|
||||
"Transfer X register to accumulator, then AND accumulator with value." +
|
||||
"\r\n\r\nAlt mnemonic: XAA"
|
||||
},
|
||||
{ OpName.ARR,
|
||||
"AND byte with accumulator, then rotate one bit right. Equivalent to " +
|
||||
"AND + ROR."
|
||||
},
|
||||
{ OpName.ASR,
|
||||
"AND byte with accumulator, then shift right one bit. Equivalent to AND + LSR." +
|
||||
"\r\n\r\nAlt mnemonic: ALR"
|
||||
},
|
||||
{ OpName.DCP,
|
||||
"Decrement memory location, then compare result to accumulator. Equivalent " +
|
||||
"to DEC + CMP." +
|
||||
"\r\n\r\nAlt mnemonic: DCM"
|
||||
},
|
||||
{ OpName.DOP,
|
||||
"Double-byte no-operation." +
|
||||
"\r\n\r\nAlt mnemonic: NOP / SKB"
|
||||
},
|
||||
{ OpName.HLT,
|
||||
"Crash the CPU, halting execution and ignoring interrupts." +
|
||||
"\r\n\r\nAlt mnemonic: KIL / JAM"
|
||||
},
|
||||
{ OpName.ISB,
|
||||
"Increment memory, then subtract memory from accumulator with borrow. " +
|
||||
"Equivalent to INC + SBC." +
|
||||
"\r\n\r\nAlt mnemonic: ISC / INS"
|
||||
},
|
||||
{ OpName.LAE,
|
||||
"AND memory with stack pointer, then transfer result to accumulator, " +
|
||||
"X register, and stack pointer. (Note: possibly unreliable.)" +
|
||||
"\r\n\r\nAlt mnemonic: LAR / LAS"
|
||||
},
|
||||
{ OpName.LAX,
|
||||
"Load accumulator and X register from memory. Equivalent to LDA + LDX."
|
||||
},
|
||||
{ OpName.LXA,
|
||||
"ORs accumulator with a value, ANDs result with immediate value, then stores " +
|
||||
"the result in accumulator and X register." +
|
||||
"Equivalent to ORA + AND + TAX." +
|
||||
"\r\n\r\nAlt mnemonic: ATX / OAL"
|
||||
},
|
||||
{ OpName.RLA,
|
||||
"Rotate memory one bit left, then AND accumulator with memory. Equivalent " +
|
||||
"to ROL + AND."
|
||||
},
|
||||
{ OpName.RRA,
|
||||
"Rotate memory one bit right, then add accumulator to memory with carry. " +
|
||||
"Equivalent to ROR + ADC."
|
||||
},
|
||||
{ OpName.SAX,
|
||||
"AND X register with accumulator, without changing the contents of either " +
|
||||
"register, subtract an immediate value, then store result in X register." +
|
||||
"\r\n\r\nAlt mnemonic: AAX / AXS"
|
||||
},
|
||||
{ OpName.SBX,
|
||||
"AND X register with accumulator and transfer to X register, then " +
|
||||
"subtract byte from X register without borrow." +
|
||||
"\r\n\r\nAlt mnemonic: AXS / SAX"
|
||||
},
|
||||
{ OpName.SHA,
|
||||
"AND X register with accumulator, then AND result with 7 and store." +
|
||||
"\r\n\r\nAlt mnemonic: AXA"
|
||||
},
|
||||
{ OpName.SHS,
|
||||
"AND X register with accumulator, without changing the contents of either" +
|
||||
"register, and transfer to stack pointer. Then " +
|
||||
"AND stack pointer with high byte of operand + 1." +
|
||||
"\r\n\r\nAlt mnemonic: XAS / TAS"
|
||||
},
|
||||
{ OpName.SHX,
|
||||
"AND X register with the high byte of the argument + 1, and store the result." +
|
||||
"\r\n\r\nAlt mnemonic: SXA / XAS"
|
||||
},
|
||||
{ OpName.SHY,
|
||||
"AND Y register with the high byte of the argument + 1, and store the result." +
|
||||
"\r\n\r\nAlt mnemonic: SYA / SAY"
|
||||
},
|
||||
{ OpName.SLO,
|
||||
"Shift memory left one bit, then OR accumulator with memory. Equivalent to " +
|
||||
"ASL + ORA." +
|
||||
"\r\n\r\nAlt mnemonic: ASO"
|
||||
},
|
||||
{ OpName.SRE,
|
||||
"Shift memory right one bit, then EOR accumulator with memory. Equivalent to " +
|
||||
"LSR + EOR." +
|
||||
"\r\n\r\nAlt mnemonic: LSE"
|
||||
},
|
||||
{ OpName.TOP,
|
||||
"Triple-byte no-operation. This actually performs a load." +
|
||||
"\r\n\r\nAlt mnemonic: NOP / SKW"
|
||||
},
|
||||
|
||||
//
|
||||
// 65C02 undocumented instructions.
|
||||
//
|
||||
{ OpName.LDD,
|
||||
"Load and Discard. Usually a no-op, but the activity on the address bus " +
|
||||
"can affect memory-mapped I/O."
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Address mode short descriptions, USA English.
|
||||
/// </summary>
|
||||
private static Dictionary<OpDef.AddressMode, string> sAddrMode_enUS =
|
||||
new Dictionary<OpDef.AddressMode, string>() {
|
||||
{ OpDef.AddressMode.Abs, "Absolute" },
|
||||
{ OpDef.AddressMode.AbsInd, "Absolute Indirect" },
|
||||
{ OpDef.AddressMode.AbsIndLong, "Absolute Indirect Long" },
|
||||
{ OpDef.AddressMode.AbsIndexX, "Absolute Indexed X" },
|
||||
{ OpDef.AddressMode.AbsIndexXInd, "Absolute Indexed X Indirect" },
|
||||
{ OpDef.AddressMode.AbsIndexXLong, "Absolute Indexed X Long" },
|
||||
{ OpDef.AddressMode.AbsIndexY, "Absolute Indexed Y" },
|
||||
{ OpDef.AddressMode.AbsLong, "Absolute Long" },
|
||||
{ OpDef.AddressMode.Acc, "Accumulator" },
|
||||
{ OpDef.AddressMode.BlockMove, "Block Move" },
|
||||
{ OpDef.AddressMode.DP, "Direct Page" },
|
||||
{ OpDef.AddressMode.DPInd, "Direct Page Indirect" },
|
||||
{ OpDef.AddressMode.DPIndIndexY, "Direct Page Indirect Indexed Y" },
|
||||
{ OpDef.AddressMode.DPIndIndexYLong, "Direct Page Indirect Indexed Y Long" },
|
||||
{ OpDef.AddressMode.DPIndLong, "Direct Page Indirect Long" },
|
||||
{ OpDef.AddressMode.DPIndexX, "Direct Page Indexed X" },
|
||||
{ OpDef.AddressMode.DPIndexXInd, "Direct Page Indexed X Indirect" },
|
||||
{ OpDef.AddressMode.DPIndexY, "Direct Page Indexed Y" },
|
||||
{ OpDef.AddressMode.Imm, "Immediate" },
|
||||
{ OpDef.AddressMode.ImmLongA, "Immediate" },
|
||||
{ OpDef.AddressMode.ImmLongXY, "Immediate" },
|
||||
{ OpDef.AddressMode.Implied, "" },
|
||||
{ OpDef.AddressMode.PCRel, "PC Relative" },
|
||||
{ OpDef.AddressMode.PCRelLong, "PC Relative Long" },
|
||||
{ OpDef.AddressMode.StackAbs, "Stack Absolute" },
|
||||
{ OpDef.AddressMode.StackDPInd, "Stack Direct Page Indirect" },
|
||||
{ OpDef.AddressMode.StackInt, "" },
|
||||
{ OpDef.AddressMode.StackPCRelLong, "Stack PC Relative Long" },
|
||||
{ OpDef.AddressMode.StackPull, "" },
|
||||
{ OpDef.AddressMode.StackPush, "" },
|
||||
{ OpDef.AddressMode.StackRTI, "" },
|
||||
{ OpDef.AddressMode.StackRTL, "" },
|
||||
{ OpDef.AddressMode.StackRTS, "" },
|
||||
{ OpDef.AddressMode.StackRel, "" },
|
||||
{ OpDef.AddressMode.StackRelIndIndexY, "Stack Relative Indirect Index Y" },
|
||||
{ OpDef.AddressMode.WDM, "" }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Cycle modifier descriptions. These are intended to be very terse.
|
||||
/// </summary>
|
||||
private static Dictionary<OpDef.CycleMod, string> sCycleMod_enUS =
|
||||
new Dictionary<OpDef.CycleMod, string>() {
|
||||
{ OpDef.CycleMod.OneIfM0, "+1 if M=0" },
|
||||
{ OpDef.CycleMod.TwoIfM0, "+2 if M=0" },
|
||||
{ OpDef.CycleMod.OneIfX0, "+1 if X=0" },
|
||||
{ OpDef.CycleMod.OneIfDpNonzero, "+1 if DL != 0" },
|
||||
{ OpDef.CycleMod.OneIfIndexPage, "+1 if index across page" },
|
||||
{ OpDef.CycleMod.OneIfD1, "+1 if D=1 on 65C02" },
|
||||
{ OpDef.CycleMod.OneIfBranchTaken, "+1 if branch taken" },
|
||||
{ OpDef.CycleMod.OneIfBranchPage, "+1 if branch across page unless E=0" },
|
||||
{ OpDef.CycleMod.OneIfE0, "+1 if E=0" },
|
||||
{ OpDef.CycleMod.OneIf65C02, "+1 if 65C02" },
|
||||
{ OpDef.CycleMod.MinusOneIfNoPage, "-1 if 65C02 and not across page" },
|
||||
{ OpDef.CycleMod.BlockMove, "+7 per byte" },
|
||||
};
|
||||
}
|
||||
}
|
148
Asm65/OpName.cs
Normal file
148
Asm65/OpName.cs
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
|
||||
namespace Asm65 {
|
||||
/// <summary>
|
||||
/// String constants for opcodes. These are not (and should not be) localized. They
|
||||
/// must be lower-case.
|
||||
/// </summary>
|
||||
public static class OpName {
|
||||
// NOTE: these all happen to be three characters, but I don't think we want to
|
||||
// guarantee that. On the 65816 some mnemonics are extended (e.g. LDAL for LDA with
|
||||
// a 24-bit operand), but that's assembler-specific and handled elsewhere.
|
||||
public const string Unknown = "???";
|
||||
public const string ADC = "adc";
|
||||
public const string AND = "and";
|
||||
public const string ASL = "asl";
|
||||
public const string BCC = "bcc";
|
||||
public const string BCS = "bcs";
|
||||
public const string BEQ = "beq";
|
||||
public const string BIT = "bit";
|
||||
public const string BMI = "bmi";
|
||||
public const string BNE = "bne";
|
||||
public const string BPL = "bpl";
|
||||
public const string BRA = "bra";
|
||||
public const string BRK = "brk";
|
||||
public const string BRL = "brl";
|
||||
public const string BVC = "bvc";
|
||||
public const string BVS = "bvs";
|
||||
public const string CLC = "clc";
|
||||
public const string CLD = "cld";
|
||||
public const string CLI = "cli";
|
||||
public const string CLV = "clv";
|
||||
public const string CMP = "cmp";
|
||||
public const string COP = "cop";
|
||||
public const string CPX = "cpx";
|
||||
public const string CPY = "cpy";
|
||||
public const string DEC = "dec";
|
||||
public const string DEX = "dex";
|
||||
public const string DEY = "dey";
|
||||
public const string EOR = "eor";
|
||||
public const string INC = "inc";
|
||||
public const string INX = "inx";
|
||||
public const string INY = "iny";
|
||||
public const string JML = "jml";
|
||||
public const string JMP = "jmp";
|
||||
public const string JSL = "jsl";
|
||||
public const string JSR = "jsr";
|
||||
public const string LDA = "lda";
|
||||
public const string LDX = "ldx";
|
||||
public const string LDY = "ldy";
|
||||
public const string LSR = "lsr";
|
||||
public const string MVN = "mvn";
|
||||
public const string MVP = "mvp";
|
||||
public const string NOP = "nop";
|
||||
public const string ORA = "ora";
|
||||
public const string PEA = "pea";
|
||||
public const string PEI = "pei";
|
||||
public const string PER = "per";
|
||||
public const string PHA = "pha";
|
||||
public const string PHB = "phb";
|
||||
public const string PHD = "phd";
|
||||
public const string PHK = "phk";
|
||||
public const string PHP = "php";
|
||||
public const string PHX = "phx";
|
||||
public const string PHY = "phy";
|
||||
public const string PLA = "pla";
|
||||
public const string PLB = "plb";
|
||||
public const string PLD = "pld";
|
||||
public const string PLP = "plp";
|
||||
public const string PLX = "plx";
|
||||
public const string PLY = "ply";
|
||||
public const string REP = "rep";
|
||||
public const string ROL = "rol";
|
||||
public const string ROR = "ror";
|
||||
public const string RTI = "rti";
|
||||
public const string RTL = "rtl";
|
||||
public const string RTS = "rts";
|
||||
public const string SBC = "sbc";
|
||||
public const string SEC = "sec";
|
||||
public const string SED = "sed";
|
||||
public const string SEI = "sei";
|
||||
public const string SEP = "sep";
|
||||
public const string STA = "sta";
|
||||
public const string STP = "stp";
|
||||
public const string STX = "stx";
|
||||
public const string STY = "sty";
|
||||
public const string STZ = "stz";
|
||||
public const string TAX = "tax";
|
||||
public const string TAY = "tay";
|
||||
public const string TCD = "tcd";
|
||||
public const string TCS = "tcs";
|
||||
public const string TDC = "tdc";
|
||||
public const string TRB = "trb";
|
||||
public const string TSB = "tsb";
|
||||
public const string TSC = "tsc";
|
||||
public const string TSX = "tsx";
|
||||
public const string TXA = "txa";
|
||||
public const string TXS = "txs";
|
||||
public const string TXY = "txy";
|
||||
public const string TYA = "tya";
|
||||
public const string TYX = "tyx";
|
||||
public const string WAI = "wai";
|
||||
public const string WDM = "wdm";
|
||||
public const string XBA = "xba";
|
||||
public const string XCE = "xce";
|
||||
|
||||
// Undocumented 6502 instructions.
|
||||
public const string ANC = "anc";
|
||||
public const string ANE = "ane";
|
||||
public const string ARR = "arr";
|
||||
public const string ASR = "asr";
|
||||
public const string DCP = "dcp";
|
||||
public const string DOP = "dop";
|
||||
public const string HLT = "hlt";
|
||||
public const string ISB = "isb";
|
||||
public const string LAE = "lae";
|
||||
public const string LAX = "lax";
|
||||
public const string LXA = "lxa";
|
||||
public const string RLA = "rla";
|
||||
public const string RRA = "rra";
|
||||
public const string SAX = "sax";
|
||||
public const string SBX = "sbx";
|
||||
public const string SHA = "sha";
|
||||
public const string SHS = "shs";
|
||||
public const string SHX = "shx";
|
||||
public const string SHY = "shy";
|
||||
public const string SLO = "slo";
|
||||
public const string SRE = "sre";
|
||||
public const string TOP = "top";
|
||||
|
||||
// Undocumented 65C02 instructions.
|
||||
public const string LDD = "ldd";
|
||||
}
|
||||
}
|
73
Asm65/Properties/Resources.Designer.cs
generated
Normal file
73
Asm65/Properties/Resources.Designer.cs
generated
Normal file
@ -0,0 +1,73 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Asm65.Properties {
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Asm65.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Fourth time.
|
||||
/// </summary>
|
||||
public static string TEST_STRING {
|
||||
get {
|
||||
return ResourceManager.GetString("TEST_STRING", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
Asm65/Properties/Resources.resx
Normal file
123
Asm65/Properties/Resources.resx
Normal file
@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="TEST_STRING" xml:space="preserve">
|
||||
<value>Fourth time</value>
|
||||
</data>
|
||||
</root>
|
328
Asm65/StatusFlags.cs
Normal file
328
Asm65/StatusFlags.cs
Normal file
@ -0,0 +1,328 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Asm65 {
|
||||
/// <summary>
|
||||
/// Status flag holder. Each flag may be known to be zero, known to be one, or
|
||||
/// hold an indeterminate value (represented as a negative number).
|
||||
///
|
||||
/// For the 65802/65816, we also keep track of the E flag (emulation bit), even though
|
||||
/// that's not actually held in the P register.
|
||||
///
|
||||
/// Note this is a value type, not a reference type.
|
||||
///
|
||||
/// The default value is UNSPECIFIED for all bits.
|
||||
/// </summary>
|
||||
public struct StatusFlags {
|
||||
private TriState16 mState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Flag bits, from processor status register definition. The 'e' (emulation)
|
||||
/// flag from the 65816 is tacked onto the end.
|
||||
///
|
||||
/// The enumerated value matches the bit number in the P register.
|
||||
/// </summary>
|
||||
public enum FlagBits {
|
||||
C = 0,
|
||||
Z = 1,
|
||||
I = 2,
|
||||
D = 3,
|
||||
B = 4, // all CPUs except 65802/65816 in native mode
|
||||
X = 4, // 65802/65816 in native mode
|
||||
M = 5, // 65802/65816 in native mode (always 1 on other CPUs)
|
||||
V = 6,
|
||||
N = 7,
|
||||
E = 8 // not actually part of P-reg; accessible only through XCE
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default value (all flags UNSPECIFIED). A newly-created array of StatusFlags will
|
||||
/// all have this value.
|
||||
/// </summary>
|
||||
public static readonly StatusFlags DefaultValue =
|
||||
new StatusFlags { mState = new TriState16(0, 0) };
|
||||
|
||||
/// <summary>
|
||||
/// All flags are INDETERMINATE.
|
||||
/// </summary>
|
||||
public static readonly StatusFlags AllIndeterminate =
|
||||
new StatusFlags() { mState = new TriState16(0x01ff, 0x01ff) };
|
||||
|
||||
public int C {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.C);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.C);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.C);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.C);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.C);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Z {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.Z);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.Z);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.Z);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.Z);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int I {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.I);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.I);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.I);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.I);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.I);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int D {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.D);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.D);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.D);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.D);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int X {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.X);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.X);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.X);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.X);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.X);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int M {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.M);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.M);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.M);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.M);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.M);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int V {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.V);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.V);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.V);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.V);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.V);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int N {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.N);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.N);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.N);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.N);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.N);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int E {
|
||||
get {
|
||||
return mState.GetBit((int)FlagBits.E);
|
||||
}
|
||||
set {
|
||||
if (value == 0) {
|
||||
mState.SetZero((int)FlagBits.E);
|
||||
} else if (value == 1) {
|
||||
mState.SetOne((int)FlagBits.E);
|
||||
} else if (value == TriState16.UNSPECIFIED) {
|
||||
mState.SetUnspecified((int)FlagBits.E);
|
||||
} else {
|
||||
mState.SetIndeterminate((int)FlagBits.E);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetBit(FlagBits index) {
|
||||
return mState.GetBit((int) index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current processor status flags are configured for a short
|
||||
/// (8-bit) accumulator.
|
||||
/// </summary>
|
||||
public bool ShortM {
|
||||
get {
|
||||
// E==1 --> true (we're in emulation mode)
|
||||
// E==0 || E==? : native / assumed native
|
||||
// M==1 || M==? --> true (native mode, configured short or assumed short)
|
||||
// M==0 --> false (native mode, configured long)
|
||||
return (E == 1) || (M != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current processor status flags are configured for short
|
||||
/// (8-bit) X/Y registers.
|
||||
/// </summary>
|
||||
public bool ShortX {
|
||||
get {
|
||||
// (same logic as ShortM)
|
||||
return (E == 1) || (X != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access the value as a single integer. Used for serialization.
|
||||
/// </summary>
|
||||
public int AsInt {
|
||||
get {
|
||||
return mState.AsInt;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the value from an integer. Used for serialization.
|
||||
/// </summary>
|
||||
public static StatusFlags FromInt(int value) {
|
||||
if ((value & ~0x01ff01ff) != 0) {
|
||||
throw new InvalidOperationException("Bad StatusFlags value " +
|
||||
value.ToString("x8"));
|
||||
}
|
||||
StatusFlags newFlags = new StatusFlags();
|
||||
newFlags.mState.AsInt = value;
|
||||
return newFlags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge a set of status flags into this one.
|
||||
/// </summary>
|
||||
public void Merge(StatusFlags other) {
|
||||
mState.Merge(other.mState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies flags, overwriting existing values. This will set one or more flags
|
||||
/// to 0, 1, or indeterminate. Unspecified (0/0) values have no effect.
|
||||
///
|
||||
/// This is useful when merging "overrides" in.
|
||||
/// </summary>
|
||||
public void Apply(StatusFlags overrides) {
|
||||
mState.Apply(overrides.mState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the flags.
|
||||
/// </summary>
|
||||
/// <param name="showMXE">If set, include the 'E' flag, and show M/X.</param>
|
||||
public string ToString(bool showMXE) {
|
||||
StringBuilder sb = new StringBuilder(showMXE ? 10 : 8);
|
||||
sb.Append("-?nN"[N + 2]);
|
||||
sb.Append("-?vV"[V + 2]);
|
||||
sb.Append(showMXE ? "-?mM"[M + 2] : '-');
|
||||
sb.Append(showMXE ? "-?xX"[X + 2] : '-');
|
||||
sb.Append("-?dD"[D + 2]);
|
||||
sb.Append("-?iI"[I + 2]);
|
||||
sb.Append("-?zZ"[Z + 2]);
|
||||
sb.Append("-?cC"[C + 2]);
|
||||
if (showMXE) {
|
||||
sb.Append(' ');
|
||||
sb.Append("-?eE"[E + 2]);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public static bool operator ==(StatusFlags a, StatusFlags b) {
|
||||
return a.mState == b.mState;
|
||||
}
|
||||
public static bool operator !=(StatusFlags a, StatusFlags b) {
|
||||
return !(a == b);
|
||||
}
|
||||
public override bool Equals(object obj) {
|
||||
return obj is StatusFlags && this == (StatusFlags)obj;
|
||||
}
|
||||
public override int GetHashCode() {
|
||||
return mState.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return ToString(true);
|
||||
// + " [" + mState.ToString() + "]"
|
||||
}
|
||||
}
|
||||
}
|
172
Asm65/TriState16.cs
Normal file
172
Asm65/TriState16.cs
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Asm65 {
|
||||
/// <summary>
|
||||
/// A value with 16 tri-state bits.
|
||||
/// </summary>
|
||||
public struct TriState16 {
|
||||
/// <summary>
|
||||
/// Two 16-bit values. The low 16 bits indicate that bit N is zero, the high
|
||||
/// 16 bits indicate that bit N is one. If neither or both are set, the value
|
||||
/// is undetermined and could be either.
|
||||
///
|
||||
/// While 0/0 and 1/1 both represent an indeterminate value, they're not equivalent.
|
||||
/// When two values are merged, a 0/0 bit has no effect on the result, while
|
||||
/// a 1/1 bit will force the merged bit to be indeterminate. We use UNSPECIFIED for
|
||||
/// 0/0 and INDETERMINATE for 1/1.
|
||||
///
|
||||
/// The default value for new instances is UNSPECIFIED for all bits.
|
||||
/// </summary>
|
||||
private ushort mZero, mOne;
|
||||
|
||||
public const int INDETERMINATE = -1;
|
||||
public const int UNSPECIFIED = -2;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor; sets initial zero/one values.
|
||||
/// </summary>
|
||||
/// <param name="zeroes">16-bit value, with bits set for each known-zero.</param>
|
||||
/// <param name="ones">16-bit value, with bits set for each known-one.</param>
|
||||
public TriState16(ushort zeroes, ushort ones) {
|
||||
mZero = zeroes;
|
||||
mOne = ones;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access the value as a single integer. Used for serialization.
|
||||
/// </summary>
|
||||
public int AsInt {
|
||||
get {
|
||||
return (int)mZero | ((int)mOne << 16);
|
||||
}
|
||||
set {
|
||||
mZero = (ushort) value;
|
||||
mOne = (ushort) (value >> 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets bit N to zero.
|
||||
/// </summary>
|
||||
public void SetZero(int bit) {
|
||||
Debug.Assert(bit >= 0 && bit < 16);
|
||||
|
||||
// clear 1-flag, set 0-flag
|
||||
//mValue = (mValue & ~(1U << (bit + 16))) | (1U << bit);
|
||||
mZero |= (ushort) (1U << bit);
|
||||
mOne &= (ushort) ~(1U << bit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets bit N to one.
|
||||
/// </summary>
|
||||
public void SetOne(int bit) {
|
||||
Debug.Assert(bit >= 0 && bit < 16);
|
||||
|
||||
// clear 0-flag, set 1-flag
|
||||
//mValue = (mValue & ~(1U << bit)) | (1U << (bit + 16));
|
||||
mZero &= (ushort)~(1U << bit);
|
||||
mOne |= (ushort)(1U << bit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets bit N to indeterminate.
|
||||
/// </summary>
|
||||
public void SetIndeterminate(int bit) {
|
||||
Debug.Assert(bit >= 0 && bit < 16);
|
||||
|
||||
// set both flags
|
||||
mZero |= (ushort)(1U << bit);
|
||||
mOne |= (ushort)(1U << bit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets bit N to unspecified.
|
||||
/// </summary>
|
||||
public void SetUnspecified(int bit) {
|
||||
Debug.Assert(bit >= 0 && bit < 16);
|
||||
|
||||
// clear both flags
|
||||
mZero &= (ushort)~(1U << bit);
|
||||
mOne &= (ushort)~(1U << bit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges bit states.
|
||||
/// </summary>
|
||||
/// <param name="other">Value to merge in.</param>
|
||||
public void Merge(TriState16 other) {
|
||||
//mValue |= other.mValue;
|
||||
mZero |= other.mZero;
|
||||
mOne |= other.mOne;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a set of bits to an existing set, overriding any bits that aren't set
|
||||
/// to "unspecified" in the input.
|
||||
/// </summary>
|
||||
public void Apply(TriState16 overrides) {
|
||||
ushort mask = (ushort) ~(overrides.mZero | overrides.mOne);
|
||||
mZero = (ushort)((mZero & mask) | overrides.mZero);
|
||||
mOne = (ushort)((mOne & mask) | overrides.mOne);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns 0, 1, -1, or -2 depending on whether the specified bit is 0, 1,
|
||||
/// indeterminate, or unspecified.
|
||||
/// </summary>
|
||||
public int GetBit(int bit) {
|
||||
bool zero = ((mZero >> bit) & 0x01) != 0;
|
||||
bool one = ((mOne >> bit) & 0x01) != 0;
|
||||
if (zero ^ one) {
|
||||
// Only one of the bits is set.
|
||||
if (one) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// Both or neither are set.
|
||||
if (zero) {
|
||||
return INDETERMINATE;
|
||||
} else {
|
||||
return UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static bool operator ==(TriState16 a, TriState16 b) {
|
||||
return a.mZero == b.mZero && a.mOne == b.mOne;
|
||||
}
|
||||
public static bool operator !=(TriState16 a, TriState16 b) {
|
||||
return !(a == b);
|
||||
}
|
||||
public override bool Equals(object obj) {
|
||||
return obj is TriState16 && this == (TriState16)obj;
|
||||
}
|
||||
public override int GetHashCode() {
|
||||
return (mOne << 16) | mZero;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return mZero.ToString("x4") + "-" + mOne.ToString("x4");
|
||||
}
|
||||
}
|
||||
}
|
6
CodeLab/App.config
Normal file
6
CodeLab/App.config
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
</configuration>
|
84
CodeLab/CodeLab.csproj
Normal file
84
CodeLab/CodeLab.csproj
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{3B66ABD8-7129-4D2B-B48E-B03FEC835CE1}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>CodeLab</RootNamespace>
|
||||
<AssemblyName>CodeLab</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="MainWindow.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MainWindow.Designer.cs">
|
||||
<DependentUpon>MainWindow.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="MainWindow.resx">
|
||||
<DependentUpon>MainWindow.cs</DependentUpon>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
</Compile>
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
142
CodeLab/Form1.resx
Normal file
142
CodeLab/Form1.resx
Normal file
@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>132, 17</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="SuperSplitButton.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
|
||||
YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
|
||||
0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
|
||||
bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
|
||||
VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
|
||||
c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
|
||||
Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
|
||||
mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
|
||||
kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
|
||||
TgDQASA1MVpwzwAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
46
CodeLab/MainWindow.Designer.cs
generated
Normal file
46
CodeLab/MainWindow.Designer.cs
generated
Normal file
@ -0,0 +1,46 @@
|
||||
namespace WorkBench
|
||||
{
|
||||
partial class MainWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// MainWindow
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(284, 261);
|
||||
this.Name = "MainWindow";
|
||||
this.Text = "6502bench";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
20
CodeLab/MainWindow.cs
Normal file
20
CodeLab/MainWindow.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace WorkBench
|
||||
{
|
||||
public partial class MainWindow : Form
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
120
CodeLab/MainWindow.resx
Normal file
120
CodeLab/MainWindow.resx
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
22
CodeLab/Program.cs
Normal file
22
CodeLab/Program.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace WorkBench
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new MainWindow());
|
||||
}
|
||||
}
|
||||
}
|
36
CodeLab/Properties/AssemblyInfo.cs
Normal file
36
CodeLab/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("WorkBench")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("WorkBench")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("3b66abd8-7129-4d2b-b48e-b03fec835ce1")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
63
CodeLab/Properties/Resources.Designer.cs
generated
Normal file
63
CodeLab/Properties/Resources.Designer.cs
generated
Normal file
@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace CodeLab.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CodeLab.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
CodeLab/Properties/Resources.resx
Normal file
117
CodeLab/Properties/Resources.resx
Normal file
@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
26
CodeLab/Properties/Settings.Designer.cs
generated
Normal file
26
CodeLab/Properties/Settings.Designer.cs
generated
Normal file
@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace CodeLab.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.6.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
CodeLab/Properties/Settings.settings
Normal file
7
CodeLab/Properties/Settings.settings
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
1
CodeLab/README.md
Normal file
1
CodeLab/README.md
Normal file
@ -0,0 +1 @@
|
||||
This is just a place-holder. Nothing to see here.
|
109
CommonUtil/CRC32.cs
Normal file
109
CommonUtil/CRC32.cs
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// Compute a standard CRC-32 (polynomial 0xedb88320, or
|
||||
/// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1).
|
||||
/// </summary>
|
||||
public static class CRC32 {
|
||||
private static readonly uint[] sTable = ComputeTable();
|
||||
|
||||
private const uint INVERT = 0xffffffff;
|
||||
|
||||
/// <summary>
|
||||
/// Generates 256-entry CRC table.
|
||||
/// </summary>
|
||||
/// <returns>Table.</returns>
|
||||
private static uint[] ComputeTable() {
|
||||
uint[] table = new uint[256];
|
||||
|
||||
uint poly = 0xedb88320;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
uint val = (uint) i;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
val = (val & 1) != 0 ? poly ^ (val >> 1) : val >> 1;
|
||||
}
|
||||
table[i] = val;
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a CRC on part of a buffer of data.
|
||||
/// </summary>
|
||||
/// <param name="crc">Previously computed CRC value. Initially zero.</param>
|
||||
/// <param name="buffer">Data to compute CRC on.</param>
|
||||
/// <param name="offset">Start offset within buffer.</param>
|
||||
/// <param name="count">Number of bytes to process.</param>
|
||||
/// <returns>New CRC value.</returns>
|
||||
public static uint OnBuffer(uint crc, byte[] buffer, int offset, int count) {
|
||||
crc = crc ^ INVERT;
|
||||
while (count-- != 0) {
|
||||
crc = sTable[(crc ^ buffer[offset]) & 0xff] ^ (crc >> 8);
|
||||
offset++;
|
||||
}
|
||||
return crc ^ INVERT;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a CRC on a buffer of data.
|
||||
/// </summary>
|
||||
/// <param name="crc">Previously computed CRC value. Initially zero.</param>
|
||||
/// <param name="buffer">Data to compute CRC on.</param>
|
||||
/// <returns>New CRC value.</returns>
|
||||
public static uint OnWholeBuffer(uint crc, byte[] buffer) {
|
||||
return OnBuffer(crc, buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a CRC on an entire file.
|
||||
/// </summary>
|
||||
/// <param name="pathName">Full path to file to open.</param>
|
||||
/// <param name="ocrc">Receives the CRC.</param>
|
||||
/// <returns>True on success, false on file error.</returns>
|
||||
public static bool OnWholeFile(string pathName, out uint ocrc) {
|
||||
try {
|
||||
using (FileStream fs = File.Open(pathName, FileMode.Open, FileAccess.Read)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
uint crc = 0;
|
||||
long remain = fs.Length;
|
||||
while (remain != 0) {
|
||||
int toRead = (remain < buffer.Length) ? (int)remain : buffer.Length;
|
||||
int actual = fs.Read(buffer, 0, toRead);
|
||||
if (toRead != actual) {
|
||||
throw new IOException("Expected " + toRead + ", got " + actual);
|
||||
}
|
||||
|
||||
crc = OnBuffer(crc, buffer, 0, toRead);
|
||||
remain -= toRead;
|
||||
}
|
||||
|
||||
ocrc = crc;
|
||||
return true;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Debug.WriteLine("Fail: " + ioe);
|
||||
ocrc = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
CommonUtil/CommonUtil.csproj
Normal file
22
CommonUtil/CommonUtil.csproj
Normal file
@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
40
CommonUtil/Container.cs
Normal file
40
CommonUtil/Container.cs
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CommonUtil {
|
||||
public class Container {
|
||||
/// <summary>
|
||||
/// Compares two lists of strings to see if their contents are equal. The lists
|
||||
/// must contain the same strings, in the same order.
|
||||
/// </summary>
|
||||
/// <param name="l1">List #1.</param>
|
||||
/// <param name="l2">List #2.</param>
|
||||
/// <param name="comparer">String comparer (e.g. StringComparer.InvariantCulture). If
|
||||
/// null, the default string comparer is used.</param>
|
||||
/// <returns>True if the lists are equal.</returns>
|
||||
public static bool StringListEquals(IList<string> l1, IList<string>l2,
|
||||
StringComparer comparer) {
|
||||
// Quick check for reference equality.
|
||||
if (l1 == l2) {
|
||||
return true;
|
||||
}
|
||||
return Enumerable.SequenceEqual<string>(l1, l2, comparer);
|
||||
}
|
||||
}
|
||||
}
|
233
CommonUtil/DebugLog.cs
Normal file
233
CommonUtil/DebugLog.cs
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// Debug log facility, with priority levels and time stamps.
|
||||
///
|
||||
/// The logs are held in memory. The internal storage expands as logs are added
|
||||
/// until the maximum size is reached, then switches to circular buffering. This
|
||||
/// minimizes overhead for small logs while avoiding infinite expansion.
|
||||
/// </summary>
|
||||
public class DebugLog {
|
||||
/// <summary>
|
||||
/// Log priority levels, in ascending order. "Silent" is only used as an argument
|
||||
/// when setting the minimum priority level.
|
||||
/// </summary>
|
||||
public enum Priority {
|
||||
Verbose = 0, Debug, Info, Warning, Error, Silent
|
||||
}
|
||||
|
||||
private static char[] sSingleLetter = { 'V', 'D', 'I', 'W', 'E', 'S' };
|
||||
|
||||
/// <summary>
|
||||
/// Holds a single log entry.
|
||||
/// </summary>
|
||||
private struct LogEntry {
|
||||
public DateTime mWhen;
|
||||
public Priority mPriority;
|
||||
public string mText;
|
||||
|
||||
public LogEntry(Priority prio, string msg) {
|
||||
mWhen = DateTime.Now;
|
||||
mPriority = prio;
|
||||
mText = msg;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log collection.
|
||||
/// </summary>
|
||||
private List<LogEntry> mEntries = new List<LogEntry>();
|
||||
private int mTopEntry = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Date/time when the log object was created. Used for relative time display mode.
|
||||
/// </summary>
|
||||
private DateTime mStartWhen;
|
||||
|
||||
/// <summary>
|
||||
/// If set, display time stamps as relative time rather than absolute.
|
||||
/// </summary>
|
||||
private bool mShowRelTime = false;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum priority level. Anything below this is ignored.
|
||||
/// </summary>
|
||||
private Priority mMinPriority = Priority.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of lines we'll hold in memory. This is a simple measure
|
||||
/// to keep the process from expanding without bound.
|
||||
/// </summary>
|
||||
private int mMaxLines = 100000;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor. Configures min priority to Info.
|
||||
/// </summary>
|
||||
public DebugLog() : this(Priority.Info) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="prio">Minimum log priority level.</param>
|
||||
public DebugLog(Priority prio) {
|
||||
mMinPriority = prio;
|
||||
mStartWhen = DateTime.Now;
|
||||
LogI("Log started at " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss zzz"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the message priority threshold. Messages below the specified priority
|
||||
/// will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="prio">Minimum priority value.</param>
|
||||
public void SetMinPriority(Priority prio) {
|
||||
mMinPriority = prio;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the "show relative time" flag. If set, the timestamp in the log is
|
||||
/// relative to when the log object was created, instead of wall-clock time.
|
||||
/// </summary>
|
||||
/// <param name="showRelTime"></param>
|
||||
public void SetShowRelTime(bool showRelTime) {
|
||||
mShowRelTime = showRelTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a message logged at the specified priority would be accepted.
|
||||
/// </summary>
|
||||
/// <param name="prio"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsLoggable(Priority prio) {
|
||||
return prio >= mMinPriority;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all entries.
|
||||
/// </summary>
|
||||
public void Clear() {
|
||||
mEntries.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a message to the log buffer.
|
||||
/// </summary>
|
||||
/// <param name="prio">Log priority.</param>
|
||||
/// <param name="message">Message to log.</param>
|
||||
public void Log(Priority prio, string message) {
|
||||
if (prio < mMinPriority) {
|
||||
return;
|
||||
}
|
||||
LogEntry ent = new LogEntry(prio, message);
|
||||
if (mEntries.Count < mMaxLines) {
|
||||
// Still growing.
|
||||
mEntries.Add(ent);
|
||||
} else {
|
||||
// Circular replacement. Adding to the end then removing [0] has
|
||||
// significant performance issues.
|
||||
mEntries[mTopEntry++] = ent;
|
||||
if (mTopEntry == mMaxLines) {
|
||||
mTopEntry = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogV(string message) {
|
||||
Log(Priority.Verbose, message);
|
||||
}
|
||||
public void LogD(string message) {
|
||||
Log(Priority.Debug, message);
|
||||
}
|
||||
public void LogI(string message) {
|
||||
Log(Priority.Info, message);
|
||||
}
|
||||
public void LogW(string message) {
|
||||
Log(Priority.Warning, message);
|
||||
}
|
||||
public void LogE(string message) {
|
||||
Log(Priority.Error, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dumps the contents of the log to a file.
|
||||
/// </summary>
|
||||
/// <param name="pathName">Full or partial pathname.</param>
|
||||
public void WriteToFile(string pathName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
using (StreamWriter sw = new StreamWriter(pathName, false, Encoding.UTF8)) {
|
||||
for (int i = mTopEntry; i < mEntries.Count; i++) {
|
||||
WriteEntry(sw, mEntries[i], sb);
|
||||
}
|
||||
for (int i = 0; i < mTopEntry; i++) {
|
||||
WriteEntry(sw, mEntries[i], sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a single entry to a file. Pass in a StringBuilder so we don't have
|
||||
/// to create a new one every time.
|
||||
/// </summary>
|
||||
private void WriteEntry(StreamWriter sw, LogEntry ent, StringBuilder sb) {
|
||||
sb.Clear();
|
||||
FormatEntry(ent, sb);
|
||||
sw.WriteLine(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats an entry, appending the text to the provided StringBuilder.
|
||||
/// </summary>
|
||||
private void FormatEntry(LogEntry ent, StringBuilder sb) {
|
||||
if (mShowRelTime) {
|
||||
sb.Append((ent.mWhen - mStartWhen).ToString(@"mm\:ss\.fff"));
|
||||
} else {
|
||||
sb.Append(ent.mWhen.ToString(@"hh\:mm\:ss\.fff"));
|
||||
}
|
||||
sb.Append(' ');
|
||||
sb.Append(sSingleLetter[(int)ent.mPriority]);
|
||||
sb.Append(' ');
|
||||
sb.Append(ent.mText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dumps the contents of the log to a string. This is intended for display in a
|
||||
/// text box, so lines are separated with CRLF.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string WriteToString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = mTopEntry; i < mEntries.Count; i++) {
|
||||
FormatEntry(mEntries[i], sb);
|
||||
sb.Append("\r\n");
|
||||
}
|
||||
for (int i = 0; i < mTopEntry; i++) {
|
||||
FormatEntry(mEntries[i], sb);
|
||||
sb.Append("\r\n");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "DebugLog has " + mEntries.Count + " entries";
|
||||
}
|
||||
}
|
||||
}
|
136
CommonUtil/FileLoadReport.cs
Normal file
136
CommonUtil/FileLoadReport.cs
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// File load item, identifying the location, severity, and details of the issue.
|
||||
/// </summary>
|
||||
public class FileLoadItem {
|
||||
public const int NO_LINE = -1;
|
||||
public const int NO_COLUMN = -1;
|
||||
|
||||
public enum Type {
|
||||
Unknown = 0,
|
||||
Notice,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
public int Line { get; private set; }
|
||||
public int Column { get; private set; }
|
||||
public Type MsgType { get; private set; }
|
||||
public string Message { get; private set; }
|
||||
|
||||
public FileLoadItem(int line, int col, Type msgType, string msg) {
|
||||
Line = line;
|
||||
Column = col;
|
||||
MsgType = msgType; ;
|
||||
Message = msg;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A structured collection of errors and warnings generated when reading data from a file.
|
||||
/// </summary>
|
||||
public class FileLoadReport : IEnumerable<FileLoadItem> {
|
||||
// List of items. Currently unsorted; items will appear in the order they were added.
|
||||
private List<FileLoadItem> mItems = new List<FileLoadItem>();
|
||||
|
||||
public string FileName { get; private set; }
|
||||
public bool HasWarnings { get; private set; }
|
||||
public bool HasErrors { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="fileName">Name of file being loaded. This is just for output; this
|
||||
/// class doesn't actually try to access the file.</param>
|
||||
public FileLoadReport(string fileName) {
|
||||
FileName = fileName;
|
||||
}
|
||||
|
||||
public IEnumerator<FileLoadItem> GetEnumerator() {
|
||||
return mItems.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return mItems.GetEnumerator();
|
||||
}
|
||||
|
||||
public int Count { get { return mItems.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new message to the report.
|
||||
/// </summary>
|
||||
/// <param name="isWarn">Is this a warning or an error?</param>
|
||||
/// <param name="msg">Human-readable message.</param>
|
||||
public void Add(FileLoadItem.Type msgType, string msg) {
|
||||
Add(FileLoadItem.NO_LINE, FileLoadItem.NO_COLUMN, msgType, msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new message to the report.
|
||||
/// </summary>
|
||||
/// <param name="line">Line where the issue was seen.</param>
|
||||
/// <param name="col">Column where the problem starts, or NO_COLUMN.</param>
|
||||
/// <param name="isWarn">Is this a warning or an error?</param>
|
||||
/// <param name="msg">Human-readable message.</param>
|
||||
public void Add(int line, int col, FileLoadItem.Type msgType, string msg) {
|
||||
mItems.Add(new FileLoadItem(line, col, msgType, msg));
|
||||
switch (msgType) {
|
||||
case FileLoadItem.Type.Warning:
|
||||
HasWarnings = true;
|
||||
break;
|
||||
case FileLoadItem.Type.Error:
|
||||
HasErrors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the entire collection into a single multi-line string.
|
||||
/// </summary>
|
||||
/// <returns>Formatted string.</returns>
|
||||
public string Format() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (mItems.Count > 0) {
|
||||
sb.AppendFormat("File {0}:\r\n", FileName);
|
||||
}
|
||||
foreach (FileLoadItem item in mItems) {
|
||||
if (item.Line != FileLoadItem.NO_LINE && item.Column != FileLoadItem.NO_COLUMN) {
|
||||
sb.AppendFormat(" Line {0}.{1}: {2}: {3}\r\n", item.Line, item.Column,
|
||||
item.MsgType.ToString().ToLower(), item.Message);
|
||||
} else if (item.Line != FileLoadItem.NO_LINE) {
|
||||
sb.AppendFormat(" Line {0}: {1}: {2}\r\n", item.Line,
|
||||
item.MsgType.ToString().ToLower(), item.Message);
|
||||
} else {
|
||||
// Capitalized form looks nicer here.
|
||||
sb.AppendFormat(" {0}: {1}\r\n", item.MsgType.ToString(), item.Message);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "FileLoadReport: count=" + mItems.Count + " hasWarn=" +
|
||||
HasWarnings + " hasErr=" + HasErrors;
|
||||
}
|
||||
}
|
||||
}
|
141
CommonUtil/FileUtil.cs
Normal file
141
CommonUtil/FileUtil.cs
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUtil {
|
||||
public static class FileUtil {
|
||||
/// <summary>
|
||||
/// Compares the contents of a file against a byte array.
|
||||
/// </summary>
|
||||
/// <param name="expected">Expected data values.</param>
|
||||
/// <param name="pathName">Pathname of file to inspect.</param>
|
||||
/// <param name="badOffset">Offset of first mismatch. If this is equal to expected.Length
|
||||
/// or File(pathName).Length, then the contents were equal but one source stopped
|
||||
/// early. The badFileVal parameter will be zero.</param>
|
||||
/// <param name="badFileVal">Mismatched value, from the file.</param>
|
||||
/// <returns>True if the contents match, false if not.</returns>
|
||||
public static bool CompareBinaryFile(byte[] expected, string pathName,
|
||||
out int badOffset, out byte badFileVal) {
|
||||
badOffset = -1;
|
||||
badFileVal = 0;
|
||||
|
||||
int chunkOffset = 0;
|
||||
byte[] buffer = new byte[65536];
|
||||
using (FileStream fs = File.Open(pathName, FileMode.Open, FileAccess.Read)) {
|
||||
//if (fs.Length != expected.Length) {
|
||||
// return false;
|
||||
//}
|
||||
int fileRemain = (int)fs.Length;
|
||||
|
||||
while (fileRemain != 0) {
|
||||
int toRead = Math.Min(buffer.Length, fileRemain);
|
||||
int actual = fs.Read(buffer, 0, toRead);
|
||||
if (actual != toRead) {
|
||||
// File I/O problem; unexpected.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < toRead; i++) {
|
||||
if (chunkOffset + i >= expected.Length) {
|
||||
// File on disk was too long.
|
||||
Debug.Assert(fs.Length > expected.Length);
|
||||
badOffset = chunkOffset + i;
|
||||
return false;
|
||||
}
|
||||
if (expected[chunkOffset + i] != buffer[i]) {
|
||||
badOffset = chunkOffset + i;
|
||||
badFileVal = buffer[i];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fileRemain -= toRead;
|
||||
chunkOffset += toRead;
|
||||
}
|
||||
|
||||
if (fs.Length != expected.Length) {
|
||||
Debug.Assert(fs.Length < expected.Length);
|
||||
badOffset = (int) fs.Length;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two text files line-by-line. Ignores line termination characters.
|
||||
/// Assumes UTF-8 encoding.
|
||||
/// </summary>
|
||||
/// <param name="pathName1">Full path of first file.</param>
|
||||
/// <param name="pathName2">Full path of second file.</param>
|
||||
/// <param name="firstDiffLine">Line number of first differing line.</param>
|
||||
/// <param name="line1">Differing line from first file.</param>
|
||||
/// <param name="line2">Differing line from second file.</param>
|
||||
/// <returns>True if the files are equal.</returns>
|
||||
public static bool CompareTextFiles(string pathName1, string pathName2,
|
||||
out int firstDiffLine, out string line1, out string line2) {
|
||||
int line = 0;
|
||||
using (StreamReader sr1 = new StreamReader(pathName1, Encoding.UTF8)) {
|
||||
using (StreamReader sr2 = new StreamReader(pathName2, Encoding.UTF8)) {
|
||||
while (true) {
|
||||
// ReadLine strips the EOL char(s)
|
||||
string str1 = sr1.ReadLine();
|
||||
string str2 = sr2.ReadLine();
|
||||
line++;
|
||||
if (str1 != str2) {
|
||||
firstDiffLine = line;
|
||||
line1 = str1;
|
||||
line2 = str2;
|
||||
return false;
|
||||
}
|
||||
if (str1 == null) {
|
||||
Debug.Assert(str2 == null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
firstDiffLine = -1;
|
||||
line1 = line2 = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the destination file is missing or older than the source file.
|
||||
/// This can be used do decide whether it's necessary to copy srcFile over.
|
||||
/// </summary>
|
||||
/// <param name="dstFile">File of interest.</param>
|
||||
/// <param name="srcFile">File to compare dates with.</param>
|
||||
/// <returns>True if dstFile is missing or older than srcFile.</returns>
|
||||
public static bool FileMissingOrOlder(string dstFile, string srcFile) {
|
||||
FileInfo fid = new FileInfo(dstFile);
|
||||
if (!fid.Exists) {
|
||||
return true; // not there
|
||||
}
|
||||
FileInfo fis = new FileInfo(srcFile);
|
||||
if (!fis.Exists) {
|
||||
return false; // nothing to compare against
|
||||
}
|
||||
|
||||
return (fid.LastWriteTimeUtc < fis.LastWriteTimeUtc);
|
||||
}
|
||||
}
|
||||
}
|
32
CommonUtil/Misc.cs
Normal file
32
CommonUtil/Misc.cs
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace CommonUtil {
|
||||
public static class Misc {
|
||||
// Given a type, dump all namespaces found in the same assembly.
|
||||
// https://stackoverflow.com/a/1549216/294248
|
||||
public static void DumpNamespacesInAssembly(Type type) {
|
||||
Console.WriteLine("Assembly: " + type.Assembly.Location);
|
||||
Type[] typeList = type.Assembly.GetTypes();
|
||||
var namespaces = typeList.Select(t => t.Namespace).Distinct();
|
||||
foreach (string ns in namespaces) {
|
||||
Console.WriteLine(" " + ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
CommonUtil/Properties/Resources.Designer.cs
generated
Normal file
108
CommonUtil/Properties/Resources.Designer.cs
generated
Normal file
@ -0,0 +1,108 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace CommonUtil.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CommonUtil.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to File not found.
|
||||
/// </summary>
|
||||
public static string ERR_FILE_NOT_FOUND {
|
||||
get {
|
||||
return ResourceManager.GetString("ERR_FILE_NOT_FOUND", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid address.
|
||||
/// </summary>
|
||||
public static string ERR_INVALID_ADDRESS {
|
||||
get {
|
||||
return ResourceManager.GetString("ERR_INVALID_ADDRESS", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid numeric constant.
|
||||
/// </summary>
|
||||
public static string ERR_INVALID_NUMERIC_CONSTANT {
|
||||
get {
|
||||
return ResourceManager.GetString("ERR_INVALID_NUMERIC_CONSTANT", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Syntax error.
|
||||
/// </summary>
|
||||
public static string ERR_SYNTAX {
|
||||
get {
|
||||
return ResourceManager.GetString("ERR_SYNTAX", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to -.-.-.
|
||||
/// </summary>
|
||||
public static string NO_VERSION {
|
||||
get {
|
||||
return ResourceManager.GetString("NO_VERSION", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
135
CommonUtil/Properties/Resources.resx
Normal file
135
CommonUtil/Properties/Resources.resx
Normal file
@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ERR_FILE_NOT_FOUND" xml:space="preserve">
|
||||
<value>File not found</value>
|
||||
</data>
|
||||
<data name="ERR_INVALID_ADDRESS" xml:space="preserve">
|
||||
<value>Invalid address</value>
|
||||
</data>
|
||||
<data name="ERR_INVALID_NUMERIC_CONSTANT" xml:space="preserve">
|
||||
<value>Invalid numeric constant</value>
|
||||
</data>
|
||||
<data name="ERR_SYNTAX" xml:space="preserve">
|
||||
<value>Syntax error</value>
|
||||
</data>
|
||||
<data name="NO_VERSION" xml:space="preserve">
|
||||
<value>-.-.-</value>
|
||||
</data>
|
||||
</root>
|
426
CommonUtil/RangeSet.cs
Normal file
426
CommonUtil/RangeSet.cs
Normal file
@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// Compact representation of a set of integers that tend to be adjacent.
|
||||
///
|
||||
/// <para>The default enumeration is a series of integers, not a series of ranges. Use
|
||||
/// RangeListIterator to get the latter.</para>
|
||||
///
|
||||
/// <para>Most operations operate in log(N) time, where N is the number of
|
||||
/// regions.</para>
|
||||
/// </summary>
|
||||
public class RangeSet : IEnumerable<int> {
|
||||
/// <summary>
|
||||
/// List of ranges, in sorted order.
|
||||
/// </summary>
|
||||
private List<Range> mRangeList = new List<Range>();
|
||||
|
||||
/// <summary>
|
||||
/// Number of values in the set.
|
||||
/// </summary>
|
||||
public int Count { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// For unit tests: return the number of Range elements in the list.
|
||||
/// </summary>
|
||||
public int DebugRangeCount { get { return mRangeList.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// Represents a contiguous range of values.
|
||||
/// </summary>
|
||||
public struct Range {
|
||||
/// <summary>
|
||||
/// Lowest value (inclusive).
|
||||
/// </summary>
|
||||
public int Low { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Highest value (inclusive).
|
||||
/// </summary>
|
||||
public int High { get; set; }
|
||||
|
||||
public Range(int low, int high) {
|
||||
Debug.Assert(low <= high);
|
||||
Low = low;
|
||||
High = high;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified value falls in this range.
|
||||
/// </summary>
|
||||
public bool Contains(int val) {
|
||||
return (val >= Low && val <= High);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterator definition.
|
||||
/// </summary>
|
||||
private class RangeSetIterator : IEnumerator {
|
||||
/// <summary>
|
||||
/// The RangeSet we're iterating over.
|
||||
/// </summary>
|
||||
private RangeSet mSet;
|
||||
|
||||
// Index of current Range element in mSet.mRangeList.
|
||||
private int mListIndex = -1;
|
||||
|
||||
// Current range, extracted from mRangeList.
|
||||
private Range mCurrentRange;
|
||||
|
||||
// Current value in mCurrentRange.
|
||||
private int mCurrentVal;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="set">RangeSet to iterate over.</param>
|
||||
public RangeSetIterator(RangeSet set) {
|
||||
mSet = set;
|
||||
Reset();
|
||||
}
|
||||
|
||||
// IEnumerator: current element
|
||||
public object Current {
|
||||
get {
|
||||
if (mListIndex < 0) {
|
||||
// not started
|
||||
return null;
|
||||
}
|
||||
return mCurrentVal;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the next range in the list in mCurrentRange.
|
||||
/// </summary>
|
||||
/// <returns>True on success, false if we reached the end of the list.</returns>
|
||||
private bool GetNextRange() {
|
||||
mListIndex++; // increments to 0 on first invocation
|
||||
if (mListIndex == mSet.mRangeList.Count) {
|
||||
// no more ranges
|
||||
return false;
|
||||
}
|
||||
|
||||
mCurrentRange = mSet.mRangeList[mListIndex];
|
||||
mCurrentVal = mCurrentRange.Low;
|
||||
return true;
|
||||
}
|
||||
|
||||
// IEnumerator: move to the next element, returning false if there isn't one
|
||||
public bool MoveNext() {
|
||||
if (mListIndex < 0) {
|
||||
// just started
|
||||
return GetNextRange();
|
||||
} else {
|
||||
// iterating within range object
|
||||
mCurrentVal++;
|
||||
if (mCurrentVal > mCurrentRange.High) {
|
||||
// finished with this one, move on to the next
|
||||
return GetNextRange();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IEnumerator: reset state
|
||||
public void Reset() {
|
||||
mListIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// General constructor. Creates an empty set.
|
||||
/// </summary>
|
||||
public RangeSet() {
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs set from an iterator.
|
||||
/// </summary>
|
||||
/// <param name="iter">Iterator that generates a set of integers in ascending order.</param>
|
||||
public RangeSet(IEnumerator iter) : this() {
|
||||
if (!iter.MoveNext()) {
|
||||
return;
|
||||
}
|
||||
int first = (int) iter.Current;
|
||||
Count++;
|
||||
Range curRange = new Range(first, first);
|
||||
|
||||
while (iter.MoveNext()) {
|
||||
int val = (int) iter.Current;
|
||||
Count++;
|
||||
if (val == curRange.High + 1) {
|
||||
// Add to current range.
|
||||
curRange.High = val;
|
||||
} else {
|
||||
// Not contiguous, create new range.
|
||||
mRangeList.Add(curRange);
|
||||
curRange = new Range(val, val);
|
||||
}
|
||||
}
|
||||
|
||||
mRangeList.Add(curRange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the range list, returning Range objects.
|
||||
/// </summary>
|
||||
public IEnumerator<Range> RangeListIterator {
|
||||
get { return mRangeList.GetEnumerator(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all values from the set.
|
||||
/// </summary>
|
||||
public void Clear() {
|
||||
mRangeList.Clear();
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
// IEnumerable: get an enumerator instance that returns integer values
|
||||
public IEnumerator GetEnumerator() {
|
||||
return new RangeSetIterator(this);
|
||||
}
|
||||
|
||||
// IEnumerable<int>
|
||||
IEnumerator<int> IEnumerable<int>.GetEnumerator() {
|
||||
return (IEnumerator<int>)GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the range that contains "val", or an appropriate place in the list to
|
||||
/// insert a new range.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to find.</param>
|
||||
/// <returns>The index of the matching element, or a negative value indicating
|
||||
/// the index to insert at. 2C doesn't support negative 0, so the insertion
|
||||
/// index will be incremented before negation.</returns>
|
||||
private int FindValue(int val) {
|
||||
int low = 0;
|
||||
int high = mRangeList.Count - 1;
|
||||
while (low <= high) {
|
||||
int mid = (low + high) / 2;
|
||||
Range midRange = mRangeList[mid];
|
||||
|
||||
if (midRange.Contains(val)) {
|
||||
// found it
|
||||
return mid;
|
||||
} else if (val < midRange.Low) {
|
||||
// too big, move the high end in
|
||||
high = mid - 1;
|
||||
} else if (val > midRange.High) {
|
||||
// too small, move the low end in
|
||||
low = mid + 1;
|
||||
} else {
|
||||
// WTF... list not sorted?
|
||||
throw new Exception("Bad binary search");
|
||||
}
|
||||
}
|
||||
|
||||
// Not found, insert before "low".
|
||||
return -(low + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether val is a member of the set.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to check.</param>
|
||||
/// <returns>True if the value is a member of the set.</returns>
|
||||
public bool Contains(int val) {
|
||||
return (FindValue(val) >= 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a value to the set. If the value is already present, nothing changes.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to add.</param>
|
||||
public void Add(int val) {
|
||||
int listIndex = FindValue(val);
|
||||
if (listIndex >= 0) {
|
||||
// Already present in set.
|
||||
return;
|
||||
}
|
||||
Count++;
|
||||
|
||||
if (mRangeList.Count == 0) {
|
||||
// Empty list, skip the gymnastics.
|
||||
mRangeList.Add(new Range(val, val));
|
||||
return;
|
||||
}
|
||||
|
||||
// Negate and decrement to get insertion index. This value may == Count if
|
||||
// the value is higher than all current members.
|
||||
listIndex = -listIndex - 1;
|
||||
|
||||
if (listIndex > 0 && mRangeList[listIndex - 1].High == val - 1) {
|
||||
// Expand prior range. Check to see if it blends into next.
|
||||
if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1) {
|
||||
// Combine ranges.
|
||||
Range prior = mRangeList[listIndex - 1];
|
||||
Range next = mRangeList[listIndex];
|
||||
Debug.Assert(prior.High + 2 == next.Low);
|
||||
prior.High = next.High;
|
||||
mRangeList[listIndex - 1] = prior;
|
||||
mRangeList.RemoveAt(listIndex);
|
||||
} else {
|
||||
// Nope, just expand the prior range.
|
||||
Range prior = mRangeList[listIndex - 1];
|
||||
Debug.Assert(prior.High == val - 1);
|
||||
prior.High = val;
|
||||
mRangeList[listIndex - 1] = prior;
|
||||
}
|
||||
} else if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1) {
|
||||
// Expand next range.
|
||||
Range next = mRangeList[listIndex];
|
||||
Debug.Assert(next.Low == val + 1);
|
||||
next.Low = val;
|
||||
mRangeList[listIndex] = next;
|
||||
} else {
|
||||
// Add a new single-entry element.
|
||||
mRangeList.Insert(listIndex, new Range(val, val));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a range of contiguous values to the set.
|
||||
/// </summary>
|
||||
/// <param name="low">Lowest value (inclusive).</param>
|
||||
/// <param name="high">Highest value (inclusive).</param>
|
||||
public void AddRange(int low, int high) {
|
||||
// There's probably some very efficient way to do this. Keeping it simple for now.
|
||||
for (int i = low; i <= high; i++) {
|
||||
Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a value from the set. If the value is not present, nothing changes.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to remove.</param>
|
||||
public void Remove(int val) {
|
||||
int listIndex = FindValue(val);
|
||||
if (listIndex < 0) {
|
||||
// not found
|
||||
return;
|
||||
}
|
||||
|
||||
Count--;
|
||||
|
||||
Range rng = mRangeList[listIndex];
|
||||
if (rng.Low == val && rng.High == val) {
|
||||
// Single-value range. Remove.
|
||||
mRangeList.RemoveAt(listIndex);
|
||||
} else if (rng.Low == val) {
|
||||
// We're at the low end, reduce range.
|
||||
rng.Low = val + 1;
|
||||
mRangeList[listIndex] = rng;
|
||||
} else if (rng.High == val) {
|
||||
// We're at the high end, reduce range.
|
||||
rng.High = val - 1;
|
||||
mRangeList[listIndex] = rng;
|
||||
} else {
|
||||
// We're in the middle, split the range.
|
||||
Range next = new Range(val + 1, rng.High);
|
||||
rng.High = val - 1;
|
||||
mRangeList[listIndex] = rng;
|
||||
mRangeList.Insert(listIndex + 1, next);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal test function.
|
||||
/// </summary>
|
||||
private static bool CheckRangeSet(RangeSet set, int expectedRanges, int[] expected) {
|
||||
if (set.DebugRangeCount != expectedRanges) {
|
||||
Debug.WriteLine("Expected " + expectedRanges + " ranges, got " +
|
||||
set.DebugRangeCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare actual vs. expected. If we have more actual than expected we'll
|
||||
// throw on the array access.
|
||||
int expIndex = 0;
|
||||
foreach (int val in set) {
|
||||
if (val != expected[expIndex]) {
|
||||
Debug.WriteLine("Expected " + expected[expIndex] + ", got " + val);
|
||||
return false;
|
||||
}
|
||||
expIndex++;
|
||||
}
|
||||
|
||||
// See if we have more expected than actual.
|
||||
if (expIndex != expected.Length) {
|
||||
Debug.WriteLine("Expected " + expected.Length + " elements, found " + expIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The count is maintained separately, so check it.
|
||||
if (set.Count != expected.Length) {
|
||||
Debug.WriteLine("Expected Count=" + expected.Length + ", got " + set.Count);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes unit tests.
|
||||
/// </summary>
|
||||
/// <returns>True if all goes well.</returns>
|
||||
public static bool Test() {
|
||||
bool result = true;
|
||||
|
||||
RangeSet one = new RangeSet();
|
||||
one.Add(7);
|
||||
one.Add(5);
|
||||
one.Add(3);
|
||||
one.Add(9);
|
||||
one.Add(7);
|
||||
one.Add(8);
|
||||
one.Add(2);
|
||||
one.Add(4);
|
||||
result &= CheckRangeSet(one, 2, new int[] { 2, 3, 4, 5, 7, 8, 9 });
|
||||
|
||||
one.Remove(2);
|
||||
one.Remove(9);
|
||||
one.Remove(4);
|
||||
result &= CheckRangeSet(one, 3, new int[] { 3, 5, 7, 8 });
|
||||
|
||||
one.Clear();
|
||||
one.AddRange(10, 15);
|
||||
result &= CheckRangeSet(one, 1, new int[] { 10, 11, 12, 13, 14, 15 });
|
||||
|
||||
one.Add(-1);
|
||||
one.Add(0);
|
||||
one.Add(-2);
|
||||
result &= CheckRangeSet(one, 2, new int[] { -2, -1, 0, 10, 11, 12, 13, 14, 15 });
|
||||
|
||||
Debug.WriteLine("RangeSet: test complete (ok=" + result + ")");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
63
CommonUtil/RawData.cs
Normal file
63
CommonUtil/RawData.cs
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CommonUtil {
|
||||
public class RawData {
|
||||
/// <summary>
|
||||
/// Extracts an integer from the data stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data stream.</param>
|
||||
/// <param name="offset">Start offset.</param>
|
||||
/// <param name="width">Word width, which may be 1-4 bytes.</param>
|
||||
/// <param name="isBigEndian">True if word is in big-endian order.</param>
|
||||
/// <returns>Value found.</returns>
|
||||
public static int GetWord(byte[] data, int offset, int width, bool isBigEndian) {
|
||||
if (width < 1 || width > 4 || offset + width > data.Length) {
|
||||
throw new ArgumentOutOfRangeException("GetWord(offset=" + offset + " width=" +
|
||||
width + "), data.Length=" + data.Length);
|
||||
}
|
||||
if (isBigEndian) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return data[offset];
|
||||
case 2:
|
||||
return (data[offset] << 8) | data[offset + 1];
|
||||
case 3:
|
||||
return (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2];
|
||||
case 4:
|
||||
return (data[offset] << 24) | (data[offset + 1] << 16) |
|
||||
(data[offset + 2] << 8) | data[offset + 3];
|
||||
}
|
||||
} else {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return data[offset];
|
||||
case 2:
|
||||
return data[offset] | (data[offset + 1] << 8);
|
||||
case 3:
|
||||
return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
|
||||
case 4:
|
||||
return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) |
|
||||
data[offset + 3] << 24;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("GetWord(): should not be here");
|
||||
}
|
||||
}
|
||||
}
|
222
CommonUtil/ShellCommand.cs
Normal file
222
CommonUtil/ShellCommand.cs
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// Execute a shell command and return stdout/stderr.
|
||||
///
|
||||
/// Returning stdout/stderr separately loses the interleave, but it's unclear whether
|
||||
/// that gets lost anyway with output buffering and the asynchronous I/O facility.
|
||||
/// </summary>
|
||||
public class ShellCommand {
|
||||
// These were handy:
|
||||
// https://stackoverflow.com/a/32872174/294248
|
||||
// https://stackoverflow.com/a/18616369/294248
|
||||
// https://stackoverflow.com/a/7334029/294248
|
||||
// https://stackoverflow.com/a/5187715/294248
|
||||
|
||||
private const int CMD_TIMEOUT_MS = 10000; // 10 sec
|
||||
private bool USE_CMD_EXE = false;
|
||||
|
||||
/// <summary>
|
||||
/// Filename of shell command to execute.
|
||||
/// </summary>
|
||||
public string CommandFileName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Arguments to pass to command. Individual arguments are separated by spaces.
|
||||
/// If an argument may contain spaces (e.g. it's a filename), surround it with
|
||||
/// double quotes (").
|
||||
/// </summary>
|
||||
public string Arguments { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Working directory for command. The directory will be changed for the
|
||||
/// command only.
|
||||
/// </summary>
|
||||
public string WorkDirectory { get; private set; }
|
||||
|
||||
public Dictionary<string, string> EnvVars { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full command line, for display purposes. This is just CommandFileName + Arguments
|
||||
/// unless some funny business is going on under the hood.
|
||||
/// </summary>
|
||||
public string FullCommandLine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output from stdout.
|
||||
/// </summary>
|
||||
public string Stdout { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Output from stderr.
|
||||
/// </summary>
|
||||
public string Stderr { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Command exit code. Will be 0 on success.
|
||||
/// </summary>
|
||||
public int ExitCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Buffers for gathering stdout/stderr.
|
||||
/// </summary>
|
||||
private StringBuilder mStdout, mStderr;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="commandFileName">Filename of command to execute.</param>
|
||||
/// <param name="arguments">Command arguments, separated by spaces. Surround args with
|
||||
/// embedded spaces with double quotes. Pass empty string if no args.</param>
|
||||
/// <param name="workDir">Working directory for command. Pass an empty string if you
|
||||
/// want to use the default.</param>
|
||||
/// <param name="env">Dictionary of values to set in the shell environment.</param>
|
||||
public ShellCommand(string commandFileName, string arguments, string workDir,
|
||||
Dictionary<string, string> env) {
|
||||
Debug.Assert(commandFileName != null);
|
||||
Debug.Assert(arguments != null);
|
||||
Debug.Assert(workDir != null);
|
||||
|
||||
CommandFileName = commandFileName;
|
||||
Arguments = arguments;
|
||||
WorkDirectory = workDir;
|
||||
EnvVars = env;
|
||||
|
||||
ExitCode = -100;
|
||||
|
||||
mStdout = new StringBuilder();
|
||||
mStderr = new StringBuilder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the command.
|
||||
/// </summary>
|
||||
/// <returns>Process exit code. 0 on success.</returns>
|
||||
public void Execute() {
|
||||
// Works for full paths to command, but not for shell stuff like "dir".
|
||||
FileInfo fi = new FileInfo(CommandFileName);
|
||||
if (!fi.Exists) {
|
||||
Debug.WriteLine("Warning: file '" + CommandFileName + "' does not exist");
|
||||
}
|
||||
|
||||
ProcessStartInfo psi = new ProcessStartInfo();
|
||||
if (USE_CMD_EXE) {
|
||||
// Run inside cmd.exe on Windows.
|
||||
psi.FileName = "cmd.exe";
|
||||
psi.Arguments = "/C " + CommandFileName +
|
||||
(string.IsNullOrEmpty(Arguments) ? "" : " " + Arguments);
|
||||
} else {
|
||||
psi.FileName = CommandFileName;
|
||||
psi.Arguments = Arguments;
|
||||
}
|
||||
FullCommandLine = psi.FileName + " " + psi.Arguments;
|
||||
psi.CreateNoWindow = true;
|
||||
psi.RedirectStandardInput = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.UseShellExecute = false; // required for stdin/stdout redirect
|
||||
if (!string.IsNullOrEmpty(WorkDirectory)) {
|
||||
psi.WorkingDirectory = WorkDirectory;
|
||||
}
|
||||
|
||||
if (EnvVars != null) {
|
||||
foreach (KeyValuePair<string, string> kvp in EnvVars) {
|
||||
Debug.WriteLine("ENV: " + kvp.Key + "=" + kvp.Value);
|
||||
psi.Environment.Add(kvp);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
using (Process process = Process.Start(psi)) {
|
||||
process.OutputDataReceived += (sendingProcess, outLine) =>
|
||||
mStdout.AppendLine(outLine.Data);
|
||||
process.ErrorDataReceived += (sendingProcess, errLine) =>
|
||||
mStderr.AppendLine(errLine.Data);
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
// Close stdin so interactive programs don't stall.
|
||||
process.StandardInput.Close();
|
||||
|
||||
// I'm calling with a (fairly long) timeout, just in case.
|
||||
process.WaitForExit(CMD_TIMEOUT_MS);
|
||||
if (!process.HasExited) {
|
||||
Debug.WriteLine("Process stalled, killing");
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
// WaitForExit(timeout) can return before the async stdout/stderr stuff
|
||||
// has completed. Calling it without a timeout ensures correct behavior.
|
||||
process.WaitForExit();
|
||||
|
||||
// This will be zero on success. I've seen 1 when the command wasn't
|
||||
// found, and -1 when the process was killed.
|
||||
ExitCode = process.ExitCode;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// This can happen if the command doesn't exist.
|
||||
ExitCode = -2000;
|
||||
Stdout = string.Empty;
|
||||
Stderr = "Failed to execute command: " + FullCommandLine + "\r\n" + ex.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
Stdout = mStdout.ToString();
|
||||
Stderr = mStderr.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a tab in the system web browser for the specified URL.
|
||||
///
|
||||
/// NOTE: on Windows 10, as of 2018/09/02, this loses the anchor (the "#thing" at the
|
||||
/// end of the URL). Chasing through various stackoverflow posts, it appears the
|
||||
/// only way around this is to invoke the specific browser (which you dredge out of
|
||||
/// the Registry).
|
||||
/// </summary>
|
||||
public static void OpenUrl(string url) {
|
||||
// See https://stackoverflow.com/a/43232486/294248
|
||||
// The idea is to see if Start() will just do it, and if it doesn't then fall
|
||||
// back on something platform-specific. I don't know if this is actually
|
||||
// necessary -- I suspect Mono will do the right thing.
|
||||
try {
|
||||
Process.Start(url);
|
||||
} catch {
|
||||
// hack because of this: https://github.com/dotnet/corefx/issues/10361
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||
url = url.Replace("&", "^&");
|
||||
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") {
|
||||
CreateNoWindow = true
|
||||
});
|
||||
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
|
||||
Process.Start("xdg-open", url);
|
||||
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
|
||||
Process.Start("open", url);
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
199
CommonUtil/TaskTimer.cs
Normal file
199
CommonUtil/TaskTimer.cs
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// Collects timestamps from a series of events. Events may be nested, but may not overlap.
|
||||
/// A summary of task durations can be written to a log.
|
||||
/// </summary>
|
||||
public class TaskTimer {
|
||||
// TODO(maybe): create a start/end pair that works with a "using" directive to ensure
|
||||
// that a given item is always closed.
|
||||
|
||||
/// <summary>
|
||||
/// Timed task info.
|
||||
/// </summary>
|
||||
private class TimedItem {
|
||||
public string mTag;
|
||||
public int mIndentLevel;
|
||||
public DateTime mStartWhen;
|
||||
public DateTime mEndWhen;
|
||||
|
||||
public TimedItem(string tag, int indentLevel) {
|
||||
mTag = tag;
|
||||
mIndentLevel = indentLevel;
|
||||
mStartWhen = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of items. They are ordered by when the tasks ended.
|
||||
/// </summary>
|
||||
private List<TimedItem> mItems = new List<TimedItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Indentation level. Cosmetic.
|
||||
/// </summary>
|
||||
private int mIndentLevel = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Place where next record is to be inserted.
|
||||
///
|
||||
/// We keep inserting records ahead of whatever we inserted last, only advancing
|
||||
/// the insertion point when we close a record. Essentially a stack that moves
|
||||
/// forward when you pop() instead of removing the element. This lets us handle
|
||||
/// nested tasks correctly.
|
||||
/// </summary>
|
||||
private int mInsertPoint = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets object to initial state.
|
||||
/// </summary>
|
||||
public void Clear() {
|
||||
mItems.Clear();
|
||||
mIndentLevel = mInsertPoint = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a start record for a task.
|
||||
/// </summary>
|
||||
/// <param name="tag">Task tag.</param>
|
||||
public void StartTask(string tag) {
|
||||
TimedItem ti = new TimedItem(tag, mIndentLevel);
|
||||
mItems.Insert(mInsertPoint, ti);
|
||||
mIndentLevel++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes out a record. The tag must match the most recently started task.
|
||||
/// </summary>
|
||||
/// <param name="tag">Task tag.</param>
|
||||
public void EndTask(string tag) {
|
||||
TimedItem lastItem = mItems[mInsertPoint];
|
||||
if (lastItem.mTag != tag) {
|
||||
Debug.WriteLine("ERROR: tag mismatch: " + tag + " vs. " + lastItem.mTag);
|
||||
Debug.Assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
lastItem.mEndWhen = DateTime.Now;
|
||||
mIndentLevel--;
|
||||
mInsertPoint++;
|
||||
Debug.Assert(mIndentLevel >= 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints the timing data into a log object.
|
||||
/// </summary>
|
||||
/// <param name="log">Output destination.</param>
|
||||
/// <param name="msg">Header message.</param>
|
||||
public void DumpTimes(string msg, DebugLog log) {
|
||||
if (mItems.Count == 0) {
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(msg)) {
|
||||
log.LogI(msg);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int lastIndent = 0;
|
||||
foreach (TimedItem ti in mItems) {
|
||||
sb.Clear();
|
||||
FormatItem(ti, ref lastIndent, sb);
|
||||
log.LogI(sb.ToString());
|
||||
}
|
||||
|
||||
//DateTime firstStart = mItems[0].mStartWhen;
|
||||
//DateTime lastEnd = mItems[mItems.Count - 1].mEndWhen;
|
||||
//log.LogI(" Total: " + (lastEnd - firstStart).TotalMilliseconds + " ms");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints the timing data into the debug log.
|
||||
/// </summary>
|
||||
/// <param name="msg">Header message.</param>
|
||||
public void DumpTimes(string msg) {
|
||||
if (mItems.Count == 0) {
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(msg)) {
|
||||
Debug.WriteLine(msg);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int lastIndent = 0;
|
||||
foreach (TimedItem ti in mItems) {
|
||||
sb.Clear();
|
||||
FormatItem(ti, ref lastIndent, sb);
|
||||
Debug.WriteLine(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints the timing data into a string with newlines.
|
||||
/// </summary>
|
||||
/// <param name="log">Output destination.</param>
|
||||
/// <param name="msg">Header message.</param>
|
||||
public string DumpToString(string msg) {
|
||||
if (mItems.Count == 0) {
|
||||
return msg;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
#if DEBUG
|
||||
sb.Append("[NOTE: debug build -- assertions and extra checks are enabled]\r\n\r\n");
|
||||
#endif
|
||||
if (!string.IsNullOrEmpty(msg)) {
|
||||
sb.Append(msg);
|
||||
sb.Append("\r\n\r\n");
|
||||
}
|
||||
int lastIndent = 0;
|
||||
foreach (TimedItem ti in mItems) {
|
||||
FormatItem(ti, ref lastIndent, sb);
|
||||
sb.Append("\r\n");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Formats the specified item, appending it to the StringBuilder.
|
||||
/// </summary>
|
||||
/// <param name="ti">Item to format.</param>
|
||||
/// <param name="lastIndent">Previous indentation level.</param>
|
||||
/// <param name="sb">StringBuilder to append to.</param>
|
||||
private void FormatItem(TimedItem ti, ref int lastIndent, StringBuilder sb) {
|
||||
for (int i = 0; i <= ti.mIndentLevel - 1; i++) {
|
||||
sb.Append("| ");
|
||||
}
|
||||
if (lastIndent < ti.mIndentLevel) {
|
||||
//sb.Append("/-");
|
||||
sb.Append("/ ");
|
||||
} else /*if (lastIndent == ti.mIndentLevel)*/ {
|
||||
sb.Append("| ");
|
||||
}
|
||||
sb.Append(ti.mTag);
|
||||
sb.Append(": ");
|
||||
sb.Append((ti.mEndWhen - ti.mStartWhen).TotalMilliseconds.ToString());
|
||||
sb.Append(" ms");
|
||||
|
||||
lastIndent = ti.mIndentLevel;
|
||||
}
|
||||
}
|
||||
}
|
241
CommonUtil/TextUtil.cs
Normal file
241
CommonUtil/TextUtil.cs
Normal file
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// Text utility functions.
|
||||
/// </summary>
|
||||
public static class TextUtil {
|
||||
// 100 spaces, useful when padding things out.
|
||||
private const string SPACES = " " +
|
||||
" ";
|
||||
private static readonly char[] CSV_ESCAPE_CHARS = { ',', '"' };
|
||||
private const string NonPrintableAsciiPattern = @"[^\u0020-\u007e]";
|
||||
private static Regex sNonPrintableAsciiRegex = new Regex(NonPrintableAsciiPattern);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a string to an ASCII-only string, replacing iso-latin characters
|
||||
/// with their ASCII equivalents. This may change the length of the string.
|
||||
///
|
||||
/// For example, "¿Dónde está Über bären?" becomes "Donde esta Uber baren?".
|
||||
/// </summary>
|
||||
/// <param name="inString"></param>
|
||||
/// <returns>Converted string.</returns>
|
||||
public static string LatinToAscii(string inString) {
|
||||
// https://stackoverflow.com/questions/140422/
|
||||
var newStringBuilder = new StringBuilder();
|
||||
newStringBuilder.Append(inString.Normalize(NormalizationForm.FormKD)
|
||||
.Where(x => x < 128)
|
||||
.ToArray());
|
||||
return newStringBuilder.ToString();
|
||||
|
||||
// Alternatively?
|
||||
// System.Text.Encoding.ASCII.GetString(
|
||||
// System.Text.Encoding.GetEncoding(1251).GetBytes(text))
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the value is valid high- or low-ASCII.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to test.</param>
|
||||
/// <returns>True if val is valid ASCII.</returns>
|
||||
public static bool IsHiLoAscii(int val) {
|
||||
return (val >= 0x20 && val < 0x7f) || (val >= 0xa0 && val < 0xff);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the character is printable ASCII.
|
||||
/// </summary>
|
||||
/// <param name="ch">Character to evaluate.</param>
|
||||
/// <returns>True if the character is printable ASCII.</returns>
|
||||
public static bool IsPrintableAscii(char ch) {
|
||||
return ch >= 0x20 && ch < 0x7f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the string has nothing but printable ASCII characters in it.
|
||||
/// </summary>
|
||||
/// <param name="str">String to evaluate.</param>
|
||||
/// <returns>True if all characters are printable ASCII.</returns>
|
||||
public static bool IsPrintableAscii(string str) {
|
||||
// Linq version: return str.Any(c => c < 0x20 || c > 0x7e);
|
||||
|
||||
MatchCollection matches = sNonPrintableAsciiRegex.Matches(str);
|
||||
return matches.Count == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts high-ASCII bytes to a string.
|
||||
/// </summary>
|
||||
/// <param name="data">Array of bytes with ASCII data.</param>
|
||||
/// <param name="offset">Start offset.</param>
|
||||
/// <param name="length">String length.</param>
|
||||
/// <returns>Converted string.</returns>
|
||||
public static string HighAsciiToString(byte[] data, int offset, int length) {
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
for (int i = offset; i < offset + length; i++) {
|
||||
sb.Append((char)(data[i] & 0x7f));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trims whitespace off the end of a StringBuilder.
|
||||
/// </summary>
|
||||
/// <param name="sb">StringBuilder reference.</param>
|
||||
public static void TrimEnd(StringBuilder sb) {
|
||||
const string WSPC = " \t\r\n";
|
||||
int len = sb.Length;
|
||||
while (WSPC.IndexOf(sb[len - 1]) >= 0) {
|
||||
len--;
|
||||
}
|
||||
sb.Length = len;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all occurrences of a string with another string, but only if they appear
|
||||
/// outside quoted text. Useful for replacing structural bits of a JSON string without
|
||||
/// messing with the quoted items. Assumes that quoted quotes (backslash-quote) only
|
||||
/// appear inside quoted text.
|
||||
/// </summary>
|
||||
/// <param name="inStr"></param>
|
||||
/// <param name="findStr"></param>
|
||||
/// <param name="repStr"></param>
|
||||
public static string NonQuoteReplace(string inStr, string findStr, string repStr) {
|
||||
// There's probably a better way to do this...
|
||||
StringBuilder sb = new StringBuilder(inStr.Length + inStr.Length / 20);
|
||||
int cmpLen = findStr.Length;
|
||||
bool findStrQuote = findStr.Contains('\"'); // find/rep str switches to in-quote
|
||||
Debug.Assert(findStrQuote == repStr.Contains('\"'));
|
||||
|
||||
bool inQuote = false;
|
||||
for (int i = 0; i < inStr.Length; i++) {
|
||||
char ch = inStr[i];
|
||||
if (inQuote) {
|
||||
// Check to see if the double-quote is quoted. It's safe to back up
|
||||
// one because we don't start in-quote.
|
||||
if (ch == '\"' && inStr[i-1] != '\\') {
|
||||
inQuote = false;
|
||||
} else {
|
||||
// in quoted text, keep going
|
||||
}
|
||||
sb.Append(ch);
|
||||
} else {
|
||||
if (string.Compare(inStr, i, findStr, 0, cmpLen) == 0) {
|
||||
sb.Append(repStr);
|
||||
i += cmpLen - 1;
|
||||
inQuote = findStrQuote;
|
||||
} else {
|
||||
if (ch == '"') {
|
||||
// There are no quoted-quotes outside of quotes, so we don't need
|
||||
// to check for a '\'.
|
||||
inQuote = true;
|
||||
}
|
||||
sb.Append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads a string with trailing spaces so that the total length of the line, including
|
||||
/// previous contents, is the length specified. One trailing space will be added even
|
||||
/// if the string's length is >= toLen.
|
||||
///
|
||||
/// You must begin with an empty StringBuilder at the start of each line.
|
||||
/// </summary>
|
||||
/// <param name="sb">StringBuilder to append to.</param>
|
||||
/// <param name="str">String to add.</param>
|
||||
/// <param name="toLen">Total line width to pad to.</param>
|
||||
public static void AppendPaddedString(StringBuilder sb, string str, int toLen) {
|
||||
if (str == null) {
|
||||
str = string.Empty;
|
||||
}
|
||||
int newLen = sb.Length + str.Length;
|
||||
if (newLen >= toLen) {
|
||||
sb.Append(str);
|
||||
sb.Append(' ');
|
||||
} else {
|
||||
sb.Append(str);
|
||||
// would be nice to avoid this allocation/copy
|
||||
sb.Append(SPACES.Substring(0, toLen - newLen));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes a string for CSV.
|
||||
/// </summary>
|
||||
/// <param name="str">String to process.</param>
|
||||
/// <returns>Escaped string, or an empty string if the input was null.</returns>
|
||||
public static string EscapeCSV(string str) {
|
||||
if (str == null) {
|
||||
return string.Empty;
|
||||
}
|
||||
bool needQuote = (str.IndexOfAny(CSV_ESCAPE_CHARS) >= 0);
|
||||
if (needQuote) {
|
||||
return '"' + str.Replace("\"", "\"\"") + '"';
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes an integer array into a string.
|
||||
/// </summary>
|
||||
/// <param name="values">Array to serialize.</param>
|
||||
/// <returns>Serialized data.</returns>
|
||||
public static string SerializeIntArray(int[] values) {
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
sb.Append("int[]");
|
||||
for (int i = 0; i < values.Length; i++) {
|
||||
sb.Append(',');
|
||||
sb.Append(values[i]);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes an integer array from a string.
|
||||
/// </summary>
|
||||
/// <param name="cereal"></param>
|
||||
/// <returns></returns>
|
||||
public static int[] DeserializeIntArray(string cereal) {
|
||||
string[] splitted = cereal.Split(',');
|
||||
if (splitted.Length == 0) {
|
||||
throw new Exception("Bad serialized int[]");
|
||||
}
|
||||
if (splitted[0] != "int[]") {
|
||||
throw new Exception("Bad serialized int[], started with " + splitted[0]);
|
||||
}
|
||||
int[] arr = new int[splitted.Length - 1];
|
||||
try {
|
||||
for (int i = 1; i < splitted.Length; i++) {
|
||||
arr[i - 1] = int.Parse(splitted[i]);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new Exception("Bad serialized int[]: " + ex.Message);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
}
|
511
CommonUtil/TypedRangeSet.cs
Normal file
511
CommonUtil/TypedRangeSet.cs
Normal file
@ -0,0 +1,511 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// Compact representation of a set of typed integers that tend to be adjacent.
|
||||
/// We expect there to be relatively few different types of things.
|
||||
///
|
||||
/// <para>The default enumeration is a series of integers, not a series of ranges. Use
|
||||
/// RangeListIterator to get the latter.</para>
|
||||
///
|
||||
/// <para>Most operations operate in log(N) time, where N is the number of
|
||||
/// regions.</para>
|
||||
/// </summary>
|
||||
public class TypedRangeSet : IEnumerable<TypedRangeSet.Tuple> {
|
||||
/// <summary>
|
||||
/// List of ranges, in sorted order.
|
||||
/// </summary>
|
||||
private List<TypedRange> mRangeList = new List<TypedRange>();
|
||||
|
||||
/// <summary>
|
||||
/// Number of values in the set.
|
||||
/// </summary>
|
||||
public int Count { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of Range elements in the list.
|
||||
/// </summary>
|
||||
public int RangeCount { get { return mRangeList.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// Represents a contiguous range of values.
|
||||
/// </summary>
|
||||
public struct TypedRange {
|
||||
/// <summary>
|
||||
/// Lowest value (inclusive).
|
||||
/// </summary>
|
||||
public int Low { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Highest value (inclusive).
|
||||
/// </summary>
|
||||
public int High { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Value type in this range.
|
||||
/// </summary>
|
||||
public int Type { get; set; }
|
||||
|
||||
public TypedRange(int low, int high, int type) {
|
||||
Debug.Assert(low <= high);
|
||||
Low = low;
|
||||
High = high;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public bool Contains(int val) {
|
||||
return (val >= Low && val <= High);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value + type pair. Returned from foreach enumerator.
|
||||
/// </summary>
|
||||
public struct Tuple {
|
||||
public int Value;
|
||||
public int Type;
|
||||
|
||||
public Tuple(int value, int type) {
|
||||
Value = value;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public static bool operator ==(Tuple a, Tuple b) {
|
||||
return a.Value == b.Value && a.Type == b.Type;
|
||||
}
|
||||
public static bool operator !=(Tuple a, Tuple b) {
|
||||
return !(a == b);
|
||||
}
|
||||
public override bool Equals(object obj) {
|
||||
return obj is Tuple && this == (Tuple)obj;
|
||||
}
|
||||
public override int GetHashCode() {
|
||||
return Value ^ Type;
|
||||
}
|
||||
public override string ToString() {
|
||||
return Value + " (" + Type + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterator definition.
|
||||
/// </summary>
|
||||
private class TypedRangeSetIterator : IEnumerator<Tuple> {
|
||||
/// <summary>
|
||||
/// The TypedRangeSet we're iterating over.
|
||||
/// </summary>
|
||||
private TypedRangeSet mSet;
|
||||
|
||||
// Index of current Range element in mSet.mRangeList.
|
||||
private int mListIndex = -1;
|
||||
|
||||
// Current range, extracted from mRangeList.
|
||||
private TypedRange mCurrentRange;
|
||||
|
||||
// Current value in mCurrentRange.
|
||||
private int mCurrentVal;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="set">TypedRangeSet to iterate over.</param>
|
||||
public TypedRangeSetIterator(TypedRangeSet set) {
|
||||
mSet = set;
|
||||
Reset();
|
||||
}
|
||||
|
||||
// IEnumerator: current element
|
||||
public object Current {
|
||||
get {
|
||||
if (mListIndex < 0) {
|
||||
// not started
|
||||
return null;
|
||||
}
|
||||
return new Tuple(mCurrentVal, mCurrentRange.Type);
|
||||
}
|
||||
}
|
||||
|
||||
// IEnumerator<Tuple>
|
||||
Tuple IEnumerator<Tuple>.Current {
|
||||
get {
|
||||
return (Tuple)Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the next range in the list in mCurrentRange.
|
||||
/// </summary>
|
||||
/// <returns>True on success, false if we reached the end of the list.</returns>
|
||||
private bool GetNextRange() {
|
||||
mListIndex++; // increments to 0 on first invocation
|
||||
if (mListIndex == mSet.mRangeList.Count) {
|
||||
// no more ranges
|
||||
return false;
|
||||
}
|
||||
|
||||
mCurrentRange = mSet.mRangeList[mListIndex];
|
||||
mCurrentVal = mCurrentRange.Low;
|
||||
return true;
|
||||
}
|
||||
|
||||
// IEnumerator: move to the next element, returning false if there isn't one
|
||||
public bool MoveNext() {
|
||||
if (mListIndex < 0) {
|
||||
// just started
|
||||
return GetNextRange();
|
||||
} else {
|
||||
// iterating within range object
|
||||
mCurrentVal++;
|
||||
if (mCurrentVal > mCurrentRange.High) {
|
||||
// finished with this one, move on to the next
|
||||
return GetNextRange();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IEnumerator: reset state
|
||||
public void Reset() {
|
||||
mListIndex = -1;
|
||||
}
|
||||
|
||||
// IEnumerator<Tuple>
|
||||
public void Dispose() {
|
||||
mSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor. Creates an empty set.
|
||||
/// </summary>
|
||||
public TypedRangeSet() {
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the range list, returning Range objects.
|
||||
/// </summary>
|
||||
public IEnumerator<TypedRange> RangeListIterator {
|
||||
get { return mRangeList.GetEnumerator(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all values from the set.
|
||||
/// </summary>
|
||||
public void Clear() {
|
||||
mRangeList.Clear();
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
// IEnumerable: get an enumerator instance that returns integer values
|
||||
public IEnumerator GetEnumerator() {
|
||||
return new TypedRangeSetIterator(this);
|
||||
}
|
||||
|
||||
// IEnumerable<Tuple>
|
||||
IEnumerator<Tuple> IEnumerable<Tuple>.GetEnumerator() {
|
||||
return (IEnumerator<Tuple>)GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the range that contains "val", or an appropriate place in the list to
|
||||
/// insert a new range.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to find.</param>
|
||||
/// <returns>The index of the matching element, or a negative value indicating
|
||||
/// the index to insert at. 2C doesn't support negative 0, so the insertion
|
||||
/// index will be incremented before negation.</returns>
|
||||
private int FindValue(int val) {
|
||||
int low = 0;
|
||||
int high = mRangeList.Count - 1;
|
||||
while (low <= high) {
|
||||
int mid = (low + high) / 2;
|
||||
TypedRange midRange = mRangeList[mid];
|
||||
|
||||
if (midRange.Contains(val)) {
|
||||
// found it
|
||||
return mid;
|
||||
} else if (val < midRange.Low) {
|
||||
// too big, move the high end in
|
||||
high = mid - 1;
|
||||
} else if (val > midRange.High) {
|
||||
// too small, move the low end in
|
||||
low = mid + 1;
|
||||
} else {
|
||||
// WTF... list not sorted?
|
||||
throw new Exception("Bad binary search");
|
||||
}
|
||||
}
|
||||
|
||||
// Not found, insert before "low".
|
||||
return -(low + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether val is a member of the set.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to check.</param>
|
||||
/// <returns>True if the value is a member of the set.</returns>
|
||||
public bool Contains(int val) {
|
||||
return (FindValue(val) >= 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the specified value.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to query.</param>
|
||||
/// <param name="type">Receives the type, or -1 if the value is not in the set.</param>
|
||||
/// <returns>True if the value is in the set.</returns>
|
||||
public bool GetType(int val, out int type) {
|
||||
int listIndex = FindValue(val);
|
||||
if (listIndex >= 0) {
|
||||
type = mRangeList[listIndex].Type;
|
||||
return true;
|
||||
} else {
|
||||
type = -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or changes a value to the set. If the value is already present and has
|
||||
/// a matching type, nothing changes.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to add.</param>
|
||||
/// <param name="type">Value's type.</param>
|
||||
public void Add(int val, int type) {
|
||||
int listIndex = FindValue(val);
|
||||
if (listIndex >= 0) {
|
||||
// Value is present in set, check type.
|
||||
if (mRangeList[listIndex].Type == type) {
|
||||
// It's a match, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrong type. Remove previous entry, then fall through to add new.
|
||||
Remove(val);
|
||||
listIndex = FindValue(val); // get insertion point
|
||||
}
|
||||
Count++;
|
||||
|
||||
if (mRangeList.Count == 0) {
|
||||
// Empty list, skip the gymnastics.
|
||||
mRangeList.Add(new TypedRange(val, val, type));
|
||||
return;
|
||||
}
|
||||
|
||||
// Negate and decrement to get insertion index. This value may == Count if
|
||||
// the value is higher than all current members.
|
||||
listIndex = -listIndex - 1;
|
||||
|
||||
if (listIndex > 0 && mRangeList[listIndex - 1].High == val - 1 &&
|
||||
mRangeList[listIndex - 1].Type == type) {
|
||||
// Expand prior range. Check to see if it blends into next as well.
|
||||
if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1 &&
|
||||
mRangeList[listIndex].Type == type) {
|
||||
// Combine ranges.
|
||||
TypedRange prior = mRangeList[listIndex - 1];
|
||||
TypedRange next = mRangeList[listIndex];
|
||||
Debug.Assert(prior.High + 2 == next.Low);
|
||||
prior.High = next.High;
|
||||
mRangeList[listIndex - 1] = prior;
|
||||
mRangeList.RemoveAt(listIndex);
|
||||
} else {
|
||||
// Nope, just expand the prior range.
|
||||
TypedRange prior = mRangeList[listIndex - 1];
|
||||
Debug.Assert(prior.High == val - 1);
|
||||
prior.High = val;
|
||||
mRangeList[listIndex - 1] = prior;
|
||||
}
|
||||
} else if (listIndex < mRangeList.Count && mRangeList[listIndex].Low == val + 1 &&
|
||||
mRangeList[listIndex].Type == type) {
|
||||
// Expand next range.
|
||||
TypedRange next = mRangeList[listIndex];
|
||||
Debug.Assert(next.Low == val + 1);
|
||||
next.Low = val;
|
||||
mRangeList[listIndex] = next;
|
||||
} else {
|
||||
// Nothing adjacent, add a new single-entry element.
|
||||
mRangeList.Insert(listIndex, new TypedRange(val, val, type));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a range of contiguous values to the set.
|
||||
/// </summary>
|
||||
/// <param name="low">Lowest value (inclusive).</param>
|
||||
/// <param name="high">Highest value (inclusive).</param>
|
||||
/// <param name="high">Value type.</param>
|
||||
public void AddRange(int low, int high, int type) {
|
||||
// There's probably some very efficient way to do this. Keeping it simple for now.
|
||||
for (int i = low; i <= high; i++) {
|
||||
Add(i, type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a value from the set. If the value is not present, nothing changes.
|
||||
/// </summary>
|
||||
/// <param name="val">Value to remove.</param>
|
||||
public void Remove(int val) {
|
||||
int listIndex = FindValue(val);
|
||||
if (listIndex < 0) {
|
||||
// not found
|
||||
return;
|
||||
}
|
||||
|
||||
Count--;
|
||||
|
||||
TypedRange rng = mRangeList[listIndex];
|
||||
if (rng.Low == val && rng.High == val) {
|
||||
// Single-value range. Remove.
|
||||
mRangeList.RemoveAt(listIndex);
|
||||
} else if (rng.Low == val) {
|
||||
// We're at the low end, reduce range.
|
||||
rng.Low = val + 1;
|
||||
mRangeList[listIndex] = rng;
|
||||
} else if (rng.High == val) {
|
||||
// We're at the high end, reduce range.
|
||||
rng.High = val - 1;
|
||||
mRangeList[listIndex] = rng;
|
||||
} else {
|
||||
// We're in the middle, split the range.
|
||||
TypedRange next = new TypedRange(val + 1, rng.High, rng.Type);
|
||||
rng.High = val - 1;
|
||||
mRangeList[listIndex] = rng;
|
||||
mRangeList.Insert(listIndex + 1, next);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal test function.
|
||||
/// </summary>
|
||||
private static bool CheckTypedRangeSet(TypedRangeSet set, int expectedRanges,
|
||||
Tuple[] expected) {
|
||||
if (set.RangeCount != expectedRanges) {
|
||||
Debug.WriteLine("Expected " + expectedRanges + " ranges, got " +
|
||||
set.RangeCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare actual vs. expected. If we have more actual than expected we'll
|
||||
// throw on the array access.
|
||||
int expIndex = 0;
|
||||
foreach (TypedRangeSet.Tuple val in set) {
|
||||
if (val != expected[expIndex]) {
|
||||
Debug.WriteLine("Expected " + expected[expIndex] + ", got " + val);
|
||||
return false;
|
||||
}
|
||||
expIndex++;
|
||||
}
|
||||
|
||||
// See if we have more expected than actual.
|
||||
if (expIndex != expected.Length) {
|
||||
Debug.WriteLine("Expected " + expected.Length + " elements, found " + expIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The count is maintained separately, so check it.
|
||||
if (set.Count != expected.Length) {
|
||||
Debug.WriteLine("Expected Count=" + expected.Length + ", got " + set.Count);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes unit tests.
|
||||
/// </summary>
|
||||
/// <returns>True if all goes well.</returns>
|
||||
public static bool Test() {
|
||||
bool result = true;
|
||||
|
||||
TypedRangeSet one = new TypedRangeSet();
|
||||
one.Add(7, 100);
|
||||
one.Add(5, 100);
|
||||
one.Add(3, 100);
|
||||
one.Add(9, 100);
|
||||
one.Add(7, 100);
|
||||
one.Add(8, 100);
|
||||
one.Add(2, 100);
|
||||
one.Add(4, 100);
|
||||
result &= CheckTypedRangeSet(one, 2, new Tuple[] {
|
||||
new Tuple(2, 100),
|
||||
new Tuple(3, 100),
|
||||
new Tuple(4, 100),
|
||||
new Tuple(5, 100),
|
||||
new Tuple(7, 100),
|
||||
new Tuple(8, 100),
|
||||
new Tuple(9, 100) });
|
||||
|
||||
one.Remove(2);
|
||||
one.Remove(9);
|
||||
one.Remove(4);
|
||||
result &= CheckTypedRangeSet(one, 3, new Tuple[] {
|
||||
new Tuple(3, 100),
|
||||
new Tuple(5, 100),
|
||||
new Tuple(7, 100),
|
||||
new Tuple(8, 100) });
|
||||
|
||||
one.Clear();
|
||||
one.Add(1, 200);
|
||||
one.Add(3, 100);
|
||||
one.Add(7, 100);
|
||||
one.Add(5, 100);
|
||||
one.Add(9, 100);
|
||||
one.Add(6, 100);
|
||||
one.Add(8, 100);
|
||||
one.Add(6, 200);
|
||||
one.Add(2, 200);
|
||||
one.Add(4, 300);
|
||||
one.Add(4, 100);
|
||||
result &= CheckTypedRangeSet(one, 4, new Tuple[] {
|
||||
new Tuple(1, 200),
|
||||
new Tuple(2, 200),
|
||||
new Tuple(3, 100),
|
||||
new Tuple(4, 100),
|
||||
new Tuple(5, 100),
|
||||
new Tuple(6, 200),
|
||||
new Tuple(7, 100),
|
||||
new Tuple(8, 100),
|
||||
new Tuple(9, 100) });
|
||||
|
||||
one.Add(6, 100);
|
||||
result &= CheckTypedRangeSet(one, 2, new Tuple[] {
|
||||
new Tuple(1, 200),
|
||||
new Tuple(2, 200),
|
||||
new Tuple(3, 100),
|
||||
new Tuple(4, 100),
|
||||
new Tuple(5, 100),
|
||||
new Tuple(6, 100),
|
||||
new Tuple(7, 100),
|
||||
new Tuple(8, 100),
|
||||
new Tuple(9, 100) });
|
||||
|
||||
Debug.WriteLine("TypedRangeSet: test complete (ok=" + result + ")");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
192
CommonUtil/Version.cs
Normal file
192
CommonUtil/Version.cs
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace CommonUtil {
|
||||
/// <summary>
|
||||
/// Version number container. Instances are immutable.
|
||||
///
|
||||
/// See https://semver.org/ for explanation of system.
|
||||
/// </summary>
|
||||
public struct Version {
|
||||
// Must be in ascending order, e.g. Alpha release comes before Beta.
|
||||
public enum PreRelType { Dev, Alpha, Beta, Final };
|
||||
|
||||
/// <summary>
|
||||
/// Major version number.
|
||||
/// </summary>
|
||||
public int Major { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minor version number.
|
||||
/// </summary>
|
||||
public int Minor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bug fix release number.
|
||||
/// </summary>
|
||||
public int Patch { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Software grade, for pre-release versions.
|
||||
/// </summary>
|
||||
public PreRelType PreReleaseType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pre-release version.
|
||||
/// </summary>
|
||||
public int PreRelease { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version instance to use when no version information is available. This will
|
||||
/// always compare as less than a "real" version.
|
||||
/// </summary>
|
||||
public static readonly Version NO_VERSION = new Version(-1, -1, -1);
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for comparing vs. NO_VERSION.
|
||||
/// </summary>
|
||||
public bool IsValid {
|
||||
get {
|
||||
return this != NO_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Version(int major, int minor) :
|
||||
this(major, minor, 0, PreRelType.Final, 0) { }
|
||||
|
||||
public Version(int major, int minor, int patch) :
|
||||
this(major, minor, patch, PreRelType.Final, 0) { }
|
||||
|
||||
public Version(int major, int minor, int patch, PreRelType preRelType, int preRel) {
|
||||
Debug.Assert(preRelType != PreRelType.Final || preRel == 0);
|
||||
Major = major;
|
||||
Minor = minor;
|
||||
Patch = patch;
|
||||
PreReleaseType = preRelType;
|
||||
PreRelease = preRel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse the argument into version components.
|
||||
/// </summary>
|
||||
/// <param name="str">Version string.</param>
|
||||
/// <returns>New Version object, or NO_VERSION on parsing failure.</returns>
|
||||
public static Version Parse(string str) {
|
||||
try {
|
||||
int major, minor, patch;
|
||||
major = minor = patch = 0;
|
||||
|
||||
string[] parts = str.Split(new char[] { '.', '-' });
|
||||
major = int.Parse(parts[0]);
|
||||
if (parts.Length > 1) {
|
||||
minor = int.Parse(parts[1]);
|
||||
}
|
||||
if (parts.Length > 2) {
|
||||
patch = int.Parse(parts[2]);
|
||||
}
|
||||
// parse the preRel thing someday
|
||||
return new Version(major, minor, patch);
|
||||
} catch (Exception ex) {
|
||||
Debug.WriteLine("Version parse failed: '" + str + "': " + ex.Message);
|
||||
return NO_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static bool operator ==(Version a, Version b) {
|
||||
return a.Major == b.Major && a.Minor == b.Minor && a.Patch == b.Patch &&
|
||||
a.PreReleaseType == b.PreReleaseType && a.PreRelease == b.PreRelease;
|
||||
}
|
||||
public static bool operator !=(Version a, Version b) {
|
||||
return !(a == b);
|
||||
}
|
||||
public override bool Equals(object obj) {
|
||||
return obj is Version && this == (Version)obj;
|
||||
}
|
||||
public override int GetHashCode() {
|
||||
return Major * 10000 + Minor * 1000 + Patch * 100 +
|
||||
(int)PreReleaseType * 10 + PreRelease;
|
||||
}
|
||||
|
||||
public static bool operator <(Version a, Version b) {
|
||||
if (a.Major != b.Major) {
|
||||
return a.Major < b.Major;
|
||||
}
|
||||
if (a.Minor != b.Minor) {
|
||||
return a.Minor < b.Minor;
|
||||
}
|
||||
if (a.Patch != b.Patch) {
|
||||
return a.Patch < b.Patch;
|
||||
}
|
||||
if (a.PreReleaseType != b.PreReleaseType) {
|
||||
return (int)a.PreReleaseType < (int)b.PreReleaseType;
|
||||
}
|
||||
if (a.PreRelease != b.PreRelease) {
|
||||
return a.PreRelease < b.PreRelease;
|
||||
}
|
||||
Debug.Assert(a == b);
|
||||
return false;
|
||||
}
|
||||
public static bool operator >(Version a, Version b) {
|
||||
return b < a;
|
||||
}
|
||||
public static bool operator <=(Version a, Version b) {
|
||||
return a == b || a < b;
|
||||
}
|
||||
public static bool operator >=(Version a, Version b) {
|
||||
return a == b || a > b;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
if (this == NO_VERSION) {
|
||||
return Properties.Resources.NO_VERSION;
|
||||
} else if (PreReleaseType == PreRelType.Final) {
|
||||
return string.Format("{0}.{1}.{2}", Major, Minor, Patch);
|
||||
} else {
|
||||
return string.Format("{0}.{1}.{2}-{3}{4}", Major, Minor, Patch,
|
||||
PreReleaseType.ToString().ToLower(), PreRelease);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple unit test.
|
||||
/// </summary>
|
||||
public static bool Test() {
|
||||
bool ok = true;
|
||||
|
||||
Version checkVers = new Version(1, 2, 3, PreRelType.Beta, 4);
|
||||
Version sameVers = new Version(1, 2, 3, PreRelType.Beta, 4);
|
||||
ok &= (checkVers == sameVers);
|
||||
ok &= (checkVers <= sameVers);
|
||||
ok &= (checkVers >= sameVers);
|
||||
ok &= (!(checkVers < sameVers));
|
||||
ok &= (!(checkVers > sameVers));
|
||||
|
||||
ok &= (checkVers != new Version(1, 2, 3));
|
||||
ok &= (checkVers < new Version(1, 2, 3));
|
||||
ok &= (checkVers > new Version(1, 2, 3, PreRelType.Beta, 3));
|
||||
ok &= (checkVers < new Version(2, 0));
|
||||
ok &= (checkVers > new Version(1, 2, 2));
|
||||
ok &= (checkVers < new Version(1, 3, 1));
|
||||
|
||||
Debug.WriteLine("Version struct test complete (ok=" + ok + ")");
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
}
|
49
CommonWinForms/CommonWinForms.csproj
Normal file
49
CommonWinForms/CommonWinForms.csproj
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{08EC328D-078E-4236-B574-BE6B3FD85915}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>CommonWinForms</RootNamespace>
|
||||
<AssemblyName>CommonWinForms</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;BUILD_FOR_WINDOWS</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;BUILD_FOR_WINDOWS</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="WinFormsExtensions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
85
CommonWinForms/NativeMethods.cs
Normal file
85
CommonWinForms/NativeMethods.cs
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
#if BUILD_FOR_WINDOWS // I don't know if other platforms will emulate the LVITEM stuff
|
||||
namespace CommonWinForms {
|
||||
// From https://stackoverflow.com/questions/1019388/
|
||||
|
||||
/// <summary>
|
||||
/// Unpleasant hackery to make "select all" faster. With 500K items it takes about 24
|
||||
/// seconds to select everything individually, and there isn't a better way. With this
|
||||
/// it only takes a few milliseconds.
|
||||
/// </summary>
|
||||
public class NativeMethods {
|
||||
private const int LVM_FIRST = 0x1000;
|
||||
private const int LVM_SETITEMSTATE = LVM_FIRST + 43;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct LVITEM {
|
||||
public int mask;
|
||||
public int iItem;
|
||||
public int iSubItem;
|
||||
public int state;
|
||||
public int stateMask;
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
public string pszText;
|
||||
public int cchTextMax;
|
||||
public int iImage;
|
||||
public IntPtr lParam;
|
||||
public int iIndent;
|
||||
public int iGroupId;
|
||||
public int cColumns;
|
||||
public IntPtr puColumns;
|
||||
};
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SendMessageLVItem(IntPtr hWnd, int msg, int wParam, ref LVITEM lvi);
|
||||
|
||||
/// <summary>
|
||||
/// Select all rows on the given listview
|
||||
/// </summary>
|
||||
/// <param name="list">The listview whose items are to be selected</param>
|
||||
public static void SelectAllItems(ListView list) {
|
||||
NativeMethods.SetItemState(list, -1, 2, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselect all rows on the given listview
|
||||
/// </summary>
|
||||
/// <param name="list">The listview whose items are to be deselected</param>
|
||||
public static void DeselectAllItems(ListView list) {
|
||||
NativeMethods.SetItemState(list, -1, 2, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the item state on the given item
|
||||
/// </summary>
|
||||
/// <param name="list">The listview whose item's state is to be changed</param>
|
||||
/// <param name="itemIndex">The index of the item to be changed</param>
|
||||
/// <param name="mask">Which bits of the value are to be set?</param>
|
||||
/// <param name="value">The value to be set</param>
|
||||
public static void SetItemState(ListView list, int itemIndex, int mask, int value) {
|
||||
LVITEM lvItem = new LVITEM();
|
||||
lvItem.stateMask = mask;
|
||||
lvItem.state = value;
|
||||
SendMessageLVItem(list.Handle, LVM_SETITEMSTATE, itemIndex, ref lvItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif //BUILD_FOR_WINDOWS
|
36
CommonWinForms/Properties/AssemblyInfo.cs
Normal file
36
CommonWinForms/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("CommonWinForms")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("CommonWinForms")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("08ec328d-078e-4236-b574-be6b3fd85915")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
121
CommonWinForms/WinFormsExtensions.cs
Normal file
121
CommonWinForms/WinFormsExtensions.cs
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace CommonWinForms {
|
||||
/// <summary>
|
||||
/// Overload RichTextBox.AppendText() with a version that takes a color as an argument.
|
||||
///
|
||||
/// From https://stackoverflow.com/a/1926822/294248
|
||||
/// </summary>
|
||||
public static class RichTextBoxExtensions {
|
||||
public static void AppendText(this RichTextBox box, string text, Color color) {
|
||||
box.SelectionStart = box.TextLength;
|
||||
box.SelectionLength = 0;
|
||||
box.SelectionColor = color;
|
||||
box.AppendText(text);
|
||||
box.SelectionColor = box.ForeColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add functions to select and deselect all items.
|
||||
/// </summary>
|
||||
public static class ListViewExtensions {
|
||||
/// <summary>
|
||||
/// Selects all items in the list view.
|
||||
/// </summary>
|
||||
public static void SelectAll(this ListView listView) {
|
||||
// Neither I nor the Internet can figure out how to do this efficiently for
|
||||
// large lists without P/Invoke interop. With 554253 lines, it takes 24.3 seconds
|
||||
// to select each item individually, but only 4 milliseconds to do it through an
|
||||
// LVITEM. The latter causes a single VirtualItemsSelectionRangeChanged event
|
||||
// instead of 554K ItemSelectionChanged events.
|
||||
//
|
||||
// https://stackoverflow.com/questions/9039989/
|
||||
// https://stackoverflow.com/questions/1019388/
|
||||
|
||||
#if BUILD_FOR_WINDOWS // defined in our project properties
|
||||
NativeMethods.SelectAllItems(listView);
|
||||
#else
|
||||
try {
|
||||
Application.UseWaitCursor = true;
|
||||
Cursor.Current = Cursors.WaitCursor;
|
||||
listView.BeginUpdate();
|
||||
int max = listView.VirtualListSize;
|
||||
for (int i = 0; i < max; i++) {
|
||||
//codeListView.Items[i].Selected = true;
|
||||
listView.SelectedIndices.Add(i);
|
||||
}
|
||||
} finally {
|
||||
listView.EndUpdate();
|
||||
Application.UseWaitCursor = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselects all items in the list view.
|
||||
/// </summary>
|
||||
public static void DeselectAll(this ListView listView) {
|
||||
// This is as fast as the native DeselectAllItems(), so just use it.
|
||||
listView.SelectedIndices.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the double-buffered status of the list view.
|
||||
/// </summary>
|
||||
public static void SetDoubleBuffered(this ListView listView, bool enable) {
|
||||
WinFormsUtil.SetDoubleBuffered(listView, enable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified item is visible in the list view.
|
||||
/// </summary>
|
||||
public static bool IsItemVisible(this ListView listView, ListViewItem item) {
|
||||
Rectangle lvBounds = listView.ClientRectangle;
|
||||
if (listView.HeaderStyle != ColumnHeaderStyle.None) {
|
||||
// Need to factor the header height out. There's no easy way to do that,
|
||||
// but the header should be (almost) the same height as an item.
|
||||
// https://stackoverflow.com/q/538906/294248
|
||||
int headerHeight = item.Bounds.Height + 5; // 5 is magic, will probably break
|
||||
lvBounds = new Rectangle(lvBounds.X, lvBounds.Y + headerHeight,
|
||||
lvBounds.Width, lvBounds.Height - headerHeight);
|
||||
}
|
||||
//Console.WriteLine("IsVis LV: " + lvBounds + " IT: " +
|
||||
// item.GetBounds(ItemBoundsPortion.Entire));
|
||||
return lvBounds.IntersectsWith(item.GetBounds(ItemBoundsPortion.Entire));
|
||||
}
|
||||
}
|
||||
|
||||
public static class WinFormsUtil {
|
||||
/// <summary>
|
||||
/// Sets the "DoubleBuffered" property on a Control. For some reason the
|
||||
/// property is defined as "protected", but I don't want to subclass a ListView
|
||||
/// just so I can enable double-buffering.
|
||||
/// </summary>
|
||||
/// <param name="ctrl">Control to update.</param>
|
||||
/// <param name="enable">New state.</param>
|
||||
public static void SetDoubleBuffered(Control ctrl, bool enable) {
|
||||
System.Reflection.PropertyInfo prop = ctrl.GetType().GetProperty("DoubleBuffered",
|
||||
System.Reflection.BindingFlags.Instance |
|
||||
System.Reflection.BindingFlags.NonPublic);
|
||||
prop.SetValue(ctrl, enable, null);
|
||||
}
|
||||
}
|
||||
}
|
BIN
ImageSrc/SourceGenIcon.ico
Normal file
BIN
ImageSrc/SourceGenIcon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 139 KiB |
BIN
ImageSrc/SourceGenIcon.xcf
Normal file
BIN
ImageSrc/SourceGenIcon.xcf
Normal file
Binary file not shown.
BIN
ImageSrc/SourceGenIconSmall.ico
Normal file
BIN
ImageSrc/SourceGenIconSmall.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
ImageSrc/left-arrow.png
Normal file
BIN
ImageSrc/left-arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 B |
BIN
ImageSrc/left-arrow.xcf
Normal file
BIN
ImageSrc/left-arrow.xcf
Normal file
Binary file not shown.
BIN
ImageSrc/right-arrow.png
Normal file
BIN
ImageSrc/right-arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 155 B |
6
MakeDist/App.config
Normal file
6
MakeDist/App.config
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
</configuration>
|
103
MakeDist/CopyProgress.Designer.cs
generated
Normal file
103
MakeDist/CopyProgress.Designer.cs
generated
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
namespace MakeDist {
|
||||
partial class CopyProgress {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.progressRichTextBox = new System.Windows.Forms.RichTextBox();
|
||||
this.cancelButton = new System.Windows.Forms.Button();
|
||||
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// progressRichTextBox
|
||||
//
|
||||
this.progressRichTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.progressRichTextBox.Location = new System.Drawing.Point(13, 13);
|
||||
this.progressRichTextBox.Name = "progressRichTextBox";
|
||||
this.progressRichTextBox.ReadOnly = true;
|
||||
this.progressRichTextBox.Size = new System.Drawing.Size(459, 507);
|
||||
this.progressRichTextBox.TabIndex = 0;
|
||||
this.progressRichTextBox.Text = "";
|
||||
//
|
||||
// cancelButton
|
||||
//
|
||||
this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelButton.Location = new System.Drawing.Point(397, 526);
|
||||
this.cancelButton.Name = "cancelButton";
|
||||
this.cancelButton.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelButton.TabIndex = 1;
|
||||
this.cancelButton.Text = "Cancel";
|
||||
this.cancelButton.UseVisualStyleBackColor = true;
|
||||
this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
|
||||
//
|
||||
// backgroundWorker1
|
||||
//
|
||||
this.backgroundWorker1.WorkerReportsProgress = true;
|
||||
this.backgroundWorker1.WorkerSupportsCancellation = true;
|
||||
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
|
||||
this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged);
|
||||
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
|
||||
//
|
||||
// CopyProgress
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelButton;
|
||||
this.ClientSize = new System.Drawing.Size(484, 561);
|
||||
this.Controls.Add(this.cancelButton);
|
||||
this.Controls.Add(this.progressRichTextBox);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "CopyProgress";
|
||||
this.ShowInTaskbar = false;
|
||||
this.Text = "CopyDistFiles";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.CopyProgress_FormClosing);
|
||||
this.Load += new System.EventHandler(this.CopyProgress_Load);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.RichTextBox progressRichTextBox;
|
||||
private System.Windows.Forms.Button cancelButton;
|
||||
private System.ComponentModel.BackgroundWorker backgroundWorker1;
|
||||
}
|
||||
}
|
134
MakeDist/CopyProgress.cs
Normal file
134
MakeDist/CopyProgress.cs
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using CommonWinForms;
|
||||
|
||||
namespace MakeDist {
|
||||
public partial class CopyProgress : Form {
|
||||
/// <summary>
|
||||
/// Progress message, with colorful text. This is generated by the worker thread and
|
||||
/// passed to the UI thread.
|
||||
/// </summary>
|
||||
public class ProgressMessage {
|
||||
public string Text { get; private set; }
|
||||
public Color Color { get; private set; }
|
||||
public bool HasColor { get { return Color.A != 0; } }
|
||||
|
||||
public ProgressMessage(string msg) : this(msg, Color.FromArgb(0, 0, 0, 0)) { }
|
||||
|
||||
public ProgressMessage(string msg, Color color) {
|
||||
Text = msg;
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
private bool mClosedWhileRunning;
|
||||
|
||||
// Copy parameters.
|
||||
private FileCopier.BuildType mBuildType;
|
||||
private bool mCopyTestFiles;
|
||||
|
||||
|
||||
public CopyProgress(FileCopier.BuildType buildType, bool copyTestFiles) {
|
||||
InitializeComponent();
|
||||
|
||||
this.Size = new Size(1200, 600);
|
||||
|
||||
mBuildType = buildType;
|
||||
mCopyTestFiles = copyTestFiles;
|
||||
}
|
||||
|
||||
private void CopyProgress_Load(object sender, EventArgs e) {
|
||||
backgroundWorker1.RunWorkerAsync();
|
||||
}
|
||||
|
||||
private void CopyProgress_FormClosing(object sender, FormClosingEventArgs e) {
|
||||
if (backgroundWorker1.IsBusy) {
|
||||
backgroundWorker1.CancelAsync();
|
||||
DialogResult = DialogResult.Cancel;
|
||||
|
||||
// First close attempt is converted to a cancel.
|
||||
if (!mClosedWhileRunning) {
|
||||
e.Cancel = true;
|
||||
mClosedWhileRunning = true;
|
||||
}
|
||||
} else {
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelButton_Click(object sender, EventArgs e) {
|
||||
if (backgroundWorker1.IsBusy) {
|
||||
backgroundWorker1.CancelAsync();
|
||||
} else {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
|
||||
BackgroundWorker worker = sender as BackgroundWorker;
|
||||
|
||||
FileCopier copier = new FileCopier(mBuildType, mCopyTestFiles);
|
||||
e.Result = copier.CopyAllFiles(worker);
|
||||
}
|
||||
|
||||
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
|
||||
if (e.UserState is ProgressMessage) {
|
||||
ProgressMessage msg = e.UserState as ProgressMessage;
|
||||
if (msg.HasColor) {
|
||||
progressRichTextBox.AppendText(msg.Text, msg.Color);
|
||||
} else {
|
||||
// plain foreground text color
|
||||
progressRichTextBox.AppendText(msg.Text);
|
||||
}
|
||||
progressRichTextBox.SelectionStart = progressRichTextBox.Text.Length;
|
||||
progressRichTextBox.ScrollToCaret();
|
||||
} else {
|
||||
if (!string.IsNullOrEmpty((string)e.UserState)) {
|
||||
Debug.WriteLine("Weird progress: " + e.UserState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void backgroundWorker1_RunWorkerCompleted(object sender,
|
||||
RunWorkerCompletedEventArgs e) {
|
||||
if (e.Cancelled) {
|
||||
Debug.WriteLine("Test halted -- user cancellation");
|
||||
} else if (e.Error != null) {
|
||||
// test harness shouldn't be throwing errors like this
|
||||
Debug.WriteLine("Test failed: " + e.Error.ToString());
|
||||
progressRichTextBox.AppendText("\r\n");
|
||||
progressRichTextBox.AppendText(e.Error.ToString());
|
||||
progressRichTextBox.SelectionStart = progressRichTextBox.Text.Length;
|
||||
progressRichTextBox.ScrollToCaret();
|
||||
} else {
|
||||
bool ok = (bool)e.Result;
|
||||
Debug.WriteLine("Tests complete, success=" + ok);
|
||||
}
|
||||
|
||||
if (mClosedWhileRunning) {
|
||||
Close();
|
||||
}
|
||||
|
||||
cancelButton.Text = "Close";
|
||||
}
|
||||
}
|
||||
}
|
123
MakeDist/CopyProgress.resx
Normal file
123
MakeDist/CopyProgress.resx
Normal file
@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="backgroundWorker1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
316
MakeDist/FileCopier.cs
Normal file
316
MakeDist/FileCopier.cs
Normal file
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MakeDist {
|
||||
public class FileCopier {
|
||||
private const string SOURCEGEN_DIRNAME = "SourceGen";
|
||||
|
||||
/// <summary>
|
||||
/// Type of build to gather files for.
|
||||
/// </summary>
|
||||
public enum BuildType { Unknown, Release, Debug };
|
||||
|
||||
private enum SourceFileSpec {
|
||||
Unknown = 0,
|
||||
All,
|
||||
List,
|
||||
RegressionTests,
|
||||
AsmSources,
|
||||
NotBins,
|
||||
}
|
||||
|
||||
private class CopySpec {
|
||||
public string SourceDir { get; private set; }
|
||||
public string DestDir { get; private set; }
|
||||
public SourceFileSpec FileSpec { get; private set; }
|
||||
public bool IsRecursive { get; private set; }
|
||||
public string[] FileList { get; private set; }
|
||||
|
||||
public CopySpec(string srcDir, string dstDir, SourceFileSpec spec, bool recursive,
|
||||
string[] fileList) {
|
||||
SourceDir = srcDir;
|
||||
DestDir = dstDir;
|
||||
FileSpec = spec;
|
||||
IsRecursive = recursive;
|
||||
FileList = fileList;
|
||||
}
|
||||
}
|
||||
|
||||
private static CopySpec[] sMainSpec = {
|
||||
new CopySpec(".", ".",
|
||||
SourceFileSpec.List, false, new string[] { "README.md" }),
|
||||
new CopySpec("Asm65/bin/{BUILD_TYPE}/netstandard2.0/", ".",
|
||||
SourceFileSpec.List, false, new string[] { "Asm65.dll" }),
|
||||
new CopySpec("CommonUtil/bin/{BUILD_TYPE}/netstandard2.0/", ".",
|
||||
SourceFileSpec.List, false, new string[] { "CommonUtil.dll" }),
|
||||
new CopySpec("CommonWinForms/bin/{BUILD_TYPE}/", ".",
|
||||
SourceFileSpec.List, false, new string[] { "CommonWinForms.dll" }),
|
||||
new CopySpec("PluginCommon/bin/{BUILD_TYPE}/netstandard2.0/", ".",
|
||||
SourceFileSpec.List, false, new string[] { "PluginCommon.dll" }),
|
||||
new CopySpec("SourceGen/bin/{BUILD_TYPE}/", ".",
|
||||
SourceFileSpec.List, false, new string[] { "SourceGen.exe" }),
|
||||
new CopySpec("SourceGen/RuntimeData", "RuntimeData",
|
||||
SourceFileSpec.NotBins, true, null),
|
||||
new CopySpec("SourceGen/Examples", "Examples",
|
||||
SourceFileSpec.All, true, null),
|
||||
};
|
||||
private static CopySpec[] sTestSpec = {
|
||||
new CopySpec("SourceGen/SGTestData", "SGTestData",
|
||||
SourceFileSpec.RegressionTests, false, null),
|
||||
new CopySpec("SourceGen/SGTestData", "SGTestData",
|
||||
SourceFileSpec.List, false, new string[] { "README.md" }),
|
||||
new CopySpec("SourceGen/SGTestData/Expected", "SGTestData/Expected",
|
||||
SourceFileSpec.AsmSources, false, null),
|
||||
new CopySpec("SourceGen/SGTestData/Source", "SGTestData/Source",
|
||||
SourceFileSpec.AsmSources, false, null),
|
||||
new CopySpec("SourceGen/SGTestData/FunkyProjects", "SGTestData/FunkyProjects",
|
||||
SourceFileSpec.All, false, null),
|
||||
};
|
||||
|
||||
private static string sBasePath;
|
||||
|
||||
// We want all of the regression test binaries, plus the .sym65, .dis65, and .cs,
|
||||
// but nothing with an underscore in the part before the extension.
|
||||
private const string TestCasePattern = @"^\d\d\d\d-[A-Za-z0-9-]+(\..*)?$";
|
||||
private static Regex sTestCaseRegex = new Regex(TestCasePattern);
|
||||
|
||||
|
||||
private BuildType mBuildType;
|
||||
private bool mCopyTestFiles;
|
||||
private BackgroundWorker mWorker;
|
||||
|
||||
|
||||
public FileCopier(BuildType buildType, bool copyTestFiles) {
|
||||
mBuildType = buildType;
|
||||
mCopyTestFiles = copyTestFiles;
|
||||
}
|
||||
|
||||
private void ReportProgress(string msg) {
|
||||
mWorker.ReportProgress(0, new CopyProgress.ProgressMessage(msg + "\r\n"));
|
||||
}
|
||||
|
||||
private void ReportProgress(string msg, Color color) {
|
||||
mWorker.ReportProgress(0, new CopyProgress.ProgressMessage(msg + "\r\n", color));
|
||||
}
|
||||
|
||||
private void ReportErrMsg(string msg) {
|
||||
ReportProgress(msg, Color.Red);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main entry point.
|
||||
/// </summary>
|
||||
/// <param name="worker">Background task interface object.</param>
|
||||
/// <returns>True on success.</returns>
|
||||
public bool CopyAllFiles(BackgroundWorker worker) {
|
||||
mWorker = worker;
|
||||
|
||||
ReportProgress("Preparing... build type is " + mBuildType + ", test files are " +
|
||||
(mCopyTestFiles ? "" : "NOT ") + "included.");
|
||||
|
||||
string buildStr = mBuildType.ToString();
|
||||
string basePath = FindBasePath();
|
||||
Debug.Assert(basePath != null);
|
||||
string distPath = Path.Combine(basePath, "DIST_" + buildStr);
|
||||
|
||||
// TODO(maybe): recursively delete distPath
|
||||
|
||||
if (!CopySpecList(sMainSpec, basePath, distPath, buildStr)) {
|
||||
return false;
|
||||
}
|
||||
if (mCopyTestFiles) {
|
||||
if (!CopySpecList(sTestSpec, basePath, distPath, buildStr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CopySpecList(CopySpec[] specList, string basePath, string distPath,
|
||||
string buildStr) {
|
||||
foreach (CopySpec cs in specList) {
|
||||
string srcDir = Path.GetFullPath(Path.Combine(basePath,
|
||||
cs.SourceDir.Replace("{BUILD_TYPE}", buildStr)));
|
||||
string dstDir = Path.GetFullPath(Path.Combine(distPath, cs.DestDir));
|
||||
|
||||
ReportProgress("Scanning [" + cs.FileSpec + "] " + srcDir);
|
||||
|
||||
if (!CopyBySpec(srcDir, dstDir, cs.FileSpec, cs.FileList, cs.IsRecursive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CopyBySpec(string srcDir, string dstDir, SourceFileSpec sfspec,
|
||||
string[] specFileList, bool isRecursive) {
|
||||
if (!EnsureDirectoryExists(dstDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] fileList;
|
||||
if (sfspec == SourceFileSpec.List) {
|
||||
fileList = specFileList;
|
||||
} else {
|
||||
fileList = Directory.GetFiles(srcDir);
|
||||
}
|
||||
|
||||
foreach (string str in fileList) {
|
||||
// Spec list is filenames, GetFiles is paths; convert to simple filename.
|
||||
string fileName = Path.GetFileName(str);
|
||||
|
||||
switch (sfspec) {
|
||||
case SourceFileSpec.All:
|
||||
case SourceFileSpec.List:
|
||||
// keep all
|
||||
break;
|
||||
case SourceFileSpec.NotBins:
|
||||
// Mostly this means "skip obj and bin dirs", which happens later.
|
||||
// Rather than specify everything we do want, just omit this one thing.
|
||||
if (fileName == "RuntimeData.csproj") {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case SourceFileSpec.AsmSources:
|
||||
if (!fileName.ToUpperInvariant().EndsWith(".S")) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case SourceFileSpec.RegressionTests:
|
||||
MatchCollection matches = sTestCaseRegex.Matches(fileName);
|
||||
if (matches.Count != 1) {
|
||||
continue;
|
||||
}
|
||||
// Could probably do this with regex... but why.
|
||||
if (fileName.StartsWith("1") && fileName.EndsWith(".dis65")) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unsupported spec " + sfspec);
|
||||
}
|
||||
|
||||
string srcPath = Path.Combine(srcDir, fileName);
|
||||
string dstPath = Path.Combine(dstDir, fileName);
|
||||
if (!CopyFile(srcPath, dstPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRecursive) {
|
||||
string[] dirList = Directory.GetDirectories(srcDir);
|
||||
|
||||
foreach (string str in dirList) {
|
||||
string dirFileName = Path.GetFileName(str);
|
||||
if (sfspec == SourceFileSpec.NotBins &&
|
||||
(dirFileName == "obj" || dirFileName == "bin")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CopyBySpec(Path.Combine(srcDir, dirFileName),
|
||||
Path.Combine(dstDir, dirFileName),
|
||||
sfspec, specFileList, isRecursive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EnsureDirectoryExists(string dirPath) {
|
||||
if (Directory.Exists(dirPath)) {
|
||||
return true;
|
||||
}
|
||||
if (File.Exists(dirPath)) {
|
||||
ReportErrMsg("File exists and is not directory: " + dirPath);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Directory.CreateDirectory(dirPath);
|
||||
ReportProgress(" Created " + dirPath);
|
||||
} catch (Exception ex) {
|
||||
ReportErrMsg("Failed creating directory " + dirPath + ": " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CopyFile(string srcPath, string dstPath) {
|
||||
// Poll cancel button.
|
||||
if (mWorker.CancellationPending) {
|
||||
ReportErrMsg("Cancel\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ReportProgress(" Copy " + srcPath + " --> " + dstPath);
|
||||
|
||||
try {
|
||||
File.Copy(srcPath, dstPath, true);
|
||||
} catch (Exception ex) {
|
||||
ReportErrMsg("Failed: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the base directory of the 6502bench installation.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string FindBasePath() {
|
||||
if (sBasePath != null) {
|
||||
return sBasePath;
|
||||
}
|
||||
|
||||
string exeName = Process.GetCurrentProcess().MainModule.FileName;
|
||||
string baseDir = Path.GetDirectoryName(exeName);
|
||||
if (string.IsNullOrEmpty(baseDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string tryPath;
|
||||
|
||||
// Use the SourceGen directory as a sentinel.
|
||||
tryPath = Path.Combine(baseDir, SOURCEGEN_DIRNAME);
|
||||
if (Directory.Exists(tryPath)) {
|
||||
sBasePath = Path.GetFullPath(tryPath);
|
||||
return sBasePath;
|
||||
}
|
||||
|
||||
string upThree = Path.GetDirectoryName(
|
||||
Path.GetDirectoryName(Path.GetDirectoryName(baseDir)));
|
||||
tryPath = Path.Combine(upThree, SOURCEGEN_DIRNAME);
|
||||
if (Directory.Exists(tryPath)) {
|
||||
sBasePath = Path.GetFullPath(upThree);
|
||||
return sBasePath;
|
||||
}
|
||||
|
||||
Debug.WriteLine("Unable to find RuntimeData dir near " + exeName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
162
MakeDist/MakeDist.Designer.cs
generated
Normal file
162
MakeDist/MakeDist.Designer.cs
generated
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
namespace MakeDist {
|
||||
partial class MakeDist {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.descriptionLabel = new System.Windows.Forms.Label();
|
||||
this.distributionTypeGroupBox = new System.Windows.Forms.GroupBox();
|
||||
this.releaseDistRadio = new System.Windows.Forms.RadioButton();
|
||||
this.debugDistRadio = new System.Windows.Forms.RadioButton();
|
||||
this.includeTestsCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this.goButton = new System.Windows.Forms.Button();
|
||||
this.cancelButton = new System.Windows.Forms.Button();
|
||||
this.distributionTypeGroupBox.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// descriptionLabel
|
||||
//
|
||||
this.descriptionLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.descriptionLabel.Location = new System.Drawing.Point(13, 13);
|
||||
this.descriptionLabel.Name = "descriptionLabel";
|
||||
this.descriptionLabel.Size = new System.Drawing.Size(371, 35);
|
||||
this.descriptionLabel.TabIndex = 0;
|
||||
this.descriptionLabel.Text = "This program gathers up all the files needed for a 6502bench distribution. A full" +
|
||||
" debug or release build should be performed before running this.";
|
||||
//
|
||||
// distributionTypeGroupBox
|
||||
//
|
||||
this.distributionTypeGroupBox.Controls.Add(this.releaseDistRadio);
|
||||
this.distributionTypeGroupBox.Controls.Add(this.debugDistRadio);
|
||||
this.distributionTypeGroupBox.Location = new System.Drawing.Point(16, 51);
|
||||
this.distributionTypeGroupBox.Name = "distributionTypeGroupBox";
|
||||
this.distributionTypeGroupBox.Size = new System.Drawing.Size(120, 67);
|
||||
this.distributionTypeGroupBox.TabIndex = 1;
|
||||
this.distributionTypeGroupBox.TabStop = false;
|
||||
this.distributionTypeGroupBox.Text = "Distribution Type";
|
||||
//
|
||||
// releaseDistRadio
|
||||
//
|
||||
this.releaseDistRadio.AutoSize = true;
|
||||
this.releaseDistRadio.Location = new System.Drawing.Point(7, 20);
|
||||
this.releaseDistRadio.Name = "releaseDistRadio";
|
||||
this.releaseDistRadio.Size = new System.Drawing.Size(64, 17);
|
||||
this.releaseDistRadio.TabIndex = 0;
|
||||
this.releaseDistRadio.TabStop = true;
|
||||
this.releaseDistRadio.Text = "Release";
|
||||
this.releaseDistRadio.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// debugDistRadio
|
||||
//
|
||||
this.debugDistRadio.AutoSize = true;
|
||||
this.debugDistRadio.Location = new System.Drawing.Point(7, 43);
|
||||
this.debugDistRadio.Name = "debugDistRadio";
|
||||
this.debugDistRadio.Size = new System.Drawing.Size(57, 17);
|
||||
this.debugDistRadio.TabIndex = 1;
|
||||
this.debugDistRadio.TabStop = true;
|
||||
this.debugDistRadio.Text = "Debug";
|
||||
this.debugDistRadio.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// includeTestsCheckBox
|
||||
//
|
||||
this.includeTestsCheckBox.AutoSize = true;
|
||||
this.includeTestsCheckBox.Location = new System.Drawing.Point(16, 125);
|
||||
this.includeTestsCheckBox.Name = "includeTestsCheckBox";
|
||||
this.includeTestsCheckBox.Size = new System.Drawing.Size(153, 17);
|
||||
this.includeTestsCheckBox.TabIndex = 2;
|
||||
this.includeTestsCheckBox.Text = "Include regression test files";
|
||||
this.includeTestsCheckBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// goButton
|
||||
//
|
||||
this.goButton.Location = new System.Drawing.Point(192, 59);
|
||||
this.goButton.Name = "goButton";
|
||||
this.goButton.Size = new System.Drawing.Size(88, 47);
|
||||
this.goButton.TabIndex = 3;
|
||||
this.goButton.Text = "BUILD";
|
||||
this.goButton.UseVisualStyleBackColor = true;
|
||||
this.goButton.Click += new System.EventHandler(this.goButton_Click);
|
||||
//
|
||||
// cancelButton
|
||||
//
|
||||
this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelButton.Location = new System.Drawing.Point(309, 120);
|
||||
this.cancelButton.Name = "cancelButton";
|
||||
this.cancelButton.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelButton.TabIndex = 4;
|
||||
this.cancelButton.Text = "Close";
|
||||
this.cancelButton.UseVisualStyleBackColor = true;
|
||||
this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
|
||||
//
|
||||
// MakeDist
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelButton;
|
||||
this.ClientSize = new System.Drawing.Size(396, 155);
|
||||
this.Controls.Add(this.cancelButton);
|
||||
this.Controls.Add(this.goButton);
|
||||
this.Controls.Add(this.includeTestsCheckBox);
|
||||
this.Controls.Add(this.distributionTypeGroupBox);
|
||||
this.Controls.Add(this.descriptionLabel);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "MakeDist";
|
||||
this.Text = "6502bench Distribution Maker";
|
||||
this.Load += new System.EventHandler(this.MakeDist_Load);
|
||||
this.distributionTypeGroupBox.ResumeLayout(false);
|
||||
this.distributionTypeGroupBox.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label descriptionLabel;
|
||||
private System.Windows.Forms.GroupBox distributionTypeGroupBox;
|
||||
private System.Windows.Forms.RadioButton releaseDistRadio;
|
||||
private System.Windows.Forms.RadioButton debugDistRadio;
|
||||
private System.Windows.Forms.CheckBox includeTestsCheckBox;
|
||||
private System.Windows.Forms.Button goButton;
|
||||
private System.Windows.Forms.Button cancelButton;
|
||||
}
|
||||
}
|
||||
|
47
MakeDist/MakeDist.cs
Normal file
47
MakeDist/MakeDist.cs
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2018 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace MakeDist {
|
||||
public partial class MakeDist : Form {
|
||||
public MakeDist() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void MakeDist_Load(object sender, EventArgs e) {
|
||||
releaseDistRadio.Checked = true;
|
||||
}
|
||||
|
||||
private void goButton_Click(object sender, EventArgs e) {
|
||||
FileCopier.BuildType buildType;
|
||||
if (releaseDistRadio.Checked) {
|
||||
buildType = FileCopier.BuildType.Release;
|
||||
} else {
|
||||
buildType = FileCopier.BuildType.Debug;
|
||||
}
|
||||