Răsfoiți Sursa

Code generator support

Brian Fiete 3 ani în urmă
părinte
comite
73099e4a04

+ 3 - 0
BeefLibs/Beefy2D/src/widgets/Dialog.bf

@@ -256,7 +256,10 @@ namespace Beefy.widgets
         public virtual void PopupWindow(WidgetWindow parentWindow, float offsetX = 0, float offsetY = 0)
         {
             if (mClosed)
+			{
+				BFApp.sApp.DeferDelete(this);
                 return;
+			}
 
             mInPopupWindow = true;
 

+ 181 - 1
BeefLibs/corlib/src/Compiler.bf

@@ -1,8 +1,188 @@
 using System.Reflection;
+using System.Diagnostics;
+using System.Collections;
+using System.Security.Cryptography;
+
 namespace System
 {
 	static class Compiler
 	{
+		public abstract class Generator
+		{
+			public enum Flags
+			{
+				None = 0,
+				AllowRegenerate = 1
+			}
+
+			public String mCmdInfo = new String() ~ delete _;
+			public Dictionary<StringView, StringView> mParams = new .() ~ delete _;
+			public abstract String Name { get; }
+
+			public StringView ProjectName => mParams["ProjectName"];
+			public StringView ProjectDir => mParams["ProjectDir"];
+			public StringView FolderDir => mParams["FolderDir"];
+			public StringView Namespace => mParams["Namespace"];
+			public StringView DefaultNamespace => mParams["DefaultNamespace"];
+			public StringView WorkspaceName => mParams["WorkspaceName"];
+			public StringView WorkspaceDir => mParams["WorkspaceDir"];
+			public StringView DateTime => mParams["DateTime"];
+
+			public void Fail(StringView error)
+			{
+				mCmdInfo.AppendF("error\t");
+				error.QuoteString(mCmdInfo);
+				mCmdInfo.Append("\n");
+			}
+
+			public void AddEdit(StringView dataName, StringView label, StringView defaultValue)
+			{
+				mCmdInfo.AppendF($"addEdit\t");
+				dataName.QuoteString(mCmdInfo);
+				mCmdInfo.Append("\t");
+				label.QuoteString(mCmdInfo);
+				mCmdInfo.Append("\t");
+				defaultValue.QuoteString(mCmdInfo);
+				mCmdInfo.Append("\n");
+			}
+
+			public void AddCombo(StringView dataName, StringView label, StringView defaultValue, Span<StringView> values)
+			{
+				mCmdInfo.AppendF($"addCombo\t");
+				dataName.QuoteString(mCmdInfo);
+				mCmdInfo.Append("\t");
+				label.QuoteString(mCmdInfo);
+				mCmdInfo.Append("\t");
+				defaultValue.QuoteString(mCmdInfo);
+				for (var value in values)
+				{
+					mCmdInfo.Append("\t");
+					value.QuoteString(mCmdInfo);
+				}
+				mCmdInfo.Append("\n");
+			}
+
+			public void AddCheckbox(StringView dataName, StringView label, bool defaultValue)
+			{
+				mCmdInfo.AppendF($"addCheckbox\t");
+				dataName.QuoteString(mCmdInfo);
+				mCmdInfo.Append("\t");
+				label.QuoteString(mCmdInfo);
+				mCmdInfo.AppendF($"\t{defaultValue}\n");
+			}
+
+			public bool GetString(StringView key, String outVal)
+			{
+				if (mParams.TryGetAlt(key, var matchKey, var value))
+				{
+					outVal.Append(value);
+					return true;
+				}
+				return false;
+			}
+
+			public virtual void InitUI()
+			{
+			}
+
+			public virtual void Generate(String outFileName, String outText, ref Flags generateFlags)
+			{
+			}
+
+			static String GetName<T>() where T : Generator
+			{
+				T val = scope T();
+				String str = val.Name;
+				return str;
+			}
+
+			void HandleArgs(String args)
+			{
+				for (var line in args.Split('\n', .RemoveEmptyEntries))
+				{
+					int tabPos = line.IndexOf('\t');
+					var key = line.Substring(0, tabPos);
+					var value = line.Substring(tabPos + 1);
+					if (mParams.TryAdd(key, var keyPtr, var valuePtr))
+					{
+						*keyPtr = key;
+						*valuePtr = value;
+					}
+				}
+			}
+
+			static String InitUI<T>(String args) where T : Generator
+			{
+				T val = scope T();
+				val.HandleArgs(args);
+				val.InitUI();
+				return val.mCmdInfo;
+			}
+
+			static String Generate<T>(String args) where T : Generator
+			{
+				T val = scope T();
+				val.HandleArgs(args);
+				String fileName = scope .();
+				String outText = scope .();
+				Flags flags = .None;
+				val.Generate(fileName, outText, ref flags);
+				val.mCmdInfo.Append("fileName\t");
+				fileName.QuoteString(val.mCmdInfo);
+				val.mCmdInfo.Append("\n");
+				val.mCmdInfo.Append("data\n");
+
+				if (flags.HasFlag(.AllowRegenerate))
+				{
+					bool writeArg = false;
+					for (var line in args.Split('\n', .RemoveEmptyEntries))
+					{
+						int tabPos = line.IndexOf('\t');
+						var key = line.Substring(0, tabPos);
+						var value = line.Substring(tabPos + 1);
+
+						if (key == "Generator")
+							writeArg = true;
+						if (writeArg)
+						{
+							val.mCmdInfo.AppendF($"// {key}={value}\n");
+						}
+					}
+					var hash = MD5.Hash(.((.)outText.Ptr, outText.Length));
+					val.mCmdInfo.AppendF($"// GenHash={hash}\n\n");
+				}
+				val.mCmdInfo.Append(outText);
+				return val.mCmdInfo;
+			}
+		}
+
+		public class NewClassGenerator : Generator
+		{
+			public override String Name => "New Class";
+			
+			public override void InitUI()
+			{
+				AddEdit("name", "Class Name", "");
+			}
+
+			public override void Generate(String outFileName, String outText, ref Flags generateFlags)
+			{
+				var name = mParams["name"];
+				if (name.EndsWith(".bf", .OrdinalIgnoreCase))
+					name.RemoveFromEnd(3);
+				outFileName.Append(name);
+				outText.AppendF(
+					$"""
+					namespace {Namespace}
+					{{
+						class {name}
+						{{
+						}}
+					}}
+					""");
+			}
+		}
+
 		public struct MethodBuilder
 		{
 			void* mNative;
@@ -43,7 +223,7 @@ namespace System
 		public static extern String CallerProject;
 
 		[LinkName("#CallerExpression")]
-		public static extern String[0x0FFFFFFF] CallerExpression;
+		public static extern String[0x00FFFFFF] CallerExpression;
 
 		[LinkName("#ProjectName")]
 		public static extern String ProjectName;

+ 12 - 4
IDE/src/CommandQueueManager.bf

@@ -15,9 +15,10 @@ namespace IDE
 		public Action mOnThreadDone ~ delete _;
 		[ThreadStatic]
 		public static bool mBpSetThreadName;
+		public bool mAllowFastFinish;
 		WaitEvent mWaitEvent = new WaitEvent() ~ delete _;
 
-		public void DoBackground(ThreadStart threadStart, Action onThreadDone = null, int maxWait = 0)
+		public void DoBackground(ThreadStart threadStart, Action onThreadDone = null, int maxWait = 0, bool allowFastFinish = true)
 		{
 		    Debug.Assert(Thread.CurrentThread == IDEApp.sApp.mMainThread);
 
@@ -26,6 +27,7 @@ namespace IDE
 
 			BeefPerf.Event("DoBackground:starting", "");
 
+			mAllowFastFinish = allowFastFinish;
 		    mOnThreadDone = onThreadDone;
 		    mThreadRunning = true;
 			mWaitEvent.Reset();
@@ -47,6 +49,7 @@ namespace IDE
 				delete threadStart;
 		        mThread = null;
 		        mThreadRunning = false;
+				mAllowFastFinish = true;
 				BeefPerf.Event("DoBackground:threadEnd", "");
 
 				mWaitEvent.Set();
@@ -132,7 +135,7 @@ namespace IDE
 
         }
 
-		public virtual void RequestFastFinish()
+		public virtual void RequestFastFinish(bool force = false)
 		{
 
 		}
@@ -190,15 +193,20 @@ namespace IDE
             return (mThreadWorker.mThreadRunning || mThreadWorkerHi.mThreadRunning);
         }
 
+		public bool IsPerformingBackgroundOperationHi()
+		{
+		    return (mThreadWorkerHi.mThreadRunning);
+		}
+
         public void DoBackground(ThreadStart threadStart, Action onThreadDone = null, int maxWait = 0)
         {
 			CancelBackground();
             mThreadWorker.DoBackground(threadStart, onThreadDone, maxWait);
         }
 
-		public void DoBackgroundHi(ThreadStart threadStart, Action onThreadDone = null)
+		public void DoBackgroundHi(ThreadStart threadStart, Action onThreadDone = null, bool allowFastFinish = true)
 		{
-		 	mThreadWorkerHi.DoBackground(threadStart, onThreadDone);
+		 	mThreadWorkerHi.DoBackground(threadStart, onThreadDone, 0, allowFastFinish);
 		}
 
 		public void CheckThreadDone()

+ 31 - 3
IDE/src/Compiler/BfCompiler.bf

@@ -94,6 +94,15 @@ namespace IDE.Compiler
 		[CallingConvention(.Stdcall), CLink]
 		static extern char8* BfCompiler_GetUsedOutputFileNames(void* bfCompiler, void* bfProject, bool flushQueuedHotFiles, out bool hadOutputChanges);
 
+		[CallingConvention(.Stdcall), CLink]
+		static extern char8* BfCompiler_GetGeneratorTypeDefList(void* bfCompiler);
+
+		[CallingConvention(.Stdcall), CLink]
+		static extern char8* BfCompiler_GetGeneratorInitData(void* bfCompiler, char8* typeDefName, char8* args);
+
+		[CallingConvention(.Stdcall), CLink]
+		static extern char8* BfCompiler_GetGeneratorGenData(void* bfCompiler, char8* typeDefName, char8* args);
+
 		[CallingConvention(.Stdcall), CLink]
 		static extern char8* BfCompiler_GetTypeDefList(void* bfCompiler);
 
@@ -674,17 +683,21 @@ namespace IDE.Compiler
 
         public override void RequestCancelBackground()
         {
-            if ([Friend]mThreadWorker.mThreadRunning)
+            if (mThreadWorker.mThreadRunning)
             {
 				if ((mNativeBfCompiler != null) && (!gApp.mDeterministic))
                 	BfCompiler_Cancel(mNativeBfCompiler);
             }
         }
 
-		public override void RequestFastFinish()
+		public override void RequestFastFinish(bool force = false)
 		{
-		    if ([Friend]mThreadWorker.mThreadRunning || [Friend]mThreadWorkerHi.mThreadRunning)
+		    if (mThreadWorker.mThreadRunning || mThreadWorkerHi.mThreadRunning)
 		    {
+				if ((!force) &&
+					((!mThreadWorker.mAllowFastFinish) || (!mThreadWorkerHi.mAllowFastFinish)))
+					return;
+
 				if ((mNativeBfCompiler != null) && (!gApp.mDeterministic))
 		        	BfCompiler_RequestFastFinish(mNativeBfCompiler);
 		    }
@@ -710,6 +723,21 @@ namespace IDE.Compiler
 			return BfCompiler_GetCurConstEvalExecuteId(mNativeBfCompiler);
 		}
 
+		public void GetGeneratorTypeDefList(String outStr)
+		{
+			outStr.Append(BfCompiler_GetGeneratorTypeDefList(mNativeBfCompiler));
+		}
+
+		public void GetGeneratorInitData(String typeDefName, String args, String outStr)
+		{
+			outStr.Append(BfCompiler_GetGeneratorInitData(mNativeBfCompiler, typeDefName, args));
+		}
+
+		public void GetGeneratorGenData(String typeDefName, String args, String outStr)
+		{
+			outStr.Append(BfCompiler_GetGeneratorGenData(mNativeBfCompiler, typeDefName, args));
+		}
+
 		public void GetTypeDefList(String outStr)
 		{
 			outStr.Append(BfCompiler_GetTypeDefList(mNativeBfCompiler));

+ 2 - 0
IDE/src/Project.bf

@@ -37,6 +37,7 @@ namespace IDE
         public ProjectFolder mParentFolder;
         public String mName = new String() ~ delete _;
         public String mComment = new String() ~ delete _;
+		public bool mDetached;
 
 		public virtual bool IncludeInMap
 		{
@@ -105,6 +106,7 @@ namespace IDE
 
 		public virtual void Detach()
 		{
+			mDetached = true;
 			ReleaseRef();
 		}
     }

+ 4 - 0
IDE/src/ui/ClassViewPanel.bf

@@ -598,10 +598,14 @@ namespace IDE.ui
 				}
 			}
 
+			var bfSystem = gApp.mBfResolveSystem;
+
 			String typeName = scope .();
 			GetName(item, typeName);
 			String info = scope .();
+			bfSystem.Lock(0);
 			gApp.mBfResolveCompiler.GetTypeDefInfo(typeName, info);
+			bfSystem.Unlock();
 
 			for (let str in info.Split('\n'))
 			{

+ 869 - 0
IDE/src/ui/GenerateDialog.bf

@@ -0,0 +1,869 @@
+using Beefy.theme.dark;
+using Beefy.widgets;
+using System;
+using System.Collections;
+using Beefy.gfx;
+using Beefy.events;
+using IDE.Compiler;
+using System.Threading;
+using System.Security.Cryptography;
+using Beefy.theme;
+
+namespace IDE.ui
+{
+	class GenerateListView : DarkListView
+	{
+		public GenerateDialog mNewClassDialog;
+	}
+
+	class GenerateKindBar : DarkComboBox
+	{
+	    public class Entry
+		{
+			public String mTypeName ~ delete _;
+			public String mName ~ delete _;
+		}
+
+	    public static Dictionary<String, int32> sMRU = new Dictionary<String, int32>() ~ delete _;
+	    public static int32 sCurrentMRUIndex = 1;
+
+		public GenerateDialog mNewClassDialog;
+	    public List<Entry> mEntries = new List<Entry>() ~ DeleteContainerAndItems!(_);
+	    public List<Entry> mShownEntries = new List<Entry>() ~ delete _;
+	    public String mFilterString ~ delete _;
+		public String mCurLocation = new String() ~ delete _;
+		public bool mIgnoreChange = false;
+
+	    public this(GenerateDialog dialog)
+	    {
+			mNewClassDialog = dialog;
+	        mLabelAlign = FontAlign.Left;
+	        Label = "";
+	        mLabelX = GS!(16);
+	        mPopulateMenuAction.Add(new => PopulateNavigationBar);
+	        MakeEditable();
+	        mEditWidget.mOnContentChanged.Add(new => NavigationBarChanged);
+			mEditWidget.mOnGotFocus.Add(new (widget) => mEditWidget.mEditWidgetContent.SelectAll());
+			mEditWidget.mEditWidgetContent.mWantsUndo = false;
+			mEditWidget.mOnSubmit.Add(new => mNewClassDialog.[Friend]EditSubmitHandler);
+			mFocusDropdown = false;
+	    }
+
+		public ~this()
+		{
+		}
+
+		static ~this()
+		{
+			for (var key in sMRU.Keys)
+				delete key;
+		}
+
+		public void SetLocation(String location)
+		{
+			if (mCurMenuWidget == null)
+			{
+				mIgnoreChange = true;
+				mEditWidget.SetText(location);
+				mEditWidget.mEditWidgetContent.SelectAll();
+				// SetText can attempt to scroll to the right to make the cursor position visible.  Just scroll back to the start.
+				mEditWidget.HorzScrollTo(0);
+				//mNewClassDialog.SelectKind();
+				mIgnoreChange = false;
+			}
+		}
+	    private void PopulateNavigationBar(Menu menu)
+	    {
+			List<StringView> findStrs = null;
+			if (mFilterString != null)
+			 	findStrs = scope:: List<StringView>(mFilterString.Split(' '));
+
+	        EntryLoop: for (int32 entryIdx = 0; entryIdx < mEntries.Count; entryIdx++)            
+	        {
+	            var entry = mEntries[entryIdx];
+				if (findStrs != null)
+				{
+					for (let findStr in findStrs)
+					{
+					    if (entry.mName.IndexOf(findStr, true) == -1)
+					        continue EntryLoop;
+					}
+				}
+
+	            mShownEntries.Add(entry);
+	            var menuItem = menu.AddItem(entry.mName);
+	            menuItem.mOnMenuItemSelected.Add(new (evt) =>
+					{
+						mNewClassDialog.mPendingUIFocus = true;
+						ShowEntry(entryIdx, entry);
+					});
+	        }
+	    }		
+
+	    void ShowEntry(int32 entryIdx, Entry entry)
+	    {
+	        mEditWidget.SetText(entry.mName);
+			mEditWidget.mEditWidgetContent.SelectAll();
+			mCurMenuWidget?.Close();
+	    }
+
+	    private void NavigationBarChanged(EditEvent theEvent)
+	    {
+			if (mIgnoreChange)
+				return;
+
+	        var editWidget = (EditWidget)theEvent.mSender;
+	        var searchText = scope String();
+	        editWidget.GetText(searchText);
+			searchText.Trim();
+	        mFilterString = searchText;
+	        ShowDropdown();
+	        mFilterString = null;
+	    }
+
+		bool mIgnoreShowDropdown;
+
+	    public override MenuWidget ShowDropdown()
+	    {
+			if (mIgnoreShowDropdown)
+				return null;
+			mIgnoreShowDropdown = true;
+			defer { mIgnoreShowDropdown = false; }
+
+	        if (!mEditWidget.mHasFocus)
+	            SetFocus();
+
+	        if (mFilterString == null)
+	            mEditWidget.Content.SelectAll();
+
+	        mShownEntries.Clear();
+	        base.ShowDropdown();
+
+	        int32 bestItem = -1;
+	        int32 bestPri = -1;
+	        var menuWidget = (DarkMenuWidget)mCurMenuWidget;
+
+	        for (int32 itemIdx = 0; itemIdx < menuWidget.mItemWidgets.Count; itemIdx++)
+	        {
+	            var menuItemWidget = (DarkMenuItem)menuWidget.mItemWidgets[itemIdx];
+
+	            int32 pri;
+	            sMRU.TryGetValue(menuItemWidget.mMenuItem.mLabel, out pri);
+	            if (pri > bestPri)
+	            {
+	                bestItem = itemIdx;
+	                bestPri = pri;
+	            }
+	        }
+
+	        if (bestItem != -1)
+	        {
+				mCurMenuWidget.mOnSelectionChanged.Add(new => SelectionChanged);
+	            mCurMenuWidget.SetSelection(bestItem);
+	        }
+
+			return menuWidget;
+	    }
+
+		void SelectionChanged(int selIdx)
+		{
+			if (mEditWidget.mEditWidgetContent.HasSelection())
+			{
+				bool prevIgnoreShowDropdown = mIgnoreShowDropdown;
+				mIgnoreShowDropdown = true;
+				mEditWidget.SetText("");
+				mIgnoreShowDropdown = prevIgnoreShowDropdown;
+			}
+		}
+
+	    public override void MenuClosed()
+	    {
+	    }
+	}
+
+	class GenerateDialog : IDEDialog
+	{
+		public class UIEntry
+		{
+			public String mName ~ delete _;
+			public String mData ~ delete _;
+			public String mLabel ~ delete _;
+			public Widget mWidget;
+		}
+
+		public enum ThreadState
+		{
+			None,
+			Executing,
+			Done
+		}
+
+		public bool mPendingGenList;
+		public GenerateKindBar mKindBar;
+		public ThreadState mThreadState;
+		public int mThreadWaitCount;
+		public String mNamespace ~ delete _;
+		public String mProjectName ~ delete _;
+		public ProjectItem mProjectItem ~ _.ReleaseRef();
+		public String mFolderPath ~ delete _;
+		public List<UIEntry> mUIEntries = new .() ~ DeleteContainerAndItems!(_);
+		public GenerateKindBar.Entry mSelectedEntry;
+		public GenerateKindBar.Entry mPendingSelectedEntry;
+		public String mUIData ~ delete _;
+		public float mUIHeight = 0;
+		public OutputPanel mOutputPanel;
+		public bool mPendingUIFocus;
+		public bool mSubmitting;
+		public bool mSubmitQueued;
+		public bool mRegenerating;
+
+		public this(ProjectItem projectItem, bool allowHashMismatch = false)
+		{
+			var project = projectItem.mProject;
+			mProjectItem = projectItem;
+			mProjectItem.AddRef();
+
+			mNamespace = new .();
+
+			var projectFolder = projectItem as ProjectFolder;
+			var projectSource = projectItem as ProjectSource;
+			if (projectSource != null)
+			{
+				projectFolder = projectSource.mParentFolder;
+				mRegenerating = true;
+			}
+
+			projectFolder.GetRelDir(mNamespace); mNamespace.Replace('/', '.'); mNamespace.Replace('\\', '.'); mNamespace.Replace(" ", "");
+			if (mNamespace.StartsWith("src."))
+			{
+			    mNamespace.Remove(0, 4);
+				if (!project.mBeefGlobalOptions.mDefaultNamespace.IsWhiteSpace)
+				{
+					mNamespace.Insert(0, ".");
+					mNamespace.Insert(0, project.mBeefGlobalOptions.mDefaultNamespace);
+				}
+			}
+			else if (projectItem.mParentFolder == null)
+			{
+				mNamespace.Clear();
+				mNamespace.Append(project.mBeefGlobalOptions.mDefaultNamespace);
+			}
+			else
+				mNamespace.Clear();
+
+			mFolderPath = projectFolder.GetFullImportPath(.. new .());
+			mProjectName = new String(projectItem.mProject.mProjectName);
+
+			mWindowFlags = .ClientSized | .TopMost | .Caption | .Border | .SysMenu | .Resizable | .PopupPosition;
+
+			AddOkCancelButtons(new (evt) => { CreateClass(); }, null, 0, 1);
+
+			Title = "Generate";
+
+			mKindBar = new GenerateKindBar(this);
+			AddWidget(mKindBar);
+			mKindBar.mEditWidget.mOnContentChanged.Add(new (theEvent) => { SelectKind(); });
+
+			if (mRegenerating)
+			{
+				mSubmitQueued = true;
+				mKindBar.SetVisible(false);
+
+				SourceViewPanel sourceViewPanel = gApp.ShowProjectItem(projectSource, false);
+
+				String filePath = projectSource.GetFullImportPath(.. scope .());
+
+				String text = scope .();
+				sourceViewPanel.mEditWidget.GetText(text);
+
+				StringView generatorName = default;
+				StringView hash = default;
+
+				int dataIdx = -1;
+				for (var line in text.Split('\n'))
+				{
+					if (!line.StartsWith("// "))
+					{
+						dataIdx = @line.MatchPos + 1;
+						break;
+					}
+					int eqPos = line.IndexOf('=');
+					if (eqPos == -1)
+						break;
+					StringView key = line.Substring(3, eqPos - 3);
+					StringView value = line.Substring(eqPos + 1);
+					if (key == "Generator")
+						generatorName = value;
+					else if (key == "GenHash")
+						hash = value;
+					else
+					{
+						UIEntry uiEntry = new .();
+						uiEntry.mName = new .(key);
+						uiEntry.mData = new .(value);
+						mUIEntries.Add(uiEntry);
+					}
+				}
+
+				if ((generatorName == default) || (hash == default))
+				{
+					Close();
+					gApp.Fail(scope $"File '{filePath}' was not generated by a generator that include regeneration information");
+					return;
+				}
+
+				if ((dataIdx != -1) && (!allowHashMismatch))
+				{
+					var origHash = MD5Hash.Parse(hash).GetValueOrDefault();
+
+					StringView dataStr = text.Substring(dataIdx);
+					var checkHash = MD5.Hash(.((.)dataStr.Ptr, dataStr.Length));
+
+					if (origHash != checkHash)
+					{
+						Close();
+						Dialog dialog = ThemeFactory.mDefault.CreateDialog("Regenerate?", "This file has been modified since it was generated. Are you sure you want to regenerate?", DarkTheme.sDarkTheme.mIconWarning);
+						dialog.AddButton("Yes", new (evt) =>
+							{
+								gApp.mProjectPanel.Regenerate(true);
+								//dialog.Close();
+							});
+						dialog.AddButton("No", new (evt) =>
+							{
+								//dialog.Close();
+							});
+						dialog.PopupWindow(gApp.GetActiveWindow());
+						return;
+					}
+				}
+
+				GenerateKindBar.Entry entry = new .();
+				entry.mName = new .(generatorName);
+				entry.mTypeName = new .(generatorName);
+				mKindBar.mEntries.Add(entry);
+				mPendingSelectedEntry = entry;
+			}
+			else
+				mPendingGenList = true;
+
+			mKindBar.mMouseVisible = false;
+			mTabWidgets.Add(mKindBar.mEditWidget);
+		}
+
+		public ~this()
+		{
+			var bfCompiler = gApp.mBfResolveCompiler;
+			if (mThreadState == .Executing)
+			{
+				bfCompiler.WaitForBackground();
+			}
+		}
+
+		public void SelectKind()
+		{
+			GenerateKindBar.Entry foundEntry = null;
+
+			String text = mKindBar.mEditWidget.GetText(.. scope .());
+			for (var entry in mKindBar.mEntries)
+				if (entry.mName == text)
+					foundEntry = entry;
+
+			if (foundEntry == null)
+				return;
+
+			if (mSelectedEntry == foundEntry)
+				return;
+
+			mPendingSelectedEntry = foundEntry;
+		}
+
+		public void ThreadProc()
+		{
+			var bfSystem = gApp.mBfResolveSystem;
+			var bfCompiler = gApp.mBfResolveCompiler;
+			
+			String outStr = scope String();
+
+			bfSystem.Lock(0);
+			defer bfSystem.Unlock();
+
+			if (mSelectedEntry != null)
+			{
+				String args = scope .();
+				var project = gApp.mWorkspace.FindProject(mProjectName);
+				if (project == null)
+					return;
+				using (gApp.mMonitor.Enter())
+				{
+					args.AppendF(
+						$"""
+						ProjectName\t{mProjectName}
+						ProjectDir\t{project.mProjectPath}
+						FolderDir\t{mFolderPath}
+						Namespace\t{mNamespace}
+						DefaultNamespace\t{project.mBeefGlobalOptions.mDefaultNamespace}
+						WorkspaceName\t{gApp.mWorkspace.mName}
+						WorkspaceDir\t{gApp.mWorkspace.mDir}
+						DateTime\t{DateTime.Now}
+
+						""");
+
+					if (mSubmitting)
+					{
+						args.AppendF($"Generator\t{mSelectedEntry.mTypeName}\n");
+						for (var uiEntry in mUIEntries)
+						{
+							String data = scope .();
+							if (uiEntry.mData != null)
+							{
+								data.Append(uiEntry.mData);
+							}
+							else if (var editWidget = uiEntry.mWidget as EditWidget)
+							{
+								editWidget.GetText(data);
+							}
+							else if (var comboBox = uiEntry.mWidget as DarkComboBox)
+							{
+								comboBox.GetLabel(data);
+							}
+							else if (var checkBox = uiEntry.mWidget as CheckBox)
+							{
+								checkBox.Checked.ToString(data);
+							}
+							data.Replace('\n', '\r');
+							args.AppendF($"{uiEntry.mName}\t{data}\n");
+						}
+					}
+				}
+
+				mUIData = new String();
+				if (mSubmitting)
+					bfCompiler.GetGeneratorGenData(mSelectedEntry.mTypeName, args, mUIData);
+				else
+					bfCompiler.GetGeneratorInitData(mSelectedEntry.mTypeName, args, mUIData);
+			}
+			else
+			{
+				bfCompiler.GetGeneratorTypeDefList(outStr);
+
+				for (var line in outStr.Split('\n', .RemoveEmptyEntries))
+				{
+					if (line.StartsWith("!error"))
+					{
+						ShowError(line.Substring(7));
+						continue;
+					}
+
+					var entry = new GenerateKindBar.Entry();
+					var partItr = line.Split('\t');
+					entry.mTypeName = new String(partItr.GetNext().Value);
+					if (partItr.GetNext() case .Ok(let val))
+						entry.mName = new String(val);
+					else
+					{
+						entry.mName = new String(entry.mTypeName);
+						int termPos = entry.mName.LastIndexOf('.');
+						if (termPos != -1)
+							entry.mName.Remove(0, termPos + 1);
+						termPos = entry.mName.LastIndexOf('+');
+						if (termPos != -1)
+							entry.mName.Remove(0, termPos + 1);
+					}
+					mKindBar.mEntries.Add(entry);
+				}
+			}
+		}
+
+		public override void CalcSize()
+		{
+		    mWidth = GS!(320);
+		    mHeight = GS!(96);
+			mMinWidth = mWidth;
+		}
+
+		protected override void RehupMinSize()
+		{
+			mWidgetWindow.SetMinimumSize(GS!(240), (.)mUIHeight + GS!(24), true);
+		}
+
+		void CreateClass()
+		{
+			//mClassViewPanel.[Friend]mSearchEdit.mOnSubmit(null);
+		}
+
+		void ShowError(StringView error)
+		{
+			if (mOutputPanel == null)
+			{
+				mOutputPanel = new OutputPanel();
+				AddWidget(mOutputPanel);
+				ResizeComponents();
+			}
+			String str = scope .();
+			str.Append(error);
+			str.Replace('\r', '\n');
+			str.Append("\n");
+			mOutputPanel.WriteSmart(str);
+		}
+
+		public override void Update()
+		{
+			base.Update();
+
+			if ((!mKindBar.mEditWidget.mHasFocus) && (mWidgetWindow.mHasFocus))
+			{
+				var sel = mPendingSelectedEntry ?? mSelectedEntry;
+				String editText = mKindBar.mEditWidget.GetText(.. scope .());
+				if ((sel != null) && (editText != sel.mName))
+				{
+					mKindBar.mIgnoreChange = true;
+					mKindBar.mEditWidget.SetText(sel.mName);
+					mKindBar.mIgnoreChange = false;
+				}
+			}
+
+			if (mThreadState == .Done)
+			{
+				if (mSelectedEntry != null)
+				{
+					List<UIEntry> oldEntries = scope .();
+					Dictionary<String, UIEntry> entryMap = scope .();
+					if (!mSubmitting)
+					{
+						for (var uiEntry in mUIEntries)
+						{
+							if (!entryMap.TryAdd(uiEntry.mName, uiEntry))
+								oldEntries.Add(uiEntry);
+						}
+						mUIEntries.Clear();
+					}
+
+					if (mUIData != null)
+					{
+						if (mOutputPanel != null)
+						{
+							mOutputPanel.RemoveSelf();
+							DeleteAndNullify!(mOutputPanel);
+						}
+
+						String fileName = default;
+						StringView genText = default;
+						bool hadError = false;
+
+						if (mUIData.IsEmpty)
+						{
+							gApp.Fail("Generator failed to return results");
+						}
+
+						LinesLoop: for (var line in mUIData.Split('\n', .RemoveEmptyEntries))
+						{
+							var partItr = line.Split('\t');
+							var kind = partItr.GetNext().Value;
+
+							switch (kind)
+							{
+							case "!error":
+								ShowError(line.Substring(7));
+							case "addEdit":
+								if (mSubmitting)
+									break;
+								UIEntry uiEntry = new UIEntry();
+								uiEntry.mName = partItr.GetNext().Value.UnQuoteString(.. new .());
+								uiEntry.mLabel = partItr.GetNext().Value.UnQuoteString(.. new .());
+								var defaultValue = partItr.GetNext().Value.UnQuoteString(.. scope .());
+								DarkEditWidget editWidget = new DarkEditWidget();
+								uiEntry.mWidget = editWidget;
+								editWidget.SetText(defaultValue);
+								editWidget.mEditWidgetContent.SelectAll();
+								editWidget.mOnSubmit.Add(new => EditSubmitHandler);
+								AddWidget(editWidget);
+								mUIEntries.Add(uiEntry);
+								mTabWidgets.Add(editWidget);
+							case "addCombo":
+								if (mSubmitting)
+									break;
+								UIEntry uiEntry = new UIEntry();
+								uiEntry.mName = partItr.GetNext().Value.UnQuoteString(.. new .());
+								uiEntry.mLabel = partItr.GetNext().Value.UnQuoteString(.. new .());
+								var defaultValue = partItr.GetNext().Value.UnQuoteString(.. scope .());
+								List<String> choices = new List<String>();
+								DarkComboBox comboBox = new DarkComboBox();
+								while (partItr.GetNext() case .Ok(let val))
+								{
+									choices.Add(val.UnQuoteString(.. new .()));
+								}
+								comboBox.mOnDeleted.Add(new (widget) => { DeleteContainerAndItems!(choices); });
+								comboBox.mPopulateMenuAction.Add(new (menu) =>
+									{
+										for (var choice in choices)
+										{
+											var item = menu.AddItem(choice);
+											item.mOnMenuItemSelected.Add(new (menu) =>
+												{
+												   	comboBox.Label = menu.mLabel;
+												});
+										}
+									});
+								uiEntry.mWidget = comboBox;
+								comboBox.Label = defaultValue;
+								AddWidget(comboBox);
+								mUIEntries.Add(uiEntry);
+								mTabWidgets.Add(comboBox);
+							case "addCheckbox":
+								if (mSubmitting)
+									break;
+								UIEntry uiEntry = new UIEntry();
+								uiEntry.mName = partItr.GetNext().Value.UnQuoteString(.. new .());
+								uiEntry.mLabel = partItr.GetNext().Value.UnQuoteString(.. new .());
+								var defaultValue = partItr.GetNext().Value;
+								DarkCheckBox checkbox = new DarkCheckBox();
+								uiEntry.mWidget = checkbox;
+								checkbox.Label = uiEntry.mLabel;
+								checkbox.Checked = defaultValue == "True";
+								AddWidget(checkbox);
+								mUIEntries.Add(uiEntry);
+								mTabWidgets.Add(checkbox);
+							case "error":
+								hadError = true;
+								gApp.Fail(line.Substring(6).UnQuoteString(.. scope .()));
+							case "fileName":
+								fileName = line.Substring(9).UnQuoteString(.. scope:: .());
+							case "options":
+							case "data":
+								genText = .(mUIData, @line.MatchPos + 1);
+								break LinesLoop;
+							}
+						}
+						
+						ResizeComponents();
+						RehupMinSize();
+
+						if (fileName?.EndsWith(".bf", .OrdinalIgnoreCase) == true)
+							fileName.RemoveFromEnd(3);
+
+						if ((fileName != null) && (!mRegenerating))
+						{
+							for (char8 c in fileName.RawChars)
+							{
+								if (!c.IsLetterOrDigit)
+								{
+									gApp.Fail(scope $"Invalid generated file name: {fileName}");
+									hadError = true;
+									break;
+								}
+							}
+
+							if (fileName.IsEmpty)
+							{
+								gApp.Fail("Geneator failed to specify file name");
+								hadError = true;
+							}
+						}
+
+						if ((!hadError) && (genText != default) && (fileName != null))
+						{
+							if (!mProjectItem.mDetached)
+							{
+								if (mRegenerating)
+								{
+									gApp.mProjectPanel.Regenerate(mProjectItem as ProjectSource, genText);
+								}
+								else
+								{
+									gApp.mProjectPanel.Generate(mProjectItem as ProjectFolder, fileName, genText);
+								}
+							}
+							Close();
+						}
+
+						if (mPendingUIFocus)
+						{
+							mPendingUIFocus = false;
+							if (!mUIEntries.IsEmpty)
+								mUIEntries[0].mWidget.SetFocus();
+						}
+
+						DeleteAndNullify!(mUIData);
+					}
+
+					//
+
+					if (mSubmitting)
+					{
+						if (!mClosed)
+						{
+							mSubmitting = false;
+							mSubmitQueued = false;
+							mDefaultButton.mDisabled = false;
+							mEscButton.mDisabled = false;
+						}
+					}
+					else
+					{
+						for (var uiEntry in entryMap.Values)
+							oldEntries.Add(uiEntry);
+
+						for (var uiEntry in oldEntries)
+						{
+							mTabWidgets.Remove(uiEntry.mWidget);
+							uiEntry.mWidget.RemoveSelf();
+							DeleteAndNullify!(uiEntry.mWidget);
+						}
+
+						ClearAndDeleteItems(oldEntries);
+					}
+				}
+				else
+				{
+					mKindBar.mMouseVisible = true;
+					mKindBar.SetFocus();
+					mKindBar.SetLocation("New Class");
+				}
+				mThreadState = .None;
+				MarkDirty();
+			}
+
+			bool isWorking = false;
+			if (mThreadState == .None)
+			{
+				if ((mPendingGenList) || (mPendingSelectedEntry != null))
+				{
+					isWorking = true;
+					var bfCompiler = gApp.mBfResolveCompiler;
+					if (!bfCompiler.IsPerformingBackgroundOperation())
+					{
+						bfCompiler.CheckThreadDone();
+
+						mPendingGenList = false;
+						if (mPendingSelectedEntry != null)
+						{
+							if (mSubmitQueued)
+								mSubmitting = true;
+							mSelectedEntry = mPendingSelectedEntry;
+							mPendingSelectedEntry = null;
+						}
+						mThreadState = .Executing;
+						bfCompiler.DoBackgroundHi(new => ThreadProc, new () =>
+							{
+								mThreadState = .Done;
+							}, false);
+					}
+				}
+			}
+
+			gApp.mBfResolveCompiler.CheckThreadDone();
+
+			if ((mThreadState == .Executing) || (isWorking))
+			{
+				mThreadWaitCount++;
+				if (mUpdateCnt % 8 == 0)
+					MarkDirty();
+			}
+			else
+				mThreadWaitCount = 0;
+		}
+
+		public override void Resize(float x, float y, float width, float height)
+		{
+			base.Resize(x, y, width, height);
+			ResizeComponents();
+			//mClassViewPanel.Resize(0, 0, width, height - GS!(34));
+		}
+
+		public override void PopupWindow(WidgetWindow parentWindow, float offsetX = 0, float offsetY = 0)
+		{
+			base.PopupWindow(parentWindow, offsetX, offsetY);
+			//mKindBar.SetFocus();
+		}
+
+		public override void Draw(Graphics g)
+		{
+			base.Draw(g);
+
+			void DrawLabel(Widget widget, StringView label)
+			{
+				if (widget == null)
+					return;
+				if (widget is CheckBox)
+					return;
+				g.DrawString(label, widget.mX + GS!(6), widget.mY - GS!(20));
+			}
+
+			DrawLabel(mKindBar, mRegenerating ? "Regenerating ..." : "Using Generator");
+			for (var uiEntry in mUIEntries)
+				DrawLabel(uiEntry.mWidget, uiEntry.mLabel);
+		}
+
+		public override void DrawAll(Graphics g)
+		{
+			base.DrawAll(g);
+
+			if (mThreadWaitCount > 10)
+			{
+				using (g.PushColor(0x60505050))
+					g.FillRect(0, 0, mWidth, mHeight - GS!(40));
+				IDEUtils.DrawWait(g, mWidth/2, mHeight/2, mUpdateCnt);
+			}
+		}
+
+		public override void ResizeComponents()
+		{
+			base.ResizeComponents();
+
+			mUIHeight = GS!(32);
+
+			float insetSize = GS!(12);
+			
+			mKindBar.Resize(insetSize, mUIHeight, mWidth - insetSize - insetSize, GS!(22));
+			mUIHeight += GS!(52);
+
+			for (var uiEntry in mUIEntries)
+			{
+				if (uiEntry.mWidget == null)
+					continue;
+
+				float height = GS!(22);
+				if (uiEntry.mWidget is ComboBox)
+					height = GS!(26);
+
+				if (uiEntry.mWidget is CheckBox)
+				{
+					mUIHeight -= GS!(20);
+					height = GS!(20);
+				}
+
+				uiEntry.mWidget.Resize(insetSize, mUIHeight, mWidth - insetSize - insetSize, height);
+				mUIHeight += height + GS!(28);
+			}
+
+			if (mOutputPanel != null)
+			{
+				float startY = mKindBar.mVisible ? GS!(60) : GS!(36);
+				mOutputPanel.Resize(insetSize, startY, mWidth - insetSize - insetSize, Math.Max(mHeight - startY - GS!(44), GS!(32)));
+				mUIHeight = Math.Max(mUIHeight, GS!(160));
+			}
+		}
+
+		public override void Close()
+		{
+			if (mThreadState == .Executing)
+			{
+				var bfCompiler = gApp.mBfResolveCompiler;
+				bfCompiler.RequestFastFinish();
+				bfCompiler.WaitForBackground();
+			}
+			base.Close();
+		}
+
+		public override void Submit()
+		{
+			mDefaultButton.mDisabled = true;
+			mEscButton.mDisabled = true;
+
+			if (mSubmitQueued)
+				return;
+			mSubmitQueued = true;
+			mPendingSelectedEntry = mPendingSelectedEntry ?? mSelectedEntry;
+		}
+	}
+}

+ 1 - 1
IDE/src/ui/OutputPanel.bf

@@ -327,7 +327,7 @@ namespace IDE.ui
                 int lineIdx = (curLine + lineOfs) % lineCount;
 
                 if (content.GotoRefrenceAtLine(lineIdx))
-                    break;                
+                    break;
             }
         }
 

+ 98 - 6
IDE/src/ui/ProjectPanel.bf

@@ -792,16 +792,96 @@ namespace IDE.ui
 			}
         }
 
