diff --git a/CommonUtil/TextUtil.cs b/CommonUtil/TextUtil.cs
index b595661..5930c72 100644
--- a/CommonUtil/TextUtil.cs
+++ b/CommonUtil/TextUtil.cs
@@ -238,5 +238,27 @@ namespace CommonUtil {
}
return arr;
}
+
+ ///
+ /// Converts a char[] to a string, inserting line numbers at the start of each line.
+ /// Assumes lines end with '\n' (with or without a preceding '\r').
+ ///
+ /// Character data to process.
+ /// String with line numbers.
+ public static string CharArrayToLineNumberedString(char[] data) {
+ StringBuilder sb = new StringBuilder(data.Length + data.Length / 40); // guess
+ int lineStart = 0;
+ int lineNum = 0;
+
+ for (int i = 0; i < data.Length; i++) {
+ if (data[i] == '\n') {
+ sb.AppendFormat("{0,4:D0} ", ++lineNum);
+ sb.Append(data, lineStart, i - lineStart + 1);
+ lineStart = i + 1;
+ }
+ }
+
+ return sb.ToString();
+ }
}
}
diff --git a/SourceGenWPF/AsmGen/WpfGui/GenAndAsm.xaml b/SourceGenWPF/AsmGen/WpfGui/GenAndAsm.xaml
new file mode 100644
index 0000000..e33fe2f
--- /dev/null
+++ b/SourceGenWPF/AsmGen/WpfGui/GenAndAsm.xaml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Assembler:
+
+
+
+ ONE
+ TWO
+
+
+
+
+
+
+ Preview file:
+
+
+
+ ONE
+ TWO
+
+
+
+
+ Work directory:
+
+
+
+
+
+ sample text1
+
+
+
+
+
+
+
+ Assembler not configured
+
+
+
+ sample text2
+ a
+ b
+
+
+
+
+
+
+
+
diff --git a/SourceGenWPF/AsmGen/WpfGui/GenAndAsm.xaml.cs b/SourceGenWPF/AsmGen/WpfGui/GenAndAsm.xaml.cs
new file mode 100644
index 0000000..2f137f3
--- /dev/null
+++ b/SourceGenWPF/AsmGen/WpfGui/GenAndAsm.xaml.cs
@@ -0,0 +1,376 @@
+/*
+ * Copyright 2019 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.IO;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+
+using CommonUtil;
+
+namespace SourceGenWPF.AsmGen.WpfGui {
+ ///
+ /// Code generation and assembler execution dialog.
+ ///
+ public partial class GenAndAsm : Window {
+ private const int PREVIEW_BUF_SIZE = 64 * 1024; // 64KB should be enough for preview
+ private static string NO_PREVIEW_FILES = "<" + Res.Strings.NO_FILES_AVAILABLE + ">";
+
+ ///
+ /// Holds data for the preview combo box.
+ ///
+ private class ComboPath {
+ public string FileName { get; private set; }
+ public string PathName { get; private set; }
+ public ComboPath(string pathName) {
+ PathName = pathName;
+ if (string.IsNullOrEmpty(pathName)) {
+ FileName = NO_PREVIEW_FILES;
+ } else {
+ FileName = Path.GetFileName(pathName);
+ }
+ }
+ public override string ToString() {
+ return FileName;
+ }
+ }
+
+ ///
+ /// Project with data.
+ ///
+ private DisasmProject mProject;
+
+ ///
+ /// Directory where generated files and assembler output will go.
+ ///
+ private string mWorkDirectory;
+
+ ///
+ /// Base file name. For example, if this is "GenFile", we might generate
+ /// "GenFile_Cc65.S".
+ ///
+ private string mBaseFileName;
+
+ ///
+ /// Currently-selected assembler ID.
+ ///
+ private AssemblerInfo.Id mSelectedAssemblerId;
+
+ ///
+ /// Results from last source generation.
+ ///
+ private List mGenerationResults;
+
+ ///
+ /// Holds an item for the pick-your-assembler combox box.
+ ///
+ private class AsmComboItem {
+ public AssemblerInfo.Id AssemblerId { get; private set; }
+ public string Name { get; private set; }
+ public AssemblerVersion AsmVersion { get; private set; }
+
+ public AsmComboItem(AssemblerInfo info, AssemblerVersion version) {
+ AssemblerId = info.AssemblerId;
+ Name = info.Name;
+ AsmVersion = version;
+ }
+ // This determines what the combo box shows.
+ public override string ToString() {
+ if (AsmVersion == null) {
+ return Name + " " + Res.Strings.ASM_LATEST_VERSION;
+ } else {
+ return Name + " v" + AsmVersion.VersionStr;
+ }
+ }
+ }
+
+
+ ///
+ /// Constructor.
+ ///
+ /// Project reference.
+ /// Full path to the project file.
+ public GenAndAsm(Window owner, DisasmProject project, string projectPathName) {
+ InitializeComponent();
+ Owner = owner;
+
+ mProject = project;
+ mWorkDirectory = Path.GetDirectoryName(projectPathName);
+ mBaseFileName = Path.GetFileNameWithoutExtension(projectPathName);
+
+ workDirectoryTextBox.Text = mWorkDirectory;
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e) {
+ // Try to select the previously-used asm format.
+ string defaultAsm =
+ AppSettings.Global.GetString(AppSettings.SRCGEN_DEFAULT_ASM, string.Empty);
+ PopulateAssemblerComboBox(defaultAsm);
+
+ ResetElements();
+ }
+
+ ///
+ /// Populates the assembler combo box. Attempts to match the defaultAsm arg with
+ /// the entries to configure the initial value.
+ ///
+ private void PopulateAssemblerComboBox(string defaultAsm) {
+ //assemblerComboBox.DisplayMember = "Name"; // show this property
+
+ assemblerComboBox.Items.Clear();
+ IEnumerator iter = AssemblerInfo.GetInfoEnumerator();
+ bool foundMatch = false;
+ while (iter.MoveNext()) {
+ AssemblerInfo info = iter.Current;
+ AssemblerVersion version = AssemblerVersionCache.GetVersion(info.AssemblerId);
+ AsmComboItem item = new AsmComboItem(info, version);
+ assemblerComboBox.Items.Add(item);
+ if (item.AssemblerId.ToString() == defaultAsm) {
+ Debug.WriteLine("matched current " + defaultAsm);
+ assemblerComboBox.SelectedItem = item;
+ foundMatch = true;
+ }
+ }
+ if (!foundMatch) {
+ // Need to do this or box will show empty.
+ assemblerComboBox.SelectedIndex = 0;
+ }
+ }
+
+ ///
+ /// Updates the selected assembler as the combo box selection changes. This is
+ /// expected to be called during the window load event, to initialize the field.
+ ///
+ private void AssemblerComboBox_SelectionChanged(object sender,
+ SelectionChangedEventArgs e) {
+ AsmComboItem sel = (AsmComboItem)assemblerComboBox.SelectedItem;
+ if (sel == null) {
+ // this happens on Items.Clear()
+ return;
+ }
+ if (mSelectedAssemblerId != sel.AssemblerId) {
+ // Selection changed, discard window contents.
+ mSelectedAssemblerId = sel.AssemblerId;
+ AppSettings.Global.SetString(AppSettings.SRCGEN_DEFAULT_ASM,
+ mSelectedAssemblerId.ToString());
+ ResetElements();
+ }
+ }
+
+ ///
+ /// Loads the appropriate preview file when the combo box selection changes.
+ ///
+ private void PreviewFileComboBox_SelectionChanged(object sender,
+ SelectionChangedEventArgs e) {
+ ComboPath cpath = (ComboPath)previewFileComboBox.SelectedItem;
+ if (cpath == null || string.IsNullOrEmpty(cpath.PathName)) {
+ // nothing to do
+ return;
+ }
+
+ LoadPreviewFile(cpath.PathName);
+ }
+
+ ///
+ /// Resets all of the active elements to the initial state, before any source code
+ /// was generated.
+ ///
+ private void ResetElements() {
+ mGenerationResults = null;
+ previewFileComboBox.Items.Clear();
+ previewFileComboBox.Items.Add(new ComboPath(null));
+ previewFileComboBox.SelectedIndex = 0;
+
+ previewTextBox.Text = string.Empty;
+
+ cmdOutputTextBox.Text = string.Empty;
+
+ UpdateAssemblerControls();
+ }
+
+ ///
+ /// Updates the controls in the lower (assembler) half of the dialog.
+ ///
+ private void UpdateAssemblerControls() {
+ bool asmConf = IsAssemblerConfigured();
+ //Debug.WriteLine("ID=" + mSelectedAssemblerId + " asmConf=" + asmConf);
+ asmNotConfiguredText.Visibility = asmConf ? Visibility.Hidden : Visibility.Visible;
+ if (mGenerationResults == null || !asmConf) {
+ runAssemblerButton.IsEnabled = false;
+ } else {
+ runAssemblerButton.IsEnabled = true;
+ }
+ }
+
+ ///
+ /// Returns true if the selected cross-assembler executable has been configured.
+ ///
+ private bool IsAssemblerConfigured() {
+ AssemblerConfig config =
+ AssemblerConfig.GetConfig(AppSettings.Global, mSelectedAssemblerId);
+ return config != null && !string.IsNullOrEmpty(config.ExecutablePath);
+ }
+
+ private void AssemblerSettingsButton_Click(object sender, RoutedEventArgs e) {
+ // Pop open the app settings dialog, with the appropriate tab selected.
+#if false
+ mMainCtrl.ShowAppSettings(AppForms.EditAppSettings.Tab.AsmConfig,
+ mSelectedAssemblerId);
+#endif
+
+ // Update the controls based on whether or not the assembler is now available.
+ UpdateAssemblerControls();
+ AsmComboItem item = (AsmComboItem)assemblerComboBox.SelectedItem;
+ Debug.Assert(item != null);
+ PopulateAssemblerComboBox(item.AssemblerId.ToString());
+ }
+
+ private void GenerateButton_Click(object sender, RoutedEventArgs e) {
+#if false
+ IGenerator gen = AssemblerInfo.GetGenerator(mSelectedAssemblerId);
+ if (gen == null) {
+ Debug.WriteLine("Unable to get generator for " + mSelectedAssemblerId);
+ return;
+ }
+ gen.Configure(mProject, mWorkDirectory, mBaseFileName,
+ AssemblerVersionCache.GetVersion(mSelectedAssemblerId), AppSettings.Global);
+
+ GeneratorProgress dlg = new GeneratorProgress(gen);
+ dlg.ShowDialog();
+ Debug.WriteLine("Dialog returned: " + dlg.DialogResult);
+
+ List pathNames = dlg.Results;
+ dlg.Dispose();
+
+ if (pathNames == null) {
+ // errors already reported
+ return;
+ }
+
+ ResetElements();
+ mGenerationResults = pathNames;
+ previewFileComboBox.Items.Clear();
+ foreach (string str in pathNames) {
+ previewFileComboBox.Items.Add(new ComboPath(str));
+ }
+ previewFileComboBox.SelectedIndex = 0; // should trigger update
+
+ UpdateAssemblerControls();
+#else
+ Debug.WriteLine("GENERATE");
+#endif
+ }
+
+ private void LoadPreviewFile(string pathName) {
+ Debug.WriteLine("LOAD " + pathName);
+
+ try {
+ using (StreamReader sr = new StreamReader(pathName, Encoding.UTF8)) {
+ char[] bigbuf = new char[PREVIEW_BUF_SIZE];
+ int actual = sr.Read(bigbuf, 0, bigbuf.Length);
+ string str = TextUtil.CharArrayToLineNumberedString(bigbuf);
+ if (actual < PREVIEW_BUF_SIZE) {
+ previewTextBox.Text = str;
+ } else {
+ previewTextBox.Text = str + "\r\n" +
+ Res.Strings.ERR_TOO_LARGE_FOR_PREVIEW;
+ }
+ }
+ } catch (Exception ex) {
+ previewTextBox.Text = ex.ToString();
+ }
+ }
+
+ private void RunAssemblerButton_Click(object sender, RoutedEventArgs e) {
+#if false
+ IAssembler asm = AssemblerInfo.GetAssembler(mSelectedAssemblerId);
+ if (asm == null) {
+ Debug.WriteLine("Unable to get assembler for " + mSelectedAssemblerId);
+ return;
+ }
+
+ asm.Configure(mGenerationResults, mWorkDirectory);
+ AssemblerProgress dlg = new AssemblerProgress(asm);
+ dlg.ShowDialog();
+ Debug.WriteLine("Dialog returned: " + dlg.DialogResult);
+ if (dlg.DialogResult != DialogResult.OK) {
+ // Cancelled, or failed to even run the assembler.
+ return;
+ }
+
+ AssemblerResults results = dlg.Results;
+ if (results == null) {
+ Debug.WriteLine("Dialog returned OK, but no assembler results found");
+ Debug.Assert(false);
+ return;
+ }
+
+ StringBuilder sb =
+ new StringBuilder(results.Stdout.Length + results.Stderr.Length + 200);
+ sb.Append(results.CommandLine);
+ sb.Append("\r\n");
+ sb.AppendFormat("ExitCode={0} - ", results.ExitCode);
+ if (results.ExitCode == 0) {
+ FileInfo fi = new FileInfo(results.OutputPathName);
+ if (!fi.Exists) {
+ MessageBox.Show(this, Properties.Resources.ASM_OUTPUT_NOT_FOUND,
+ Properties.Resources.ASM_MISMATCH_CAPTION,
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ sb.Append(Properties.Resources.ASM_MATCH_FAILURE);
+ } else if (!CommonUtil.FileUtil.CompareBinaryFile(mProject.FileData,
+ results.OutputPathName, out int offset, out byte fileVal)) {
+ if (fi.Length != mProject.FileData.Length &&
+ offset == fi.Length || offset == mProject.FileData.Length) {
+ // The files matched up to the point where one ended.
+ string msg = string.Format(Properties.Resources.ASM_MISMATCH_LENGTH_FMT,
+ fi.Length, mProject.FileData.Length);
+ MessageBox.Show(this, msg, Properties.Resources.ASM_MISMATCH_CAPTION,
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ sb.Append(msg);
+ } else {
+ string msg = string.Format(Properties.Resources.ASM_MISMATCH_DATA_FMT,
+ offset, fileVal, mProject.FileData[offset]);
+ MessageBox.Show(this, msg, Properties.Resources.ASM_MISMATCH_CAPTION,
+ MessageBoxButtons.OK, MessageBoxIcon.Error);
+ sb.Append(msg);
+ }
+ } else {
+ sb.Append(Properties.Resources.ASM_MATCH_SUCCESS);
+ }
+ }
+ sb.Append("\r\n\r\n");
+
+ if (results.Stdout != null && results.Stdout.Length > 2) {
+ sb.Append("----- stdout -----\r\n");
+ sb.Append(results.Stdout);
+ sb.Append("\r\n");
+ }
+ if (results.Stderr != null && results.Stderr.Length > 2) {
+ sb.Append("----- stderr -----\r\n");
+ sb.Append(results.Stderr);
+ sb.Append("\r\n");
+ }
+
+ cmdOutputTextBox.Text = sb.ToString();
+ cmdOutputTextBox.BackColor = SystemColors.Window;
+#else
+ Debug.WriteLine("ASSEMBLE");
+#endif
+ }
+ }
+}
diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs
index 0ac8610..6f29952 100644
--- a/SourceGenWPF/MainController.cs
+++ b/SourceGenWPF/MainController.cs
@@ -1130,6 +1130,24 @@ namespace SourceGenWPF {
return mProject != null;
}
+ public void AssembleProject() {
+ if (string.IsNullOrEmpty(mProjectPathName)) {
+ // We need a project pathname so we know where to write the assembler
+ // source files, and what to call the output files. We could just pop up the
+ // Save As dialog, but that seems confusing unless we do a custom dialog with
+ // an explanation, or have some annoying click-through.
+ //
+ // This only appears for never-saved projects, not projects with unsaved data.
+ MessageBox.Show(Res.Strings.SAVE_BEFORE_ASM, Res.Strings.SAVE_BEFORE_ASM_CAPTION,
+ MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ AsmGen.WpfGui.GenAndAsm dlg =
+ new AsmGen.WpfGui.GenAndAsm(mMainWin, mProject, mProjectPathName);
+ dlg.ShowDialog();
+ }
+
public void HandleCodeListDoubleClick(int row, int col) {
Debug.WriteLine("DCLICK: row=" + row + " col=" + col);
diff --git a/SourceGenWPF/Res/Strings.xaml b/SourceGenWPF/Res/Strings.xaml
index a0f63c7..31afc2b 100644
--- a/SourceGenWPF/Res/Strings.xaml
+++ b/SourceGenWPF/Res/Strings.xaml
@@ -19,6 +19,7 @@ limitations under the License.
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:SourceGenWPF.Res">
+ [latest version]
Default
Bad format descriptor at +{0:x6}.
Bad format descriptor type
@@ -41,6 +42,7 @@ limitations under the License.
Project file may be corrupt
Unable to load project file
Unable to save project file
+ [File was too large for preview window]
All files (*.*)|*.*
C# Source Files(*.cs)|*.cs
SourceGen projects(*.dis65)|*.dis65
@@ -54,6 +56,7 @@ limitations under the License.
Extension scripts:
Default settings:
Symbol files:
+ no files available
The file doesn't exist.
File is empty
Unable to load data file
@@ -79,5 +82,7 @@ limitations under the License.
The RuntimeData directory was not found. It should be in the same directory as the executable.
RuntimeData Not Found
+ Please save your project before assembling. The generated source code will be placed in the same directory as the project file.
+ Save Project First
{1} CPU @ {2} MHz
\ No newline at end of file
diff --git a/SourceGenWPF/Res/Strings.xaml.cs b/SourceGenWPF/Res/Strings.xaml.cs
index fa0e70a..9b816ff 100644
--- a/SourceGenWPF/Res/Strings.xaml.cs
+++ b/SourceGenWPF/Res/Strings.xaml.cs
@@ -18,6 +18,8 @@ using System.Windows;
namespace SourceGenWPF.Res {
public static class Strings {
+ public static string ASM_LATEST_VERSION =
+ (string)Application.Current.FindResource("str_AsmLatestVersion");
public static string DEFAULT_VALUE =
(string)Application.Current.FindResource("str_DefaultValue");
public static string ERR_BAD_FD =
@@ -62,6 +64,8 @@ namespace SourceGenWPF.Res {
(string)Application.Current.FindResource("str_ErrProjectLoadFail");
public static string ERR_PROJECT_SAVE_FAIL =
(string)Application.Current.FindResource("str_ErrProjectSaveFail");
+ public static string ERR_TOO_LARGE_FOR_PREVIEW =
+ (string)Application.Current.FindResource("str_ErrTooLargeForPreview");
public static string FILE_FILTER_ALL =
(string)Application.Current.FindResource("str_FileFilterAll");
public static string FILE_FILTER_CS =
@@ -88,6 +92,8 @@ namespace SourceGenWPF.Res {
(string)Application.Current.FindResource("str_InitialParameters");
public static string INITIAL_SYMBOL_FILES =
(string)Application.Current.FindResource("str_InitialSymbolFiles");
+ public static string NO_FILES_AVAILABLE =
+ (string)Application.Current.FindResource("str_NoFilesAvailable");
public static string OPEN_DATA_DOESNT_EXIST =
(string)Application.Current.FindResource("str_OpenDataDoesntExist");
public static string OPEN_DATA_EMPTY =
@@ -138,6 +144,10 @@ namespace SourceGenWPF.Res {
(string)Application.Current.FindResource("str_RuntimeDirNotFound");
public static string RUNTIME_DIR_NOT_FOUND_CAPTION =
(string)Application.Current.FindResource("str_RuntimeDirNotFoundCaption");
+ public static string SAVE_BEFORE_ASM =
+ (string)Application.Current.FindResource("str_SaveBeforeAsm");
+ public static string SAVE_BEFORE_ASM_CAPTION =
+ (string)Application.Current.FindResource("str_SaveBeforeAsmCaption");
public static string SETUP_SYSTEM_SUMMARY_FMT =
(string)Application.Current.FindResource("str_SetupSystemSummaryFmt");
}
diff --git a/SourceGenWPF/SourceGenWPF.csproj b/SourceGenWPF/SourceGenWPF.csproj
index 897816b..051146e 100644
--- a/SourceGenWPF/SourceGenWPF.csproj
+++ b/SourceGenWPF/SourceGenWPF.csproj
@@ -73,6 +73,9 @@
+
+ GenAndAsm.xaml
+
@@ -158,6 +161,10 @@
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
diff --git a/SourceGenWPF/WpfGui/MainWindow.xaml.cs b/SourceGenWPF/WpfGui/MainWindow.xaml.cs
index 7e62f40..7896395 100644
--- a/SourceGenWPF/WpfGui/MainWindow.xaml.cs
+++ b/SourceGenWPF/WpfGui/MainWindow.xaml.cs
@@ -718,8 +718,7 @@ namespace SourceGenWPF.WpfGui {
#region Command handlers
private void AssembleCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
- // test
- Debug.WriteLine("assembling");
+ mMainCtrl.AssembleProject();
}
private void CloseCmd_Executed(object sender, ExecutedRoutedEventArgs e) {