From 73099e4a04f5674bbf46ff57d0acdb7d9f70cd17 Mon Sep 17 00:00:00 2001 From: Brian Fiete Date: Sat, 11 Dec 2021 09:08:42 -0800 Subject: [PATCH] Code generator support --- BeefLibs/Beefy2D/src/widgets/Dialog.bf | 3 + BeefLibs/corlib/src/Compiler.bf | 182 +++++- IDE/src/CommandQueueManager.bf | 16 +- IDE/src/Compiler/BfCompiler.bf | 34 +- IDE/src/Project.bf | 2 + IDE/src/ui/ClassViewPanel.bf | 4 + IDE/src/ui/GenerateDialog.bf | 869 +++++++++++++++++++++++++ IDE/src/ui/OutputPanel.bf | 2 +- IDE/src/ui/ProjectPanel.bf | 104 ++- IDEHelper/Compiler/BfCompiler.cpp | 275 ++++++-- IDEHelper/Compiler/BfCompiler.h | 11 +- IDEHelper/Compiler/BfExprEvaluator.cpp | 16 +- IDEHelper/Compiler/BfModule.cpp | 19 +- IDEHelper/Compiler/BfModule.h | 1 + IDEHelper/Compiler/CeMachine.cpp | 17 +- 15 files changed, 1472 insertions(+), 83 deletions(-) create mode 100644 IDE/src/ui/GenerateDialog.bf diff --git a/BeefLibs/Beefy2D/src/widgets/Dialog.bf b/BeefLibs/Beefy2D/src/widgets/Dialog.bf index b879a7e0..01723f6b 100644 --- a/BeefLibs/Beefy2D/src/widgets/Dialog.bf +++ b/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; diff --git a/BeefLibs/corlib/src/Compiler.bf b/BeefLibs/corlib/src/Compiler.bf index 45a8e3c3..ff90fc24 100644 --- a/BeefLibs/corlib/src/Compiler.bf +++ b/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 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 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() 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(String args) where T : Generator + { + T val = scope T(); + val.HandleArgs(args); + val.InitUI(); + return val.mCmdInfo; + } + + static String Generate(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; diff --git a/IDE/src/CommandQueueManager.bf b/IDE/src/CommandQueueManager.bf index 213fee60..4fa97410 100644 --- a/IDE/src/CommandQueueManager.bf +++ b/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() diff --git a/IDE/src/Compiler/BfCompiler.bf b/IDE/src/Compiler/BfCompiler.bf index e00b3502..d391831a 100644 --- a/IDE/src/Compiler/BfCompiler.bf +++ b/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)); diff --git a/IDE/src/Project.bf b/IDE/src/Project.bf index 7418ea2a..1a485c0d 100644 --- a/IDE/src/Project.bf +++ b/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(); } } diff --git a/IDE/src/ui/ClassViewPanel.bf b/IDE/src/ui/ClassViewPanel.bf index 3088cccc..51fac019 100644 --- a/IDE/src/ui/ClassViewPanel.bf +++ b/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')) { diff --git a/IDE/src/ui/GenerateDialog.bf b/IDE/src/ui/GenerateDialog.bf new file mode 100644 index 00000000..2d50da64 --- /dev/null +++ b/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 sMRU = new Dictionary() ~ delete _; + public static int32 sCurrentMRUIndex = 1; + + public GenerateDialog mNewClassDialog; + public List mEntries = new List() ~ DeleteContainerAndItems!(_); + public List mShownEntries = new List() ~ 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 findStrs = null; + if (mFilterString != null) + findStrs = scope:: List(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 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 oldEntries = scope .(); + Dictionary 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 choices = new List(); + 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; + } + } +} diff --git a/IDE/src/ui/OutputPanel.bf b/IDE/src/ui/OutputPanel.bf index 8d002fa1..e99e10a6 100644 --- a/IDE/src/ui/OutputPanel.bf +++ b/IDE/src/ui/OutputPanel.bf @@ -327,7 +327,7 @@ namespace IDE.ui int lineIdx = (curLine + lineOfs) % lineCount; if (content.GotoRefrenceAtLine(lineIdx)) - break; + break; } } diff --git a/IDE/src/ui/ProjectPanel.bf b/IDE/src/ui/ProjectPanel.bf index 0019a29e..fedf9188 100644 --- a/IDE/src/ui/ProjectPanel.bf +++ b/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();*/ }); diff --git a/IDEHelper/Compiler/BfCompiler.cpp b/IDEHelper/Compiler/BfCompiler.cpp index f7757a1d..9be06ac6 100644 --- a/IDEHelper/Compiler/BfCompiler.cpp +++ b/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 prevMethodInstance(mContext->mUnreifiedModule->mCurMethodInstance, moduleMethodInstance.mMethodInstance); + SetAndRestoreValue prevTypeInstance(mContext->mUnreifiedModule->mCurTypeInstance, typeInst); + + BfExprEvaluator exprEvaluator(mContext->mUnreifiedModule); + exprEvaluator.mBfEvalExprFlags = BfEvalExprFlags_Comptime; + + SizedArray 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 projectIds; + + BfResolvePassData resolvePassData; + SetAndRestoreValue prevResolvePassData(mResolvePassData, &resolvePassData); + BfPassInstance passInstance(mSystem); + SetAndRestoreValue 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 prevResolvePassData(mResolvePassData, &resolvePassData); + BfPassInstance passInstance(mSystem); + SetAndRestoreValue prevPassInstance(mPassInstance, &passInstance); + + Array 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 prevResolvePassData(mResolvePassData, &resolvePassData); + BfPassInstance passInstance(mSystem); + SetAndRestoreValue prevPassInstance(mPassInstance, &passInstance); + + Array 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& 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"; - - for (auto fieldDef : typeDef->mFields) - { - result += "F"; - result += fieldDef->mName; - matchHelper.AddFieldDef(fieldDef); - } - - for (auto propDef : typeDef->mProperties) - { - if (propDef->GetRefNode() == NULL) - continue; - - result += "P"; - matchHelper.AddPropertyDef(typeDef, propDef); - } - - for (auto methodDef : typeDef->mMethods) - { - if ((methodDef->mMethodType != BfMethodType_Normal) && - (methodDef->mMethodType != BfMethodType_Mixin) && - (methodDef->mMethodType != BfMethodType_Ctor) && - (methodDef->mMethodType != BfMethodType_Dtor)) - continue; - - if (methodDef->mMethodDeclaration == NULL) - continue; - - result += "M"; - matchHelper.AddMethodDef(methodDef); - } + { + typeDefs.Add(typeDef); } itr.MoveToNextHashMatch(); } } +} +String BfCompiler::GetTypeDefInfo(const StringImpl& inTypeName) +{ + Array typeDefs; + GetTypeDefs(inTypeName, typeDefs); + + String result; + TypeDefMatchHelper matchHelper(result); + + for (auto typeDef : typeDefs) + { + auto refNode = typeDef->GetRefNode(); + result += "S"; + matchHelper.AddLocation(refNode); + result += "\n"; + + for (auto fieldDef : typeDef->mFields) + { + result += "F"; + result += fieldDef->mName; + matchHelper.AddFieldDef(fieldDef); + } + + for (auto propDef : typeDef->mProperties) + { + if (propDef->GetRefNode() == NULL) + continue; + + result += "P"; + matchHelper.AddPropertyDef(typeDef, propDef); + } + + for (auto methodDef : typeDef->mMethods) + { + if ((methodDef->mMethodType != BfMethodType_Normal) && + (methodDef->mMethodType != BfMethodType_Mixin) && + (methodDef->mMethodType != BfMethodType_Ctor) && + (methodDef->mMethodType != BfMethodType_Dtor)) + continue; + + 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(); diff --git a/IDEHelper/Compiler/BfCompiler.h b/IDEHelper/Compiler/BfCompiler.h index d859da0f..9c2dbef6 100644 --- a/IDEHelper/Compiler/BfCompiler.h +++ b/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& typeDefs); String GetTypeDefInfo(const StringImpl& typeName); int GetEmitSource(const StringImpl& fileName, StringImpl* outBuffer); diff --git a/IDEHelper/Compiler/BfExprEvaluator.cpp b/IDEHelper/Compiler/BfExprEvaluator.cpp index e6d3ba2b..0650b7fa 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.cpp +++ b/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 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) diff --git a/IDEHelper/Compiler/BfModule.cpp b/IDEHelper/Compiler/BfModule.cpp index f9074614..b0a48ebb 100644 --- a/IDEHelper/Compiler/BfModule.cpp +++ b/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 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); diff --git a/IDEHelper/Compiler/BfModule.h b/IDEHelper/Compiler/BfModule.h index 30a11776..efa5ae8f 100644 --- a/IDEHelper/Compiler/BfModule.h +++ b/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(); diff --git a/IDEHelper/Compiler/CeMachine.cpp b/IDEHelper/Compiler/CeMachine.cpp index 0b9384c3..4a843de2 100644 --- a/IDEHelper/Compiler/CeMachine.cpp +++ b/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