-        public void NewClass(ProjectFolder folder)
+        public void GenerateCode(ProjectFolder folder)
         {
-            DarkDialog dialog = (DarkDialog)ThemeFactory.mDefault.CreateDialog("New Class", "Class Name");
+            /*DarkDialog dialog = (DarkDialog)ThemeFactory.mDefault.CreateDialog("New Class", "Class Name");
             dialog.mMinWidth = GS!(300);
             dialog.mDefaultButton = dialog.AddButton("OK", new (evt) => DoNewClass(folder, evt));
             dialog.mEscButton = dialog.AddButton("Cancel");
             dialog.AddEdit("Unnamed");
-            dialog.PopupWindow(gApp.GetActiveWindow());
+            dialog.PopupWindow(gApp.GetActiveWindow());*/
+
+			var dialog = new GenerateDialog(folder);
+			dialog.PopupWindow(gApp.GetActiveWindow());
         }
 
+		public void Regenerate(bool allowHashMismatch)
+		{
+			mListView.GetRoot().WithSelectedItems(scope (selectedItem) =>
+				{
+					if (mListViewToProjectMap.GetValue(selectedItem) case .Ok(var sourceProjectItem))
+					{
+						var dialog = new GenerateDialog(sourceProjectItem, allowHashMismatch);
+						dialog.PopupWindow(gApp.GetActiveWindow());
+					}
+				});
+		}
+
+		public void Regenerate(ProjectSource projectSource, StringView fileText)
+		{
+			var sourceViewPanel = gApp.ShowProjectItem(projectSource, false);
+			sourceViewPanel.mEditWidget.SetText(scope .(fileText));
+		}
+
+		public void Generate(ProjectFolder folder, StringView fileName, StringView fileText)
+		{
+			let project = folder.mProject;
+			if (project.mNeedsCreate)
+				project.FinishCreate();
+			String relFileName = scope .(fileName);
+			if (!relFileName.Contains('.'))
+				relFileName.Append(".bf");
+
+			String fullFilePath = scope String();
+			String relPath = scope String();
+			folder.GetRelDir(relPath);
+			if (relPath.Length > 0)
+				relPath.Append("/");
+			relPath.Append(relFileName);
+			folder.mProject.GetProjectFullPath(relPath, fullFilePath);
+			String dirName = scope String();
+			Path.GetDirectoryPath(fullFilePath, dirName);
+			Directory.CreateDirectory(dirName).IgnoreError();
+
+			if (File.Exists(fullFilePath))
+			{
+				var error = scope String();
+				error.AppendF("File '{0}' already exists", fullFilePath);
+				IDEApp.sApp.Fail(error);
+				return;
+			}
+
+			if (File.WriteAllText(fullFilePath, fileText) case .Err)
+			{
+				var error = scope String();
+				error.AppendF("Failed to create file '{0}'", fullFilePath);
+				gApp.Fail(error);
+				return;
+			}
+
+			ProjectSource projectSource = new ProjectSource();
+			projectSource.mIncludeKind = (folder.mIncludeKind == .Auto) ? .Auto : .Manual;
+			projectSource.mName.Set(relFileName);
+			projectSource.mPath = new String();
+			folder.mProject.GetProjectRelPath(fullFilePath, projectSource.mPath);
+			projectSource.mProject = folder.mProject;
+			projectSource.mParentFolder = folder;
+			folder.AddChild(projectSource);
+			let projectItem = AddProjectItem(projectSource);
+			if (projectItem != null)
+			{
+				mListView.GetRoot().SelectItemExclusively(projectItem);
+				mListView.EnsureItemVisible(projectItem, false);
+			}
+			Sort();
+			if (folder.mIncludeKind != .Auto)
+				folder.mProject.SetChanged();
+
+			gApp.RecordHistoryLocation(true);
+			gApp.ShowProjectItem(projectSource);
+			gApp.RecordHistoryLocation(true);
+		}
+
         void DoNewClass(ProjectFolder folder, DialogEvent evt)
         {
             Dialog dlg = (Dialog)evt.mSender;
@@ -1470,7 +1550,10 @@ namespace IDE.ui
 			}
 
 			if (doReleaseRef)
+			{
+				projectItem.mDetached = true;
 				projectItem.ReleaseRef();
+			}
 			//TODO: Defer this, projectItem is needed for a backgrounded QueueProjectSourceRemoved
 			//delete projectItem;
         }
@@ -2471,17 +2554,26 @@ namespace IDE.ui
 							}
 					    });
 	
-					item = menu.AddItem("New Class...");
+					item = menu.AddItem("Generate...");
 					item.mOnMenuItemSelected.Add(new (item) =>
 					    {
 							var projectFolder = GetSelectedProjectFolder();
 							if (projectFolder != null)
 					        {
 								if (CheckProjectModify(projectFolder.mProject))
-									NewClass(projectFolder);
+									GenerateCode(projectFolder);
 							}
 					    });
-	
+
+					if ((projectItem != null) && (projectItem is ProjectSource) && (!isProject))
+					{
+						item = menu.AddItem("Regenerate");
+						item.mOnMenuItemSelected.Add(new (item) =>
+						    {
+								Regenerate(false);
+						    });
+					}
+
 					item = menu.AddItem("Import File...");
 					item.mOnMenuItemSelected.Add(new (item) => { mImportFileDeferred = true; /* ImportFile();*/ });
 	

+ 224 - 45
IDEHelper/Compiler/BfCompiler.cpp

@@ -420,6 +420,7 @@ BfCompiler::BfCompiler(BfSystem* bfSystem, bool isResolveOnly)
 	mInternalTypeDef = NULL;
 	mPlatformTypeDef = NULL;
 	mCompilerTypeDef = NULL;
+	mCompilerGeneratorTypeDef = NULL;
 	mDiagnosticsDebugTypeDef = NULL;
 	mIDisposableTypeDef = NULL;
 	mIIntegerTypeDef = NULL;
@@ -6765,6 +6766,7 @@ bool BfCompiler::DoCompile(const StringImpl& outputDirectory)
 	mInternalTypeDef = _GetRequiredType("System.Internal");
 	mPlatformTypeDef = _GetRequiredType("System.Platform");
 	mCompilerTypeDef = _GetRequiredType("System.Compiler");
+	mCompilerGeneratorTypeDef = _GetRequiredType("System.Compiler.Generator");
 	mDiagnosticsDebugTypeDef = _GetRequiredType("System.Diagnostics.Debug");
 	mIDisposableTypeDef = _GetRequiredType("System.IDisposable");
 	mIIntegerTypeDef = _GetRequiredType("System.IInteger");
@@ -8087,6 +8089,149 @@ String BfCompiler::GetTypeDefList()
 	return result;
 }
 
+String BfCompiler::GetGeneratorString(BfTypeDef* typeDef, BfTypeInstance* typeInst, const StringImpl& generatorMethodName, const StringImpl* args)
+{	
+	if (typeInst == NULL)
+	{
+		auto type = mContext->mUnreifiedModule->ResolveTypeDef(typeDef, BfPopulateType_BaseType);
+		if (type != NULL)
+			typeInst = type->ToTypeInstance();
+		if (typeInst == NULL)
+			return "";
+	}
+
+	BfTypeVector typeVector;
+	typeVector.Add(typeInst);
+
+	auto generatorTypeInst = mContext->mUnreifiedModule->ResolveTypeDef(mCompilerGeneratorTypeDef)->ToTypeInstance();
+	auto methodDef = generatorTypeInst->mTypeDef->GetMethodByName(generatorMethodName);
+	auto moduleMethodInstance = mContext->mUnreifiedModule->GetMethodInstance(generatorTypeInst, methodDef, typeVector);
+
+	SetAndRestoreValue<BfMethodInstance*> prevMethodInstance(mContext->mUnreifiedModule->mCurMethodInstance, moduleMethodInstance.mMethodInstance);
+	SetAndRestoreValue<BfTypeInstance*> prevTypeInstance(mContext->mUnreifiedModule->mCurTypeInstance, typeInst);
+
+	BfExprEvaluator exprEvaluator(mContext->mUnreifiedModule);
+	exprEvaluator.mBfEvalExprFlags = BfEvalExprFlags_Comptime;	
+
+	SizedArray<BfIRValue, 1> irArgs;
+	if (args != NULL)
+		irArgs.Add(mContext->mUnreifiedModule->GetStringObjectValue(*args));
+	auto callResult = exprEvaluator.CreateCall(NULL, moduleMethodInstance.mMethodInstance, moduleMethodInstance.mFunc, false, irArgs, NULL, BfCreateCallFlags_None);
+
+	if (callResult.mValue.IsConst())
+	{
+		auto stringPtr = mContext->mUnreifiedModule->GetStringPoolString(callResult.mValue, mContext->mUnreifiedModule->mBfIRBuilder);
+		if (stringPtr != NULL)
+			return *stringPtr;
+	}
+	return "";
+}
+
+void BfCompiler::HandleGeneratorErrors(StringImpl& result)
+{
+	if ((mPassInstance->mErrors.IsEmpty()) && (mPassInstance->mOutStream.IsEmpty()))
+		return;
+
+	result.Clear();
+	
+	for (auto& msg : mPassInstance->mOutStream)
+	{
+		String error = msg;
+		error.Replace('\n', '\r');
+		result += "!error\t";
+		result += error;
+		result += "\n";
+	}
+}
+
+String BfCompiler::GetGeneratorTypeDefList()
+{
+	String result;
+
+	BfProject* curProject = NULL;
+	Dictionary<BfProject*, int> projectIds;
+	
+	BfResolvePassData resolvePassData;
+	SetAndRestoreValue<BfResolvePassData*> prevResolvePassData(mResolvePassData, &resolvePassData);	
+	BfPassInstance passInstance(mSystem);
+	SetAndRestoreValue<BfPassInstance*> prevPassInstance(mPassInstance, &passInstance);
+
+	for (auto typeDef : mSystem->mTypeDefs)
+	{
+		if (typeDef->mProject->mDisabled)
+			continue;
+
+		if (typeDef->mIsPartial)
+			continue;
+
+		auto type = mContext->mUnreifiedModule->ResolveTypeDef(typeDef, BfPopulateType_BaseType);
+		if ((type != NULL) && (type->IsTypeInstance()))
+		{
+			auto typeInst = type->ToTypeInstance();
+			if ((typeInst->mBaseType != NULL) && (typeInst->mBaseType->IsInstanceOf(mCompilerGeneratorTypeDef)))
+			{
+				result += typeDef->mProject->mName;
+				result += ":";
+				result += BfTypeUtils::TypeToString(typeDef, BfTypeNameFlag_InternalName);
+				String nameString = GetGeneratorString(typeDef, typeInst, "GetName", NULL);
+				if (!nameString.IsEmpty())
+					result += "\t" + nameString;				
+				result += "\n";
+			}
+		}
+	}
+
+	HandleGeneratorErrors(result);
+
+	return result;
+}
+
+String BfCompiler::GetGeneratorInitData(const StringImpl& typeName, const StringImpl& args)
+{
+	BfResolvePassData resolvePassData;
+	SetAndRestoreValue<BfResolvePassData*> prevResolvePassData(mResolvePassData, &resolvePassData);
+	BfPassInstance passInstance(mSystem);
+	SetAndRestoreValue<BfPassInstance*> prevPassInstance(mPassInstance, &passInstance);
+
+	Array<BfTypeDef*> typeDefs;
+	GetTypeDefs(typeName, typeDefs);
+
+	String result;
+	for (auto typeDef : typeDefs)
+	{		
+		result += GetGeneratorString(typeDef, NULL, "InitUI", &args);
+		if (!result.IsEmpty())
+			break;
+	}
+
+	HandleGeneratorErrors(result);
+
+	return result;
+}
+
+String BfCompiler::GetGeneratorGenData(const StringImpl& typeName, const StringImpl& args)
+{
+	BfResolvePassData resolvePassData;
+	SetAndRestoreValue<BfResolvePassData*> prevResolvePassData(mResolvePassData, &resolvePassData);
+	BfPassInstance passInstance(mSystem);
+	SetAndRestoreValue<BfPassInstance*> prevPassInstance(mPassInstance, &passInstance);
+
+	Array<BfTypeDef*> typeDefs;
+	GetTypeDefs(typeName, typeDefs);
+
+	String result;
+	for (auto typeDef : typeDefs)
+	{		
+		result += GetGeneratorString(typeDef, NULL, "Generate", &args);
+		if (!result.IsEmpty())
+			break;
+	}
+
+	HandleGeneratorErrors(result);
+
+	return result;
+}
+
 struct TypeDefMatchHelper
 {
 public:
@@ -8580,9 +8725,9 @@ String BfCompiler::GetTypeDefMatches(const StringImpl& searchStr)
 	return result;
 }
 
-String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName)
+void BfCompiler::GetTypeDefs(const StringImpl& inTypeName, Array<BfTypeDef*>& typeDefs)
 {
-	BfProject* project = NULL;	
+	BfProject* project = NULL;
 	int idx = 0;
 
 	int sep = (int)inTypeName.IndexOf(':');
@@ -8595,7 +8740,7 @@ String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName)
 	String typeName;
 	int genericCount = 0;
 	int pendingGenericCount = 0;
-	for ( ; idx < (int)inTypeName.length(); idx++)
+	for (; idx < (int)inTypeName.length(); idx++)
 	{
 		char c = inTypeName[idx];
 		if (c == '<')
@@ -8606,7 +8751,7 @@ String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName)
 				genericCount++;
 			else if (c == '>')
 			{
-				pendingGenericCount = genericCount;				
+				pendingGenericCount = genericCount;
 				genericCount = 0;
 			}
 		}
@@ -8620,10 +8765,10 @@ String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName)
 			typeName += c;
 		}
 	}
-	
+
 	bool isGlobals = false;
 	if (typeName == ":static")
-	{		
+	{
 		typeName.clear();
 		isGlobals = true;
 	}
@@ -8637,63 +8782,73 @@ String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName)
 		if (typeName[i] == '+')
 			typeName[i] = '.';
 	
-	String result;
-	TypeDefMatchHelper matchHelper(result);
-	
-	BfAtomComposite nameComposite;
+	BfAtomComposite nameComposite;	
 	if ((typeName.IsEmpty()) || (mSystem->ParseAtomComposite(typeName, nameComposite)))
-	{		
+	{
 		auto itr = mSystem->mTypeDefs.TryGet(nameComposite);
 		while (itr)
-		{			
+		{
 			auto typeDef = *itr;
 			if ((!typeDef->mIsPartial) &&
 				(typeDef->mProject == project) &&
 				(typeDef->mFullName == nameComposite) &&
 				(typeDef->IsGlobalsContainer() == isGlobals) &&
 				(typeDef->GetSelfGenericParamCount() == pendingGenericCount))
-			{				
-				auto refNode = typeDef->GetRefNode();
-				result += "S";
-				matchHelper.AddLocation(refNode);
-				result += "\n";
+			{
+				typeDefs.Add(typeDef);
+			}
 
-				for (auto fieldDef : typeDef->mFields)
-				{
-					result += "F";
-					result += fieldDef->mName;
-					matchHelper.AddFieldDef(fieldDef);
-				}
+			itr.MoveToNextHashMatch();
+		}
+	}
+}
 
-				for (auto propDef : typeDef->mProperties)
-				{	
-					if (propDef->GetRefNode() == NULL)
-						continue;
+String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName)
+{
+	Array<BfTypeDef*> typeDefs;
+	GetTypeDefs(inTypeName, typeDefs);
+	
+	String result;
+	TypeDefMatchHelper matchHelper(result);
+		
+	for (auto typeDef : typeDefs)								
+	{
+		auto refNode = typeDef->GetRefNode();
+		result += "S";
+		matchHelper.AddLocation(refNode);
+		result += "\n";
 
-					result += "P";
-					matchHelper.AddPropertyDef(typeDef, propDef);					
-				}
+		for (auto fieldDef : typeDef->mFields)
+		{
+			result += "F";
+			result += fieldDef->mName;
+			matchHelper.AddFieldDef(fieldDef);
+		}
 
-				for (auto methodDef : typeDef->mMethods)
-				{
-					if ((methodDef->mMethodType != BfMethodType_Normal) &&
-						(methodDef->mMethodType != BfMethodType_Mixin) &&
-						(methodDef->mMethodType != BfMethodType_Ctor) &&
-						(methodDef->mMethodType != BfMethodType_Dtor))
-						continue;
+		for (auto propDef : typeDef->mProperties)
+		{	
+			if (propDef->GetRefNode() == NULL)
+				continue;
 
-					if (methodDef->mMethodDeclaration == NULL)
-						continue;					
+			result += "P";
+			matchHelper.AddPropertyDef(typeDef, propDef);					
+		}
 
-					result += "M";
-					matchHelper.AddMethodDef(methodDef);					
-				}
-			}
+		for (auto methodDef : typeDef->mMethods)
+		{
+			if ((methodDef->mMethodType != BfMethodType_Normal) &&
+				(methodDef->mMethodType != BfMethodType_Mixin) &&
+				(methodDef->mMethodType != BfMethodType_Ctor) &&
+				(methodDef->mMethodType != BfMethodType_Dtor))
+				continue;
 
-			itr.MoveToNextHashMatch();
+			if (methodDef->mMethodDeclaration == NULL)
+				continue;					
+
+			result += "M";
+			matchHelper.AddMethodDef(methodDef);					
 		}
 	}
-
 	return result;
 }
 
@@ -8970,6 +9125,30 @@ BF_EXPORT void BF_CALLTYPE BfCompiler_ProgramDone()
 #endif
 }
 
+BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetGeneratorTypeDefList(BfCompiler* bfCompiler)
+{
+	String& outString = *gTLStrReturn.Get();
+	outString.clear();
+	outString = bfCompiler->GetGeneratorTypeDefList();
+	return outString.c_str();
+}
+
+BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetGeneratorInitData(BfCompiler* bfCompiler, char* typeDefName, char* args)
+{
+	String& outString = *gTLStrReturn.Get();
+	outString.clear();
+	outString = bfCompiler->GetGeneratorInitData(typeDefName, args);
+	return outString.c_str();
+}
+
+BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetGeneratorGenData(BfCompiler* bfCompiler, char* typeDefName, char* args)
+{
+	String& outString = *gTLStrReturn.Get();
+	outString.clear();
+	outString = bfCompiler->GetGeneratorGenData(typeDefName, args);
+	return outString.c_str();
+}
+
 BF_EXPORT const char* BF_CALLTYPE BfCompiler_GetTypeDefList(BfCompiler* bfCompiler)
 {
 	String& outString = *gTLStrReturn.Get();

+ 9 - 2
IDEHelper/Compiler/BfCompiler.h

@@ -374,6 +374,7 @@ public:
 	BfTypeDef* mInternalTypeDef;
 	BfTypeDef* mPlatformTypeDef;
 	BfTypeDef* mCompilerTypeDef;
+	BfTypeDef* mCompilerGeneratorTypeDef;
 	BfTypeDef* mDiagnosticsDebugTypeDef;
 	BfTypeDef* mIDisposableTypeDef;
 	BfTypeDef* mIIntegerTypeDef;
@@ -511,9 +512,15 @@ public:
 	void ProcessAutocompleteTempType();	
 	void GetSymbolReferences();	
 	void Cancel();
-	void RequestFastFinish();
+	void RequestFastFinish();	
 	String GetTypeDefList();
-	String GetTypeDefMatches(const StringImpl& searchSrc);
+	String GetGeneratorString(BfTypeDef* typeDef, BfTypeInstance* typeInst, const StringImpl& generatorMethodName, const StringImpl* args);
+	void HandleGeneratorErrors(StringImpl& result);
+	String GetGeneratorTypeDefList();
+	String GetGeneratorInitData(const StringImpl& typeName, const StringImpl& args);
+	String GetGeneratorGenData(const StringImpl& typeName, const StringImpl& args);
+	String GetTypeDefMatches(const StringImpl& searchSrc);	
+	void GetTypeDefs(const StringImpl& typeName, Array<BfTypeDef*>& typeDefs);
 	String GetTypeDefInfo(const StringImpl& typeName);	
 	int GetEmitSource(const StringImpl& fileName, StringImpl* outBuffer);
 

+ 8 - 8
IDEHelper/Compiler/BfExprEvaluator.cpp

@@ -11789,7 +11789,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr)
 		if (mExpectingType->IsFunction())
 		{
 			BfIRValue result;
-			if ((hasIncompatibleCallingConventions) && (mModule->HasCompiledOutput()))
+			if ((hasIncompatibleCallingConventions) && (mModule->HasExecutedOutput()))
 			{
 				//
 				{
@@ -11949,7 +11949,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr)
 
 	// Do we need a special delegate type for this?
 	if (((captureThisByValue) || (needsSplat) || (implicitParamCount > 0) /*|| (hasIncompatibleCallingConventions)*/) &&
-		(mModule->HasCompiledOutput()))
+		(mModule->HasExecutedOutput()))
 	{
 		hasCaptures = true;
 		auto curProject = mModule->mCurTypeInstance->mTypeDef->mProject;
@@ -12030,7 +12030,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr)
 	// Do we need specialized calling code for this?
 	BfIRValue funcValue;
 	if (((needsSplat) || (implicitParamCount > 0) || (hasIncompatibleCallingConventions)) && 
-		(mModule->HasCompiledOutput()))
+		(mModule->HasExecutedOutput()))
 	{
 		int fieldIdx = 0;
 		for (int implicitParamIdx = bindMethodInstance->HasThis() ? -1 : 0; implicitParamIdx < implicitParamCount; implicitParamIdx++)
@@ -12208,7 +12208,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr)
 
 	// >> delegate.mTarget = bindResult.mTarget
 	BfIRValue valPtr;
-	if (mModule->HasCompiledOutput())
+	if (mModule->HasExecutedOutput())
 	{
 		if ((implicitParamCount > 0) || (needsSplat)) // Point back to self, it contains capture data
 			valPtr = mModule->mBfIRBuilder->CreateBitCast(mResult.mValue, mModule->mBfIRBuilder->GetPrimitiveType(BfTypeCode_NullPtr));
@@ -12226,7 +12226,7 @@ void BfExprEvaluator::Visit(BfDelegateBindExpression* delegateBindExpr)
 
 		if (!funcValue)
 		{
-			if ((mModule->HasCompiledOutput()) && (!mModule->mBfIRBuilder->mIgnoreWrites))
+			if ((mModule->HasExecutedOutput()) && (!mModule->mBfIRBuilder->mIgnoreWrites))
 				mModule->AssertErrorState();
 			return;
 		}
@@ -13188,7 +13188,7 @@ BfLambdaInstance* BfExprEvaluator::GetLambdaInstance(BfLambdaBindExpression* lam
 	mModule->mIncompleteMethodCount++;
 	SetAndRestoreValue<BfClosureState*> prevClosureState(mModule->mCurMethodState->mClosureState, &closureState);
 
-	if (mModule->HasCompiledOutput())
+	if (mModule->HasExecutedOutput())
 		mModule->SetupIRMethod(methodInstance, methodInstance->mIRFunction, methodInstance->mAlwaysInline);
 
 	// This keeps us from giving errors twice.  ProcessMethod can give errors when we capture by value but needed to
@@ -14415,7 +14415,7 @@ void BfExprEvaluator::CreateObject(BfObjectCreateExpression* objCreateExpr, BfAs
 	{	
 		if (!bindResult.mFunc)
 		{
-			BF_ASSERT((!mModule->HasCompiledOutput()) || (mModule->mBfIRBuilder->mIgnoreWrites));
+			BF_ASSERT((!mModule->HasExecutedOutput()) || (mModule->mBfIRBuilder->mIgnoreWrites));
 			appendSizeValue = mModule->GetConstValue(0);
 		}
 		else
@@ -19717,7 +19717,7 @@ void BfExprEvaluator::Visit(BfIndexerExpression* indexerExpr)
 				}
 			}
 		}
-		else if (((mModule->HasCompiledOutput()) || (mModule->mIsComptimeModule)) && 
+		else if (((mModule->HasExecutedOutput()) || (mModule->mIsComptimeModule)) &&
 			(wantsChecks))
 		{
 			if (checkedKind == BfCheckedKind_NotSet)

+ 12 - 7
IDEHelper/Compiler/BfModule.cpp

@@ -9617,6 +9617,11 @@ bool BfModule::HasCompiledOutput()
 	return (!mSystem->mIsResolveOnly) && (mGeneratesCode) && (!mIsComptimeModule);
 }
 
+bool BfModule::HasExecutedOutput()
+{
+	return ((!mSystem->mIsResolveOnly) && (mGeneratesCode)) || (mIsComptimeModule);
+}
+
 // We will skip the object access check for any occurrences of this value
 void BfModule::SkipObjectAccessCheck(BfTypedValue typedVal)
 {
@@ -15818,7 +15823,7 @@ void BfModule::CreateStaticCtor()
 	auto methodDef = mCurMethodInstance->mMethodDef;
 	
 	BfIRBlock exitBB;
-	if ((HasCompiledOutput()) && (!mCurMethodInstance->mIsUnspecialized) && (mCurMethodInstance->mChainType != BfMethodChainType_ChainMember))
+	if ((HasExecutedOutput()) && (!mCurMethodInstance->mIsUnspecialized) && (mCurMethodInstance->mChainType != BfMethodChainType_ChainMember))
 	{
 		auto boolType = GetPrimitiveType(BfTypeCode_Boolean);
 		auto didStaticInitVarAddr = mBfIRBuilder->CreateGlobalVariable(			
@@ -17041,7 +17046,7 @@ void BfModule::EmitCtorBody(bool& skipBody)
 					break;
 			}
 
-			if ((HasCompiledOutput()) && (matchedMethod != NULL))
+			if ((HasExecutedOutput()) && (matchedMethod != NULL))
 			{
 				SizedArray<BfIRValue, 1> args;
 				auto ctorBodyMethodInstance = GetMethodInstance(mCurTypeInstance->mBaseType, matchedMethod, BfTypeVector());
@@ -18486,7 +18491,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup)
 		return;
 	}
 
-	if (HasCompiledOutput())
+	if (HasExecutedOutput())
 	{
 		BF_ASSERT(mIsModuleMutable);
 	}
@@ -19713,7 +19718,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup)
 		skipBody = true;
 		skipEndChecks = true;
 		
-		if ((HasCompiledOutput()) || (mIsComptimeModule))
+		if (HasExecutedOutput())
 		{
 			// Clear out DebugLoc - to mark the ".addr" code as part of prologue
 			mBfIRBuilder->ClearDebugLocation();
@@ -19977,7 +19982,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup)
 		else if ((mCurTypeInstance->IsEnum()) && (!mCurTypeInstance->IsBoxed()) && (methodDef->mName == BF_METHODNAME_TO_STRING))
 		{
 			auto enumType = ResolveTypeDef(mCompiler->mEnumTypeDef);
-			if ((HasCompiledOutput()) || (mIsComptimeModule))
+			if (HasExecutedOutput())
 			{
 				EmitEnumToStringBody();
 			}
@@ -19990,7 +19995,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup)
 		else if ((mCurTypeInstance->IsTuple()) && (!mCurTypeInstance->IsBoxed()) && (methodDef->mName == BF_METHODNAME_TO_STRING))
 		{
 			auto enumType = ResolveTypeDef(mCompiler->mEnumTypeDef);
-			if ((HasCompiledOutput()) || (mIsComptimeModule))
+			if (HasExecutedOutput())
 			{
 				EmitTupleToStringBody();
 			}
@@ -20032,7 +20037,7 @@ void BfModule::ProcessMethod(BfMethodInstance* methodInstance, bool isInlineDup)
 				{
 					mBfIRBuilder->CreateRetVoid();
 				}
-				else if ((HasCompiledOutput()) || (mIsComptimeModule))
+				else if (HasExecutedOutput())
 				{
 					String autoPropName = typeDef->GetAutoPropertyName(propertyDeclaration);
 					BfFieldInstance* fieldInstance = GetFieldByName(mCurTypeInstance, autoPropName);

+ 1 - 0
IDEHelper/Compiler/BfModule.h

@@ -1633,6 +1633,7 @@ public:
 	bool IsTargetingBeefBackend();
 	bool WantsLifetimes();
 	bool HasCompiledOutput();
+	bool HasExecutedOutput();
 	void SkipObjectAccessCheck(BfTypedValue typedVal);
 	void EmitObjectAccessCheck(BfTypedValue typedVal);	
 	void EmitEnsureInstructionAt();

+ 14 - 3
IDEHelper/Compiler/CeMachine.cpp

@@ -1288,7 +1288,7 @@ void CeBuilder::Build()
 	auto methodInstance = mCeFunction->mMethodInstance;
 	
 	if (methodInstance != NULL)
-	{		
+	{
 		BfMethodInstance dupMethodInstance;
 		dupMethodInstance.CopyFrom(methodInstance);
 		auto methodDef = methodInstance->mMethodDef;
@@ -1638,10 +1638,10 @@ void CeBuilder::Build()
 						EmitBinaryOp(CeOp_Shl_I8, CeOp_InvalidOp, ceLHS, ceRHS, result);
 						break;
 					case BeBinaryOpKind_RightShift:
-						EmitBinaryOp(CeOp_Shr_I8, CeOp_InvalidOp, ceLHS, ceRHS, result);
+						EmitBinaryOp(CeOp_Shr_U8, CeOp_InvalidOp, ceLHS, ceRHS, result);
 						break;
 					case BeBinaryOpKind_ARightShift:
-						EmitBinaryOp(CeOp_Shr_U8, CeOp_InvalidOp, ceLHS, ceRHS, result);
+						EmitBinaryOp(CeOp_Shr_I8, CeOp_InvalidOp, ceLHS, ceRHS, result);
 						break;
 					default:
 						Fail("Invalid binary op");
@@ -2476,7 +2476,18 @@ void CeBuilder::Build()
 								EmitFrameOffset(ceSize);
 							}
 							break;
+						case BfIRIntrinsic_MemSet:
+							{
+								CeOperand ceDestPtr = GetOperand(castedInst->mArgs[0].mValue);
+								CeOperand ceValue = GetOperand(castedInst->mArgs[1].mValue);
+								CeOperand ceSize = GetOperand(castedInst->mArgs[2].mValue);
 
+								Emit(CeOp_MemSet);
+								EmitFrameOffset(ceDestPtr);
+								EmitFrameOffset(ceValue);
+								EmitFrameOffset(ceSize);
+							}
+							break;
 
 						case BfIRIntrinsic_AtomicFence:
 							// Nothing to do