diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba5eaa..f901b33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - this problem only seems to appear after the user has opened a docking form, and maybe not even every time - bug with calling arg functions on projections - seems like object projections are treated as arrays when calling arg functions on them in some cases? - issue with treeview closing when a file with a treeview is moved from one view to another -- `loop()` function used in `s_sub` callbacks is not thread-safe. This doesn't matter right now because RemesPath is single-threaded, but it could matter in the future. +- `loop()` function used in `s_sub` callbacks is not thread-safe. *This doesn't matter right now* because RemesPath is single-threaded, but it could matter in the future. - __Known issues with `Select This` and `Select all children` commands in tree view (FIX THESE ASAP)__: * Empty strings are ignored by `Select this` * Floating point numbers may not be selected correctly (JSON string may have different length from the number representation in the CSV) @@ -49,13 +49,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). 1. Option to customize which [toolbar icons](/docs/README.md#toolbar-icons) are displayed, and their order. 2. New [regex search form](/docs/README.md#regex-search-form) for using treeview to see regex search results in any file -3. [For loops in RemesPath](/docs/RemesPath.md#for-loopsloop-variables-added-in-v60) -4. [`bool`, `s_csv` and `s_fa` vectorized arg functions](/docs/RemesPath.md#vectorized-functions) and [`randint`, `csv_regex`, and `to_csv` non-vectorized arg functions](/docs/RemesPath.md#non-vectorized-functions) to RemesPath. -5. Make second argument of [`s_split` RemesPath function](/docs/RemesPath.md#vectorized-functions) optional; 1-argument variant splits on whitespace. -6. Right-click dropdown menu in [error form](/docs/README.md#error-form-and-status-bar), allowing export of errors to JSON or refreshing the form. -7. The parser is now much better at recovering when an object is missing its closing `'}'` or an array is missing its closing `']'`. -8. Support for [JSON Schema validation](/docs/README.md#validating-json-against-json-schema) of `enum` keyword where the `type` is missing or an array. -9. `Ctrl+Up` now snaps to parent of currently selected node in tree view. `Ctrl+Down` now snaps to the last direct child of the currently selected node. +3. New [document type list box in tree view](/docs/README.md#document-type-box-added-in-v60) +4. [For loops in RemesPath](/docs/RemesPath.md#for-loopsloop-variables-added-in-v60) +5. [`bool`, `s_csv` and `s_fa` RemesPath vectorized arg functions](/docs/RemesPath.md#vectorized-functions) +6. [`randint`, `csv_regex`, `set`, and `to_csv` RemesPath non-vectorized arg functions](/docs/RemesPath.md#non-vectorized-functions) +7. Make second argument of [`s_split` RemesPath function](/docs/RemesPath.md#vectorized-functions) optional; 1-argument variant splits on whitespace. +8. Right-click dropdown menu in [error form](/docs/README.md#error-form-and-status-bar), allowing export of errors to JSON or refreshing the form. +9. The parser is now much better at recovering when an object is missing its closing `'}'` or an array is missing its closing `']'`. +10. Support for [JSON Schema validation](/docs/README.md#validating-json-against-json-schema) of `enum` keyword where the `type` is missing or an array. +11. `Ctrl+Up` now snaps to parent of currently selected node in tree view. `Ctrl+Down` now snaps to the last direct child of the currently selected node. ### Changed diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs b/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs index b36b371..a8e4a2c 100644 --- a/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs +++ b/JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs @@ -60,12 +60,13 @@ private void InitializeComponent() this.PythonStylePathItem = new System.Windows.Forms.ToolStripMenuItem(); this.RemesPathStylePathItem = new System.Windows.Forms.ToolStripMenuItem(); this.ToggleSubtreesItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SelectThisItem = new System.Windows.Forms.ToolStripMenuItem(); this.OpenSortFormItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SelectAllChildrenItem = new System.Windows.Forms.ToolStripMenuItem(); this.CurrentPathBox = new System.Windows.Forms.TextBox(); this.RefreshButton = new System.Windows.Forms.Button(); this.FindReplaceButton = new System.Windows.Forms.Button(); - this.SelectThisItem = new System.Windows.Forms.ToolStripMenuItem(); - this.SelectAllChildrenItem = new System.Windows.Forms.ToolStripMenuItem(); + this.DocumentTypeListBox = new System.Windows.Forms.ListBox(); this.NodeRightClickMenu.SuspendLayout(); this.SuspendLayout(); // @@ -74,10 +75,10 @@ private void InitializeComponent() this.Tree.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.Tree.Location = new System.Drawing.Point(4, 84); + this.Tree.Location = new System.Drawing.Point(4, 99); this.Tree.Name = "Tree"; - this.Tree.Size = new System.Drawing.Size(457, 346); - this.Tree.TabIndex = 6; + this.Tree.Size = new System.Drawing.Size(457, 331); + this.Tree.TabIndex = 8; this.Tree.BeforeExpand += new System.Windows.Forms.TreeViewCancelEventHandler(this.Tree_BeforeExpand); this.Tree.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.Tree_AfterSelect); this.Tree.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.Tree_NodeMouseClick); @@ -105,7 +106,7 @@ private void InitializeComponent() this.QueryBox.Location = new System.Drawing.Point(4, 4); this.QueryBox.Multiline = true; this.QueryBox.Name = "QueryBox"; - this.QueryBox.Size = new System.Drawing.Size(203, 74); + this.QueryBox.Size = new System.Drawing.Size(203, 89); this.QueryBox.TabIndex = 0; this.QueryBox.Text = "@"; this.QueryBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.QueryBox_KeyPress); @@ -163,7 +164,7 @@ private void InitializeComponent() this.OpenSortFormItem, this.SelectAllChildrenItem}); this.NodeRightClickMenu.Name = "NodeRightClickMenu"; - this.NodeRightClickMenu.Size = new System.Drawing.Size(268, 200); + this.NodeRightClickMenu.Size = new System.Drawing.Size(268, 172); // // CopyValueMenuItem // @@ -233,6 +234,12 @@ private void InitializeComponent() this.ToggleSubtreesItem.Size = new System.Drawing.Size(267, 24); this.ToggleSubtreesItem.Text = "Expand/collapse all subtrees"; // + // SelectThisItem + // + this.SelectThisItem.Name = "SelectThisItem"; + this.SelectThisItem.Size = new System.Drawing.Size(267, 24); + this.SelectThisItem.Text = "Select this"; + // // OpenSortFormItem // this.OpenSortFormItem.Name = "OpenSortFormItem"; @@ -240,6 +247,13 @@ private void InitializeComponent() this.OpenSortFormItem.Text = "Sort array..."; this.OpenSortFormItem.Visible = false; // + // SelectAllChildrenItem + // + this.SelectAllChildrenItem.Name = "SelectAllChildrenItem"; + this.SelectAllChildrenItem.Size = new System.Drawing.Size(267, 24); + this.SelectAllChildrenItem.Text = "Select all children"; + this.SelectAllChildrenItem.Visible = false; + // // CurrentPathBox // this.CurrentPathBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) @@ -248,7 +262,7 @@ private void InitializeComponent() this.CurrentPathBox.Name = "CurrentPathBox"; this.CurrentPathBox.ReadOnly = true; this.CurrentPathBox.Size = new System.Drawing.Size(337, 22); - this.CurrentPathBox.TabIndex = 8; + this.CurrentPathBox.TabIndex = 10; this.CurrentPathBox.TabStop = false; // // RefreshButton @@ -270,30 +284,35 @@ private void InitializeComponent() this.FindReplaceButton.Location = new System.Drawing.Point(4, 435); this.FindReplaceButton.Name = "FindReplaceButton"; this.FindReplaceButton.Size = new System.Drawing.Size(114, 23); - this.FindReplaceButton.TabIndex = 7; + this.FindReplaceButton.TabIndex = 9; this.FindReplaceButton.Text = "Find/replace"; this.FindReplaceButton.UseVisualStyleBackColor = true; this.FindReplaceButton.Click += new System.EventHandler(this.FindReplaceButton_Click); this.FindReplaceButton.KeyUp += new System.Windows.Forms.KeyEventHandler(this.TreeViewer_KeyUp); // - // SelectThisItem - // - this.SelectThisItem.Name = "SelectThisItem"; - this.SelectThisItem.Size = new System.Drawing.Size(267, 24); - this.SelectThisItem.Text = "Select this"; - // - // SelectAllChildrenItem - // - this.SelectAllChildrenItem.Name = "SelectAllChildrenItem"; - this.SelectAllChildrenItem.Size = new System.Drawing.Size(267, 24); - this.SelectAllChildrenItem.Text = "Select all children"; - this.SelectAllChildrenItem.Visible = false; + // DocumentTypeListBox + // + this.DocumentTypeListBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.DocumentTypeListBox.FormattingEnabled = true; + this.DocumentTypeListBox.ItemHeight = 16; + this.DocumentTypeListBox.Items.AddRange(new object[] { + "JSON mode", + "JSONL mode", + "INI mode", + "REGEX mode"}); + this.DocumentTypeListBox.Location = new System.Drawing.Point(220, 69); + this.DocumentTypeListBox.Name = "DocumentTypeListBox"; + this.DocumentTypeListBox.Size = new System.Drawing.Size(130, 20); + this.DocumentTypeListBox.TabIndex = 6; + this.DocumentTypeListBox.SelectedIndexChanged += new System.EventHandler(this.DocumentTypeListBox_SelectedIndexChanged); + this.DocumentTypeListBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.TreeViewer_KeyUp); // // TreeViewer // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(471, 461); + this.Controls.Add(this.DocumentTypeListBox); this.Controls.Add(this.FindReplaceButton); this.Controls.Add(this.RefreshButton); this.Controls.Add(this.CurrentPathBox); @@ -337,5 +356,6 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem OpenSortFormItem; private System.Windows.Forms.ToolStripMenuItem SelectThisItem; private System.Windows.Forms.ToolStripMenuItem SelectAllChildrenItem; + private System.Windows.Forms.ListBox DocumentTypeListBox; } } \ No newline at end of file diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.cs b/JsonToolsNppPlugin/Forms/TreeViewer.cs index a626f3d..c92a22c 100644 --- a/JsonToolsNppPlugin/Forms/TreeViewer.cs +++ b/JsonToolsNppPlugin/Forms/TreeViewer.cs @@ -51,6 +51,11 @@ public partial class TreeViewer : Form /// private bool isExpandingAllSubtrees; + /// + /// avoid unnecessary parsing if a change in the selected index of the DocumentTypeComboBox was not performed manually (in which case this is true) + /// + public bool documentTypeIndexChangeWasAutomatic; + /// /// If the user performs an undo or redo action, /// this will be set to true so that the next time the user performs a RemesPath query, @@ -90,6 +95,9 @@ public TreeViewer(JNode json) findReplaceForm = null; csvDelim = '\x00'; csvQuote = '\x00'; + documentTypeIndexChangeWasAutomatic = true; // avoid parsing twice on initialization + SetDocumentTypeListBoxIndex(GetDocumentType()); + documentTypeIndexChangeWasAutomatic = false; FormStyle.ApplyStyle(this, Main.settings.use_npp_styling); } @@ -149,7 +157,8 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e) else if (e.KeyCode == Keys.Tab) { Control next = GetNextControl((Control)sender, !e.Shift); - while ((next == null) || (!next.TabStop)) next = GetNextControl(next, !e.Shift); + while (next is null || !next.TabStop) + next = GetNextControl(next, !e.Shift); next.Focus(); } else if (sender is TreeView && e.Control) @@ -158,6 +167,8 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e) if (e.KeyCode == Keys.Up) { TreeNode selected = Tree.SelectedNode; + if (selected is null) + return; TreeNode parent = selected.Parent; if (parent is null) return; @@ -167,7 +178,7 @@ private void TreeViewer_KeyUp(object sender, KeyEventArgs e) else if (e.KeyCode == Keys.Down) { TreeNode selected = Tree.SelectedNode; - if (selected.Nodes.Count > 0) + if (!(selected is null) && selected.Nodes.Count > 0) { if (!selected.IsExpanded) selected.Expand(); @@ -1149,7 +1160,7 @@ private void RefreshButton_Click(object sender, EventArgs e) { shouldRefresh = false; string cur_fname = Npp.notepad.GetCurrentFilePath(); - (bool _, JNode new_json, bool _, DocumentType _) = Main.TryParseJson(); + (bool _, JNode new_json, bool _, DocumentType _) = Main.TryParseJson(GetDocumentTypeFromListBox()); if (new_json == null) return; fname = cur_fname; @@ -1173,6 +1184,45 @@ private void TreeViewer_DoubleClick(object sender, EventArgs e) Npp.notepad.OpenFile(fname); } + public void SetDocumentTypeListBoxIndex(DocumentType documentType) + { + switch (documentType) + { + case DocumentType.NONE: + case DocumentType.JSON: + DocumentTypeListBox.SelectedIndex = 0; + break; + case DocumentType.JSONL: DocumentTypeListBox.SelectedIndex = 1; break; + case DocumentType.INI: DocumentTypeListBox.SelectedIndex = 2; break; + case DocumentType.REGEX: DocumentTypeListBox.SelectedIndex = 3; break; + default: break; + } + } + + public DocumentType GetDocumentTypeFromListBox() + { + switch (DocumentTypeListBox.SelectedIndex) + { + case 0: return DocumentType.JSON; + case 1: return DocumentType.JSONL; + case 2: return DocumentType.INI; + case 3: return DocumentType.REGEX; + default: return DocumentType.NONE; + } + } + + private void DocumentTypeListBox_SelectedIndexChanged(object sender, EventArgs e) + { + if (documentTypeIndexChangeWasAutomatic) + return; + DocumentType newDocumentType = GetDocumentTypeFromListBox(); + DocumentType oldDocumentType = GetDocumentType(); + if (oldDocumentType == newDocumentType) + return; + (_, JNode newJson, _, _) =Main.TryParseJson(newDocumentType); + JsonTreePopulate(newJson); + } + /// /// Just the filename, no directory information.

/// If no fname supplied, gets the relative filename for this TreeViewer's fname. diff --git a/JsonToolsNppPlugin/Forms/TreeViewer.resx b/JsonToolsNppPlugin/Forms/TreeViewer.resx index 5439864..bc10a73 100644 --- a/JsonToolsNppPlugin/Forms/TreeViewer.resx +++ b/JsonToolsNppPlugin/Forms/TreeViewer.resx @@ -125,7 +125,7 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADC - CgAAAk1TRnQBSQFMAgEBCAEAAZgBAQGYAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo + CgAAAk1TRnQBSQFMAgEBCAEAAbABAQGwAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo AwABQAMAATADAAEBAQABCAYAAQwYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5 AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA diff --git a/JsonToolsNppPlugin/JSONTools/JNode.cs b/JsonToolsNppPlugin/JSONTools/JNode.cs index b8f2374..2e6e207 100644 --- a/JsonToolsNppPlugin/JSONTools/JNode.cs +++ b/JsonToolsNppPlugin/JSONTools/JNode.cs @@ -1977,6 +1977,7 @@ public JNode GetQuery() /// public JNode Evaluate(JNode inp) { + ArgFunction.InitializeGlobals(mutatesInput); JNode lastStatement = EvaluateStatementsFromStartToEnd(inp, 0, statements.Count); Reset(); return lastStatement; @@ -2062,7 +2063,6 @@ private JNode EvaluateStatementsFromStartToEnd(JNode inp, int start, int end) { JNode lastStatement = null; indexInStatements = start; - ArgFunction.regexSearchResultsShouldBeCached = !mutatesInput; while (indexInStatements < end) { JNode statement = statements[indexInStatements]; diff --git a/JsonToolsNppPlugin/JSONTools/RemesPath.cs b/JsonToolsNppPlugin/JSONTools/RemesPath.cs index 575c995..66266e4 100644 --- a/JsonToolsNppPlugin/JSONTools/RemesPath.cs +++ b/JsonToolsNppPlugin/JSONTools/RemesPath.cs @@ -1198,8 +1198,6 @@ private JNode ParseQuery(List toks) int pos = 0; int end = toks.Count; var context = new JQueryContext(); - ArgFunction.csvDelimiterInLastQuery = '\x00'; - ArgFunction.csvQuoteCharInLastQuery = '\x00'; Dictionary isVarnameFunctionOfInput = DetermineWhichVariablesAreFunctionsOfInput(toks); while (pos < end) { @@ -1243,7 +1241,7 @@ private Dictionary DetermineWhichVariablesAreFunctionsOfInput(List else pos = endOfStatement + 1; } - ArgFunction.regexSearchResultsShouldBeCached = !containsMutation; // any mutation could potentially mutate values inside the cache, which is very bad + ArgFunction.InitializeGlobals(containsMutation); if (containsMutation) { // no variable is safe from the mutation, not even ones that were declared before the mutation expression diff --git a/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs b/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs index 4af4da1..054a54e 100644 --- a/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs +++ b/JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs @@ -1084,6 +1084,21 @@ public void PadToMaxArgs(List args) } } + /// + /// there are currently three mutable public global variables that are set in queries:

+ /// regexSearchResultsShouldBeCached, csvDelimiterInLastQuery, and csvQuoteCharInLastQuery.

+ /// These all relate to s_csv and s_fa.

+ /// This is basically a hack, because RemesPath does not currently support deep top-down introspection of a query's AST + /// to determine if s_csv or s_fa has been called in a certain way. + ///
+ /// + public static void InitializeGlobals(bool containsMutation) + { + regexSearchResultsShouldBeCached = !containsMutation; + csvDelimiterInLastQuery = '\x00'; + csvQuoteCharInLastQuery = '\x00'; + } + #region NON_VECTORIZED_ARG_FUNCTIONS /// @@ -1691,6 +1706,28 @@ public static JNode Items(List args) return new JArray(0, its); } + /// + /// set(x: array) -> object

+ /// returns an object where the keys are all the stringified unique values in x and the values are all null. + ///
+ public static JNode Set(List args) + { + List arr = ((JArray)args[0]).children; + var set = new Dictionary(); + foreach (JNode child in arr) + { + var val = child.value; + string key = val is string s ? JNode.StrToString(s, false) : child.ToString(); + set[key] = new JNode(); + } + return new JObject(0, set); + } + + /// + /// unique(x: array, is_sorted: bool=false) -> array

+ /// returns an array of all the distinct values in x (they must all be scalars).

+ /// If is_sorted, the returned array is sorted. Otherwise the order is random. + ///
public static JNode Unique(List args) { var itbl = (JArray)args[0]; @@ -1698,6 +1735,8 @@ public static JNode Unique(List args) var uniq = new HashSet(); foreach (JNode val in itbl.children) { + if ((val.type & Dtype.SCALAR) == 0) + throw new RemesPathArgumentException("First argument to unique must be an array of all scalars.", 0, FUNCTIONS["unique"]); uniq.Add(val.value); } var uniq_list = new List(); @@ -2347,7 +2386,7 @@ public static JNode StrFind(List args) private static readonly int MAX_DOC_SIZE_CACHE_REGEX_SEARCH = IntPtr.Size * 1_250_000; /// - /// do not cache for any reason. This is usually because the query involves mutation and there is some danger of mutating a value in the cache. + /// if false, do not cache for any reason. This is usually because the query involves mutation and there is some danger of mutating a value in the cache. /// public static bool regexSearchResultsShouldBeCached = true; @@ -3306,6 +3345,7 @@ public static JNode ObjectsToJNode(object obj) ["randint"] = new ArgFunction(RandomInteger, "randint", Dtype.INT, 1, 2, false, new Dtype[] {Dtype.INT, Dtype.INT}, false), ["range"] = new ArgFunction(Range, "range", Dtype.ARR, 1, 3, false, new Dtype[] {Dtype.INT, Dtype.INT, Dtype.INT}), ["s_join"] = new ArgFunction(StringJoin, "s_join", Dtype.STR, 2, 2, false, new Dtype[] {Dtype.STR, Dtype.ARR}), + ["set"] = new ArgFunction(Set, "set", Dtype.OBJ, 1, 1, false, new Dtype[] {Dtype.ARR}), ["sort_by"] = new ArgFunction(SortBy, "sort_by", Dtype.ARR, 2, 3, false, new Dtype[] { Dtype.ARR, Dtype.STR | Dtype.INT | Dtype.FUNCTION, Dtype.BOOL }), ["sorted"] = new ArgFunction(Sorted, "sorted", Dtype.ARR, 1, 2, false, new Dtype[] {Dtype.ARR, Dtype.BOOL}), ["stringify"] = new ArgFunction(Stringify, "stringify", Dtype.STR, 1, 1, false, new Dtype[] { Dtype.ANYTHING }), diff --git a/JsonToolsNppPlugin/Main.cs b/JsonToolsNppPlugin/Main.cs index fc5c307..9490795 100644 --- a/JsonToolsNppPlugin/Main.cs +++ b/JsonToolsNppPlugin/Main.cs @@ -510,8 +510,7 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen if (wasAutotriggered && len > sizeThreshold) return (false, null, false, DocumentType.NONE); string text = Npp.editor.GetText(len + 1); - if (documentType == DocumentType.JSON || documentType == DocumentType.NONE || // if user didn't specify how to parse the document - (documentType == DocumentType.REGEX && (info == null || info.json == null || info.json.type == Dtype.NULL))) // or user specified REGEX and document was not already parsed + if (documentType == DocumentType.NONE) // if user didn't specify how to parse the document { // 1. check if the document has a file extension with an associated DocumentType string fileExtension = Npp.FileExtension().ToLower(); @@ -520,7 +519,7 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen documentType = docTypeFromExtension; // 2. check if the user previously chose a document type, and use that if so. // this overrides the default for the file extension. - if (previouslyChosenDocType != DocumentType.NONE && documentType != DocumentType.REGEX) + if (previouslyChosenDocType != DocumentType.NONE) documentType = previouslyChosenDocType; } if (documentType == DocumentType.INI) @@ -574,13 +573,21 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen foreach ((int start, int end) in selRanges) { string selRange = Npp.GetSlice(start, end); - JNode subJson = documentType == DocumentType.REGEX ? new JNode(selRange, start) : jsonParser.Parse(selRange); + JNode subJson; + if (documentType == DocumentType.REGEX) + { + subJson = new JNode(selRange, 0); + } + else + { + subJson = jsonParser.Parse(selRange); + lints.AddRange(jsonParser.lint); + fatalErrors.Add(jsonParser.fatal); + if (errorMessage == null) + errorMessage = jsonParser.fatalError?.ToString(); + } string key = $"{start},{end}"; obj[key] = subJson; - lints.AddRange(jsonParser.lint); - fatalErrors.Add(jsonParser.fatal); - if (errorMessage == null) - errorMessage = jsonParser.fatalError?.ToString(); } } if (!isRecursion && oneSelRange && SelectionManager.NoSelectionIsValidJson(json, !noTextSelected, fatalErrors)) @@ -627,7 +634,8 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen Npp.notepad.SetStatusBarSection($"JSON with fatal errors - {lintCount} errors (Alt-P-J-E to view)", StatusBarSection.DocType); } - else if (!(!info.usesSelections && documentType != DocumentType.JSON && documentType != DocumentType.JSONL)) // only touch status bar if whole doc is parsed as JSON or JSON Lines + else if (jsonParser.state < ParserState.FATAL && !(!info.usesSelections && documentType != DocumentType.JSON && documentType != DocumentType.JSONL)) + // only touch status bar if whole doc is parsed as JSON or JSON Lines, and there are no fatal errors { string doctypeDescription; switch (jsonParser.state) @@ -648,7 +656,12 @@ public static (bool fatal, JNode node, bool usesSelections, DocumentType Documen Npp.notepad.SetStatusBarSection(doctypeStatusBarEntry, StatusBarSection.DocType); } if (info.tv != null) + { info.tv.json = json; + info.tv.documentTypeIndexChangeWasAutomatic = true; + info.tv.SetDocumentTypeListBoxIndex(documentType); + info.tv.documentTypeIndexChangeWasAutomatic = false; + } info.comments = comments; jsonFileInfos[activeFname] = info; return (fatal, json, info.usesSelections, documentType); diff --git a/JsonToolsNppPlugin/Properties/AssemblyInfo.cs b/JsonToolsNppPlugin/Properties/AssemblyInfo.cs index f0397d3..a8a7a82 100644 --- a/JsonToolsNppPlugin/Properties/AssemblyInfo.cs +++ b/JsonToolsNppPlugin/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("5.8.0.16")] -[assembly: AssemblyFileVersion("5.8.0.16")] +[assembly: AssemblyVersion("5.8.0.17")] +[assembly: AssemblyFileVersion("5.8.0.17")] diff --git a/JsonToolsNppPlugin/Tests/RemesPathTests.cs b/JsonToolsNppPlugin/Tests/RemesPathTests.cs index 2b8fd4a..ab05e2d 100644 --- a/JsonToolsNppPlugin/Tests/RemesPathTests.cs +++ b/JsonToolsNppPlugin/Tests/RemesPathTests.cs @@ -488,6 +488,7 @@ public static bool Test() new Query_DesiredResult("to_csv(@.foo[1], `.`)", "\"\\\"3.0\\\"\\r\\n\\\"4.0\\\"\\r\\n\\\"5.0\\\"\""), new Query_DesiredResult("to_csv(@.foo[:][0],,`\\n`)", "\"0\\n3.0\\n6.0\""), new Query_DesiredResult("to_csv(@.foo[:]{a: @[1]})", "\"a\\r\\n1\\r\\n4.0\\r\\n7.0\""), + new Query_DesiredResult("set(concat(@.foo[0], @.foo[1][:1], j`[\"a\", \"a\"]`))", "{\"0\": null, \"1\": null, \"2\": null, \"3.0\": null, \"a\": null}"), // ===================== s_csv CSV parser ======================== // 3-column 14 rows, ',' delimiter, CRLF newline, '"' quote character, newline before EOF new Query_DesiredResult("s_csv(`nums,names,cities\\r\\n" + diff --git a/JsonToolsNppPlugin/Tests/TestRunner.cs b/JsonToolsNppPlugin/Tests/TestRunner.cs index c203e1e..b17eebc 100644 --- a/JsonToolsNppPlugin/Tests/TestRunner.cs +++ b/JsonToolsNppPlugin/Tests/TestRunner.cs @@ -42,7 +42,7 @@ public static async Task RunAll() (RemesParserTester.Test, "RemesPath parser and compiler", false, false), (RemesPathThrowsWhenAppropriateTester.Test, "RemesPath throws errors on bad inputs", false, false), (RemesPathAssignmentTester.Test, "RemesPath assignment operations", false, false), - (() => RemesPathFuzzTester.Test(10000, 20), fuzzTestName, false, false), + (() => RemesPathFuzzTester.Test(5000, 20), fuzzTestName, false, false), (RemesPathComplexQueryTester.Test, "multi-statement queries in RemesPath", false, false), (JsonSchemaMakerTester.Test, "JsonSchema generator", false, false), diff --git a/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs b/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs index e734484..8635f87 100644 --- a/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs +++ b/JsonToolsNppPlugin/Tests/UserInterfaceTests.cs @@ -15,9 +15,6 @@ public class UserInterfaceTester private static int lowestFilenameNumberNotUsed = 0; - // later on tests will be added in a method; we don't want to add them every time the tests are run, only the first time - public static bool hasRunTestsThisSession = false; - public static string lastClipboardValue = null; private static RemesParser remesParser = new RemesParser(); @@ -279,430 +276,484 @@ public static bool ExecuteFileManipulation(string command, List messages messages.Add(regexSettingsErrorMsg); Main.regexSearchForm.SearchButton.PerformClick(); break; + case "set_document_type": + string newDocumentTypeName = (string)args[0]; + if (!hasTreeView) + { + messages.Add($"FAIL: Wanted to set the document type to {newDocumentTypeName} with the treeview's combo box, but the treeview wasn't open"); + return true; + } + switch (newDocumentTypeName) + { + case "NONE": + case "JSON": + Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.JSON); + break; + case "JSONL": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.JSONL); break; + case "INI": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.INI); break; + case "REGEX": Main.openTreeViewer.SetDocumentTypeListBoxIndex(DocumentType.REGEX); break; + } + messages.Add($"Set document type to {newDocumentTypeName} with the treeview's combo box"); + break; default: throw new ArgumentException($"Unrecognized command {command}"); } return false; } - public static List<(string command, object[] args)> testcases = new List<(string command, object[] args)> + public static bool Test() { - ("overwrite", new object[]{"[1, 2, false]\r\n{\"a\": 3, \"b\": 1.5}\r\n[{\"c\": [null]}, -7]"}), // first line: 0 to 13, second line: 15 to 33, third line: 35 to 54 - ("select", new object[]{new string[]{ "0,13", "15,33", "35,54" } }), - // when pretty-printed in PPrint style looks like this: - //[ - // 1, - // 2, - // false - //] - //{ - // "a": 3, - // "b": 1.5 - //} - //[ - // {"c": [null]}, - // -7 - //] - // first JSON: 0 to 31, second JSON: 33 to 64, third JSON: 66 to 98 - ("pretty_print", new object[]{ }), - ("compare_text", new object[]{ "[\r\n 1,\r\n 2,\r\n false\r\n]\r\n{\r\n \"a\": 3,\r\n \"b\": 1.5\r\n}\r\n[\r\n {\"c\": [null]},\r\n -7\r\n]"}), - ("compare_selections", new object[]{new string[]{"0,31", "33,64", "66,98"} }), - // when compressed looks like this: - //[1,2,false] - //{"a":3,"b":1.5} - //[{"c":[null]},-7] - // first JSON: 0 to 11, second JSON: 13 to 28, third JSON: 30 to 47 - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,false]\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), - ("compare_selections", new object[]{new string[] {"0,11", "13,28", "30,47"} }), - // TEST THAT TREENODE CLICKS GO TO RIGHT LOCATION - ("tree_open", new object[]{}), - ("treenode_click", new object[]{ new string[] { "0,11 : [3]", "2 : false"} }), - ("compare_selections", new object[]{new string[] {"5,5"} }), // in front of @.`0,11`[2] - ("treenode_click", new object[]{ new string[] {"30,47 : [2]", "0 : {1}", "c : [1]", "0 : null"} }), - ("compare_selections", new object[]{new string[] {"37,37"} }), // in front of @.`30,47`[0].c[0] - ("insert_text", new object[]{11, "\r\n"}), - ("compare_text", new object[]{"[1,2,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), - // TEST QUERY ONLY SOME SELECTIONS - ("tree_query", new object[]{"@..g`[bc]`"}), - ("treenode_click", new object[]{new string[]{"15,30 : [1]", "0 : 1.5"} }), - ("compare_selections", new object[]{new string[]{"26,26" } }), - ("treenode_click", new object[]{new string[] {"32,49 : [1]", "0 : [1]", "0 : null"} }), - ("compare_selections", new object[]{new string[] {"39,39"} }), - // TEST DELETIONS/INSERTIONS THAT START AND END WITHIN A SINGLE SELECTION - ("delete_text", new object[]{2, 2}), - ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), - ("tree_query", new object[]{"@..*[is_num(@)][@ >= 2]"}), - ("treenode_click", new object[]{new string[] {"0,9 : []"} }), - ("treenode_click", new object[]{new string[] {"13,28 : [1]", "0 : 3"} }), - ("compare_selections", new object[]{new string[] {"18,18"} }), - ("insert_text", new object[]{27, ", \"c\": 0"}), // add new key to second JSON - ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5, \"c\": 0}\r\n[{\"c\":[null]},-7]"}), - ("tree_query", new object[]{"@..c"}), - ("treenode_click", new object[]{new string[] {"13,36 : [1]", "0 : 0"} }), - ("treenode_click", new object[]{new string[] {"38,55 : [1]"} }), - // TEST SELECT_EVERY_VALID - ("overwrite", new object[]{ "Errör 1 [foo]: [1,2,NaN]\r\nWarning 2: {\"ä\":3}\r\nInfo 3 {bar}: [[6,{\"b\":false}],-7]\r\nError 4: \"baz\" \"quz\"\r\nError 5: \"string with no close quote\r\nError 6: not {\"even\" [json" }), - ("select", new object[]{new string[]{"0,0"} }), - ("select_every_valid", new object[]{}), - ("compare_selections", new object[]{new string[] { "16,25", "38,46", "62,82", "93,98", "99,104" } }), - ("compare_path_to_position", new object[]{22, "[`16,25`][2]"}), - ("compare_path_to_position", new object[]{101, "[`99,104`]"}), - ("compare_path_to_position", new object[]{74, "[`62,82`][0][1].b"}), - // TEST SELECT_EVERY_VALID ON A SUBSET OF THE FILE - ("select", new object[]{new string[] {"1,46", "93,100", "161,167"} }), - ("select_every_valid", new object[]{}), - ("compare_selections", new object[]{new string[] {"16,25", "38,46", "93,98", "161,167"} }), - // TEST SORT FORM - ("overwrite", new object[]{"[1,3,2]\r\n[6,5,44]\r\n[\"a\",\"c\",\"boo\"]"}), - ("select", new object[]{new string[]{"0,0"} }), - ("select_every_valid", new object[]{}), - ("compare_selections", new object[]{ new string[] { "0,7", "9,17", "19,34" } }), - ("sort_form_open", new object[]{}), - ("sort_form_run", new object[]{".g`^(?!0,)`", true, false, 1, ""}), // sort selections not starting at 0, as strings, ascending - ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"a\",\r\n \"boo\",\r\n \"c\"\r\n]"}), - ("sort_form_run", new object[]{"", true, true, 3, "s_len(str(@))"}), // sort all arrays biggest to smallest using the length of a node's string representation as key - ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), - // TEST SELECTING NON-JSON RANGE DOES NOT FORGET SELECTIONS - ("select", new object[]{new string[] {"8,15"} }), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[\"boo\",\"a\",\"c\"]"}), - // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC - ("delete_text", new object[]{20, 5}), // select treenode json and json children with array - ("insert_text", new object[]{20, "[1, NaN,\"b\"]"}), - ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[[1, NaN,\"b\"],\"a\",\"c\"]"}), - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"19,41 : [3]", "0 : [3]"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"20,32"} }), - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"21,22", "24,27", "28,31"} }), - ("delete_text", new object[]{20, 12}), // now select treenode json and json children with object - ("insert_text", new object[]{20, "{\"a\": [null], \"b\": {}}"}), - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"20,42"} }), - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"26,32", "39,41"} }), - ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}", "a : [1]", "0 : null"} }), // try selecting treenode json with scalar - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"27,31"} }), - ("delete_text", new object[]{20, 22}), // revert to how it was before select-treenode-json tests - ("insert_text", new object[]{20, "\"boo\""}), - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"9,17 : [3]"} }), // try selecting json and json children on a different array - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"10,12", "13,14", "15,16"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"9,17"} }), - // TEST FILE WITH ONLY ONE JSON DOCUMENT - ("file_open", new object[]{1}), - ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]//a"}), - // TEST PRETTY-PRINT WITH COMMENTS AND TABS - ("pretty_print", new object[]{true, true}), - ("compare_text", new object[]{"[\r\n\t[\r\n\t\t\"Я\",\r\n\t\t1,\r\n\t\t\"a\"\r\n\t],\r\n\t// foo\r\n\t[\r\n\t\t\"◐\",\r\n\t\t2,\r\n\t\t\"b\"\r\n\t],\r\n\t// bar\r\n\t[\r\n\t\t\"ồ\",\r\n\t\t3,\r\n\t\t\"c\"\r\n\t],\r\n\t// baz\r\n\t[\r\n\t\t\"ェ\",\r\n\t\t4,\r\n\t\t\"d\"\r\n\t],\r\n\t[\r\n\t\t\"草\",\r\n\t\t5,\r\n\t\t\"e\"\r\n\t],\r\n\t[\r\n\t\t\"😀\",\r\n\t\t6,\r\n\t\t\"f\"\r\n\t]\r\n]\r\n//a\r\n"}), - // TEST PRETTY-PRINT WITH COMMENTS ONLY - ("pretty_print", new object[]{false, true}), - ("compare_text", new object[]{"[\r\n [\r\n \"Я\",\r\n 1,\r\n \"a\"\r\n ],\r\n // foo\r\n [\r\n \"◐\",\r\n 2,\r\n \"b\"\r\n ],\r\n // bar\r\n [\r\n \"ồ\",\r\n 3,\r\n \"c\"\r\n ],\r\n // baz\r\n [\r\n \"ェ\",\r\n 4,\r\n \"d\"\r\n ],\r\n [\r\n \"草\",\r\n 5,\r\n \"e\"\r\n ],\r\n [\r\n \"😀\",\r\n 6,\r\n \"f\"\r\n ]\r\n]\r\n//a\r\n" -}), - // TEST PRETTY-PRINT WITH TABS ONLY - ("pretty_print", new object[]{true}), - ("compare_text", new object[]{"[\r\n\t[\"Я\", 1, \"a\"],\r\n\t[\"◐\", 2, \"b\"],\r\n\t[\"ồ\", 3, \"c\"],\r\n\t[\"ェ\", 4, \"d\"],\r\n\t[\"草\", 5, \"e\"],\r\n\t[\"😀\", 6, \"f\"]\r\n]"}), - // TEST TREE ON WHOLE DOCUMENT - ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]"}), - ("tree_open", new object[]{}), - ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"◐\"" } }), - ("compare_selections", new object[]{new string[] {"36,36"} }), - ("compare_path_to_position", new object[]{36, "[1][0]"}), - ("treenode_click", new object[]{ new string[] { "5 : [3]", "1 : 6" } }), - ("compare_selections", new object[]{new string[] {"146,146"} }), - ("compare_path_to_position", new object[]{147, "[5][1]"}), - ("tree_query", new object[]{"@[:][1] = @ + 3" }), // mutate document - ("compare_text", new object[]{"[\r\n [\"Я\", 4, \"a\"],\r\n [\"◐\", 5, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n [\"草\", 8, \"e\"],\r\n [\"😀\", 9, \"f\"]\r\n]"}), - ("compare_path_to_position", new object[]{126, "[5][1]"}), - // TEST SELECT NON-JSON RANGE AND MAKE SURE WHOLE DOCUMENT STILL PARSED (WHEN NOT IN SELECTION MODE) - ("select", new object[]{new string[] {"1,7"} }), - ("compress", new object[]{}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],[\"草\",8,\"e\"],[\"😀\",9,\"f\"]]"}), - // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"2 : [3]"} }), // try selecting json and json children with array - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"29,34", "35,36", "37,40"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"28,41"} }), - ("treenode_click", new object[]{new string[] {"3 : [3]", "0 : \"ェ\"" } }), // try selecting treenode json with scalar - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"43,48"} }), - ("overwrite", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\"😀\",9,\"f\"]]"}), // try selecting treenode json and json children with object - ("tree_query", new object[]{"@"}), - ("treenode_click", new object[]{new string[] {"4 : {3}"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"56,92"} }), - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"63,66", "71,74", "85,91"} }), - ("select", new object[]{new string[]{"0,0"} }), - ("tree_query", new object[]{"@![4][@[1] >= 7]"}), // try selecting treenode json children when parent is RemesPath query node - ("treenode_click", new object[]{new string[]{ } }), - ("select_treenode_json_children", new object[]{}), - ("pretty_print", new object[]{}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\r\n \"ェ\",\r\n 7,\r\n \"d\"\r\n],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\r\n \"😀\",\r\n 9,\r\n \"f\"\r\n]]"}), - // TEST NON-MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS - ("select_whole_doc", new object[]{}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), - ("tree_query", new object[]{"var a = @[0];\r\nvar b = @[1];\r\nvar c = @[-1];\r\nvar d = a + b * s_len(str(c));"}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), - ("treenode_click", new object[]{new string[] { "0 : \"Я◐◐\"" } }), - ("treenode_click", new object[]{new string[] {"1 : 9"} }), - ("treenode_click", new object[]{new string[] { "2 : \"ab\"" } }), - ("tree_query", new object[]{"var mod_3s = (@[:][type(@) != object])[:]->append(@, @[1] % 3);\r\n" + - "var gb_m3 = group_by(mod_3s, -1);\r\n" + - "gb_m3.`1`"}), - ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), - ("treenode_click", new object[]{new string[] { "0 : [4]", "2 : \"a\"" } }), - ("treenode_click", new object[]{new string[] { "1 : [4]", "1 : 7" } }), - // TEST MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS - ("tree_query", new object[]{"var a = @[0];\r\n" + - "var b = @[1];\r\n" + - "var c = @[-1];\r\n" + - "var d = a + b * s_len(str(c));\r\n" + - "a[0] = @ + d[0];\r\nb[1] = @ + c[1];\r\nc[2] = @ + a[0]"}), - ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 14, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}), - ("treenode_click", new object[]{new string[]{"0 : [3]", "0 : \"ЯЯ◐◐\"" } }), - ("compare_selections", new object[]{new string[]{ "8,8" } }), - ("treenode_click", new object[]{new string[] {"1 : [3]", "1 : 14" } }), - ("compare_selections", new object[]{new string[]{ "44,44" } }), - ("treenode_click", new object[]{new string[]{"5 : [3]", "2 : \"fЯЯ◐◐\"" } }), - ("compare_selections", new object[]{new string[]{ "160,160" } }), - ("tree_query", new object[]{"var bumba = @[1][1]; bumba = @ / 7; bumba"}), - ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 2.0, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}), - ("treenode_click", new object[]{ new string[] { } }), - ("compare_selections", new object[]{new string[] {"44,44"} }), - ("tree_compare_query_result", new object[]{"2.0"}), - // TEST THAT JAVASCRIPT AND PYTHON COMMENTS AT EOF DON'T CAUSE PROBLEMS - ("overwrite", new object[]{"\r\n[1,2,\r\n3]#"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,3]"}), - ("overwrite", new object[]{"[1,2,3]#"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,3]"}), - ("overwrite", new object[]{"[1\r\n ,2,\r\n3]//"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,3]"}), - ("overwrite", new object[]{"[1,2,\r\n3]#bar"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,3]"}), - ("overwrite", new object[]{"[1,2,-9e15]\r\n//foo"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[1,2,-9E+15]"}), - // TEST PARSE JSON LINES - ("overwrite", new object[]{"[1,2,3]\r\n{\"a\": 1, \"b\": [-3,-4]}\r\n-7\r\nfalse"}), - ("tree_open", new object[]{}), // to close the tree so it can be reopened - ("tree_open", new object[]{DocumentType.JSONL}), - ("tree_query", new object[]{"@..* [abs(+@) < 3] = @ / 4"}), // divide values in open range (-3, 3) by 4; this should keep the file in JSON Lines format - ("compare_text", new object[]{"[0.25,0.5,3]\n{\"a\":0.25,\"b\":[-3,-4]}\n-7\n0.0"}), - ("tree_query", new object[]{"@"}), // re-parse document - ("treenode_click", new object[]{new string[] {"3 : 0.0"} }), - ("compare_selections", new object[]{new string[] {"39,39"} }), - ("compare_path_to_position", new object[]{32, "[1].b[1]"}), - // TEST PARSE INI FILE - ("overwrite", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\n bar=2\r\n [дaz]\r\n baz=3\r\n ;b\r\n baz2 = 7 \r\n[quz]\r\nquz=4\r\n;c"}), - ("tree_open", new object[]{}), - ("tree_open", new object[]{DocumentType.INI}), - ("tree_query", new object[]{"@..g`z`"}), - ("treenode_click", new object[]{new string[] {"0 : {2}", "baz2 : \"7 \""} }), - ("compare_selections", new object[]{new string[]{"63,63" } }), - ("compare_path_to_position", new object[]{81, ".quz.quz"}), - ("tree_query", new object[]{"@.bar.bar = @ * int(@)"}), // edit one value - ("compare_text", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\nbar=22\r\n[дaz]\r\nbaz=3\r\n;b\r\nbaz2=7 \r\n[quz]\r\nquz=4\r\n;c\r\n"}), - // TEST RUNNING SAME QUERY MULTIPLE TIMES ON SAME INPUT DOES NOT HAVE DIFFERENT RESULTS - // test when mutating a compile-time constant array - ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}), - ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}), - ("treenode_click", new object[]{new string[] {"1 : 2"} }), - // test when mutating a compile-time constant loop variable - ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}), - ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}), - ("treenode_click", new object[]{new string[] {"0 : 2"} }), - ("treenode_click", new object[]{new string[] {"1 : 2"} }), - // test when mutating projections (both object and array) - ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}), - ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}), - ("treenode_click", new object[]{new string[] {"0 : [2]", "1 : 2"} }), - ("treenode_click", new object[]{new string[] {"1 : {1}", "a : 2"} }), - // test when mutating map projection - ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}), - ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}), - ("treenode_click", new object[]{new string[] {"0 : 0"} }), - // TEST ASSIGNMENT OPERATIONS ON MULTIPLE SELECTIONS (back to file with three arrays that were sorted by the sort form) - ("file_open", new object[]{0}), - ("tree_query", new object[]{"@[:][is_num(@)] = @ / 4"}), - ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n]\r\n[\r\n 11.0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), - ("treenode_click", new object[]{new string[] {"37,72 : [3]", "1 : 1.25"} }), - ("compare_selections", new object[]{ new string[] { "55,55" } }), - // TEST DELETION THAT STARTS BEFORE A SELECTION AND ENDS INSIDE IT - ("insert_text", new object[]{47, "["}), - ("delete_text", new object[]{ 35, 12 }), - ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n][0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[0.25,0.75,0.5][0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), - // TEST INSERTION THAT BEGINS ON THE FIRST CHAR OF A SELECTION - ("insert_text", new object[]{15, "blah"}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), - // TEST DELETION THAT STARTS INSIDE A SELECTION AND ENDS AFTER IT - ("insert_text", new object[]{26, "]"}), - ("delete_text", new object[]{27, 7}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), - ("compress", new object[]{}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), - // TEST QUERY THAT PRODUCES OBJECT WITH NON-"START,END" KEYS ON A FILE WITH SELECTIONS - ("tree_query", new object[]{"j`{\"a\": \"foo\", \"b\\n\": [1, 2]}`"}), - ("treenode_click", new object[]{new string[] {"a : \"foo\""} }), - ("treenode_click", new object[]{new string[] {"b\\n : [2]", "1 : 2"} }), - // TEST MULTI-STATEMENT QUERY THAT DOESN'T MUTATE ON A FILE WITH SELECTIONS - ("tree_query", new object[]{"var s = str(@);\r\n" + - "var sl = s_len(s);\r\n" + - "(s + ` `) * sl"}), - ("treenode_click", new object[]{new string[] {"27,42 : [3]", "0 : \"boo boo boo \""} }), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), - ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" + - "var s_lens = s_len(this_s[:][1]);\r\n" + - "var min_slen = min(s_lens);\r\n" + - "var shortest_strs = this_s[s_lens == min_slen][0]"}), - ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), - ("treenode_click", new object[]{ new string[] { "0,15 : [1]", "0 : 0.5"} }), - ("compare_selections", new object[]{new string[]{"11,11" } }), - ("treenode_click", new object[]{ new string[] { "27,42 : [2]", "1 : \"c\""} }), - ("compare_selections", new object[]{new string[]{"38,38" } }), - // TEST MULTI-STATEMENT QUERY THAT *DOES* MUTATE ON A FILE WITH SELECTIONS - ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" + - "var s_lens = s_len(this_s[:][1]);\r\n" + - "var max_slen = max(s_lens);\r\n" + - "var longest_strs = this_s[s_lens == max_slen][0];\r\n" + - "longest_strs = str(@) + ` is champion!`;\r\n" + - "longest_strs"}), - ("compare_text", new object[]{"[\r\n \"0.25 is champion!\",\r\n \"0.75 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!\"\r\n][\r\n \"boo is champion!\",\r\n \"a\",\r\n \"c\"\r\n]"}), - ("treenode_click", new object[]{ new string[] { "69,106 : [1]", "0 : \"1.25 is champion!\""} }), - ("treenode_click", new object[]{ new string[] { "106,154 : [1]", "0 : \"boo is champion!\""} }), - ("tree_compare_query_result", new object[]{"{\"0,65\": [\"0.25 is champion!\", \"0.75 is champion!\"], \"106,154\": [\"boo is champion!\"], \"69,106\": [\"1.25 is champion!\"]}"}), - // TEST PRINTING SELECTION-BASED FILE WITH TABS - ("pretty_print", new object[]{true}), - ("compare_text", new object[]{"[\r\n\t\"0.25 is champion!\",\r\n\t\"0.75 is champion!\",\r\n\t0.5\r\n]blah[\r\n\t0,\r\n\t\"1.25 is champion!\"\r\n][\r\n\t\"boo is champion!\",\r\n\t\"a\",\r\n\t\"c\"\r\n]"}), - // TEST QUERIES WITH LOOP VARIABLES ON SELECTION-BASED FILE - ("tree_query", new object[]{// find all the strings in the array, then add the i^th string in the array to the i^th-from-last string in the array - "var strs = @[:][is_str(@)];\r\n" + - "var strs_cpy = str(strs);\r\n" + - // need to iterate through (the i^th string, a *copy* of the i^th string, and a *copy* of the i^th-from-last string), - // because if we don't we will mutate some strings multiple times due to the in-place nature of mutation - "for s = zip(strs, strs_cpy, strs_cpy[::-1]);\r\n" + - " s[0] = s[1] + s[2];\r\n" + - "end for" - }), - ("compare_text", new object[]{"[\r\n \"0.25 is champion!0.75 is champion!\",\r\n \"0.75 is champion!0.25 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!1.25 is champion!\"\r\n][\r\n \"boo is champion!c\",\r\n \"aa\",\r\n \"cboo is champion!\"\r\n]"}), - // TEST REGEX SEARCH FORM (REGEX MODE) - ("file_open", new object[]{2, "txt"}), - ("overwrite", new object[]{"\u200a\u202f\n foo: 1\nBA\u042f: \u00a0-3\nbaz: +85"}), - ("regex_search", new object[]{ - "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(INT)\",\"ignoreCase\":true,\"fullMatch\":false,\"numCols\":[1]}" - }), - ("treenode_click", new object[]{new string[] {"0 : [2]"} }), - ("compare_selections", new object[]{new string[] {"7,7"} }), - ("treenode_click", new object[]{new string[] {"1 : [2]", "1 : -3"} }), - ("compare_selections", new object[]{new string[] {"23,23"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"23,25"} }), - ("treenode_click", new object[]{new string[] {"2 : [2]", "1 : 85"} }), - ("compare_selections", new object[]{new string[] {"31,31"} }), - ("regex_search", new object[]{ - "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(NUMBER)\",\"ignoreCase\":false,\"fullMatch\":true}" - }), - ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"baz: +85\""} }), - ("treenode_click", new object[]{new string[] {"0 : [3]", "1 : \"foo\""} }), - ("treenode_click", new object[]{new string[] {"0 : [3]", "2 : \"1\""} }), - ("tree_query", new object[]{ - "@ = s_sub(@,\r\n" + - " g`(?i)^\\x20*([a-zЯ]+):\\s+(INT)`,\r\n" + - " ifelse(float(@[2]) < 0,\r\n" + - " str(loop()) + @[0],\r\n" + - " str(loop()) + `. ` + @[1] + ` (` + str(int(@[2])) + `)`\r\n" + - " )\r\n" + - ")"}), - ("compare_text", new object[]{"\u200a\u202f\n1. foo (1)\n2BA\u042f: \u00a0-3\n3. baz (85)"}), - // TEST REGEX SEARCH FORM (CSV MODE) - ("overwrite", new object[]{ - "ab\tc\t😀\tf\r\n" + - "'fo\nö'\t7\tbar\t-8.5\r\n" + - "baz\t19\t''\t-4e3\r\n" + - "'zorq'\t\tkywq\t'.75'" - }), - ("regex_search", new object[] - { - "{\"csv\":true,\"delim\":\"\\\\t\",\"quote\":\"'\",\"newline\":\"\\r\\n\",\"header\":\"d\",\"nColumns\":4,\"numCols\":[-1,1]}" - }), - ("treenode_click", new object[]{new string[]{"0 : {4}", "ab : \"fo\\nö\"" } }), - ("treenode_click", new object[]{new string[]{"1 : {4}", "c : 19"} }), - ("treenode_click", new object[]{new string[]{"1 : {4}", "😀 : \"\"" } }), - ("treenode_click", new object[]{new string[]{"2 : {4}", "f : \".75\""} }), - ("compare_selections", new object[]{new string[] {"62,62"} }), - ("regex_search", new object[] + List<(string command, object[] args)> testcases = new List<(string command, object[] args)> { - "{\"csv\": true, \"delim\": \"\\\\t\", \"quote\": \"'\", \"newline\": \"\\r\\n\", \"header\": 1, \"nColumns\": 4, \"numCols\": [1]}" - }), - ("treenode_click", new object[]{new string[] {"0 : [4]", "3 : \"f\""} }), - ("treenode_click", new object[]{new string[] {"1 : [4]", "1 : 7"} }), - ("treenode_click", new object[]{new string[] {"2 : [4]", "3 : \"-4e3\""} }), - ("treenode_click", new object[]{new string[] {"3 : [4]", "0 : \"zorq\""} }), - // TEST SELECT ALL CHILDREN IN CSV FILE - ("tree_query", new object[]{"s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h)[:][0]"}), - ("treenode_click", new object[]{new string[]{ } }), // click root - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"0,2", "13,20", "33,36", "49,55"} }), - ("treenode_click", new object[]{new string[]{ "1 : \"fo\\nö\"" } }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"13,20"} }), - // MUTATE CSV FILE WITH QUERY - ("tree_query", new object[]{ - "var c = s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h, 1);\r\n" + - "c[:][1][is_num(@)] = @/4;\r\n" + - "@ = to_csv(c,`,`, `\\n`, `\"`)"}), - ("compare_text", new object[]{"ab,c,😀,f\n\"fo\nö\",1.75,bar,-8.5\nbaz,4.75,,-4e3\nzorq,,kywq,.75\n"}), - // TEST REGEX SEARCH FORM (SINGLE-CAPTURE-GROUP CSV AND REGEX MODES) - ("overwrite", new object[]{" 草1\nェ2\n◐3\n'4"}), // last row has CSV quote char to make sure CSV delimiter is forgotten now that a non-csv search was made - ("regex_search", new object[]{ - "{\"csv\":false,\"regex\":\"^\\\\S\\\\d\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" - }), - ("treenode_click", new object[]{new string[]{ } }), // click root - ("select_treenode_json_children", new object[]{}), - ("compare_selections", new object[]{new string[] {"6,10", "11,15", "16,18"} }), - ("treenode_click", new object[]{new string[] { "1 : \"◐3\"" } }), - ("compare_selections", new object[]{new string[] {"11,11"} }), - ("select_treenode_json", new object[]{}), - ("compare_selections", new object[]{new string[] {"11,15"} }), - ("regex_search", new object[]{ - "{\"csv\":false,\"regex\":\"^\\\\S(\\\\d)\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" // this time capture just the number after the nonspace - }), - ("treenode_click", new object[]{new string[] { "1 : \"3\"" } }), - ("compare_selections", new object[]{new string[] {"14,14"} }), - ("regex_search", new object[] - { - "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": \"\\n\", \"header\": 0, \"nColumns\": 1}" - }), - ("treenode_click", new object[]{new string[] { "0 : \"ェ2\"" } }), - ("compare_selections", new object[]{new string[] {"6,6"} }), - ("regex_search", new object[] - { - "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": 1, \"header\": 2, \"nColumns\": 1}" - }), - ("treenode_click", new object[]{new string[] { "0 : {1}", " 草1 : \"ェ2\"" } }), - ("compare_selections", new object[]{new string[] {"6,6"} }), - }; + ("overwrite", new object[]{"[1, 2, false]\r\n{\"a\": 3, \"b\": 1.5}\r\n[{\"c\": [null]}, -7]"}), // first line: 0 to 13, second line: 15 to 33, third line: 35 to 54 + ("select", new object[]{new string[]{ "0,13", "15,33", "35,54" } }), + // when pretty-printed in PPrint style looks like this: + //[ + // 1, + // 2, + // false + //] + //{ + // "a": 3, + // "b": 1.5 + //} + //[ + // {"c": [null]}, + // -7 + //] + // first JSON: 0 to 31, second JSON: 33 to 64, third JSON: 66 to 98 + ("pretty_print", new object[]{ }), + ("compare_text", new object[]{ "[\r\n 1,\r\n 2,\r\n false\r\n]\r\n{\r\n \"a\": 3,\r\n \"b\": 1.5\r\n}\r\n[\r\n {\"c\": [null]},\r\n -7\r\n]"}), + ("compare_selections", new object[]{new string[]{"0,31", "33,64", "66,98"} }), + // when compressed looks like this: + //[1,2,false] + //{"a":3,"b":1.5} + //[{"c":[null]},-7] + // first JSON: 0 to 11, second JSON: 13 to 28, third JSON: 30 to 47 + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,false]\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), + ("compare_selections", new object[]{new string[] {"0,11", "13,28", "30,47"} }), + // TEST THAT TREENODE CLICKS GO TO RIGHT LOCATION + ("tree_open", new object[]{}), + ("treenode_click", new object[]{ new string[] { "0,11 : [3]", "2 : false"} }), + ("compare_selections", new object[]{new string[] {"5,5"} }), // in front of @.`0,11`[2] + ("treenode_click", new object[]{ new string[] {"30,47 : [2]", "0 : {1}", "c : [1]", "0 : null"} }), + ("compare_selections", new object[]{new string[] {"37,37"} }), // in front of @.`30,47`[0].c[0] + ("insert_text", new object[]{11, "\r\n"}), + ("compare_text", new object[]{"[1,2,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), + // TEST QUERY ONLY SOME SELECTIONS + ("tree_query", new object[]{"@..g`[bc]`"}), + ("treenode_click", new object[]{new string[]{"15,30 : [1]", "0 : 1.5"} }), + ("compare_selections", new object[]{new string[]{"26,26" } }), + ("treenode_click", new object[]{new string[] {"32,49 : [1]", "0 : [1]", "0 : null"} }), + ("compare_selections", new object[]{new string[] {"39,39"} }), + // TEST DELETIONS/INSERTIONS THAT START AND END WITHIN A SINGLE SELECTION + ("delete_text", new object[]{2, 2}), + ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5}\r\n[{\"c\":[null]},-7]"}), + ("tree_query", new object[]{"@..*[is_num(@)][@ >= 2]"}), + ("treenode_click", new object[]{new string[] {"0,9 : []"} }), + ("treenode_click", new object[]{new string[] {"13,28 : [1]", "0 : 3"} }), + ("compare_selections", new object[]{new string[] {"18,18"} }), + ("insert_text", new object[]{27, ", \"c\": 0"}), // add new key to second JSON + ("compare_text", new object[]{"[1,false]\r\n\r\n{\"a\":3,\"b\":1.5, \"c\": 0}\r\n[{\"c\":[null]},-7]"}), + ("tree_query", new object[]{"@..c"}), + ("treenode_click", new object[]{new string[] {"13,36 : [1]", "0 : 0"} }), + ("treenode_click", new object[]{new string[] {"38,55 : [1]"} }), + // TEST SELECT_EVERY_VALID + ("overwrite", new object[]{ "Errör 1 [foo]: [1,2,NaN]\r\nWarning 2: {\"ä\":3}\r\nInfo 3 {bar}: [[6,{\"b\":false}],-7]\r\nError 4: \"baz\" \"quz\"\r\nError 5: \"string with no close quote\r\nError 6: not {\"even\" [json" }), + ("select", new object[]{new string[]{"0,0"} }), + ("select_every_valid", new object[]{}), + ("compare_selections", new object[]{new string[] { "16,25", "38,46", "62,82", "93,98", "99,104" } }), + ("compare_path_to_position", new object[]{22, "[`16,25`][2]"}), + ("compare_path_to_position", new object[]{101, "[`99,104`]"}), + ("compare_path_to_position", new object[]{74, "[`62,82`][0][1].b"}), + // TEST SELECT_EVERY_VALID ON A SUBSET OF THE FILE + ("select", new object[]{new string[] {"1,46", "93,100", "161,167"} }), + ("select_every_valid", new object[]{}), + ("compare_selections", new object[]{new string[] {"16,25", "38,46", "93,98", "161,167"} }), + // TEST SORT FORM + ("overwrite", new object[]{"[1,3,2]\r\n[6,5,44]\r\n[\"a\",\"c\",\"boo\"]"}), + ("select", new object[]{new string[]{"0,0"} }), + ("select_every_valid", new object[]{}), + ("compare_selections", new object[]{ new string[] { "0,7", "9,17", "19,34" } }), + ("sort_form_open", new object[]{}), + ("sort_form_run", new object[]{".g`^(?!0,)`", true, false, 1, ""}), // sort selections not starting at 0, as strings, ascending + ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"a\",\r\n \"boo\",\r\n \"c\"\r\n]"}), + ("sort_form_run", new object[]{"", true, true, 3, "s_len(str(@))"}), // sort all arrays biggest to smallest using the length of a node's string representation as key + ("compare_text", new object[]{"[\r\n 1,\r\n 3,\r\n 2\r\n]\r\n[\r\n 44,\r\n 5,\r\n 6\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), + // TEST SELECTING NON-JSON RANGE DOES NOT FORGET SELECTIONS + ("select", new object[]{new string[] {"8,15"} }), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[\"boo\",\"a\",\"c\"]"}), + // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC + ("delete_text", new object[]{20, 5}), // select treenode json and json children with array + ("insert_text", new object[]{20, "[1, NaN,\"b\"]"}), + ("compare_text", new object[]{"[1,3,2]\r\n[44,5,6]\r\n[[1, NaN,\"b\"],\"a\",\"c\"]"}), + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"19,41 : [3]", "0 : [3]"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"20,32"} }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"21,22", "24,27", "28,31"} }), + ("delete_text", new object[]{20, 12}), // now select treenode json and json children with object + ("insert_text", new object[]{20, "{\"a\": [null], \"b\": {}}"}), + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"20,42"} }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"26,32", "39,41"} }), + ("treenode_click", new object[]{new string[] {"19,51 : [3]", "0 : {2}", "a : [1]", "0 : null"} }), // try selecting treenode json with scalar + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"27,31"} }), + ("delete_text", new object[]{20, 22}), // revert to how it was before select-treenode-json tests + ("insert_text", new object[]{20, "\"boo\""}), + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"9,17 : [3]"} }), // try selecting json and json children on a different array + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"10,12", "13,14", "15,16"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"9,17"} }), + // TEST FILE WITH ONLY ONE JSON DOCUMENT + ("file_open", new object[]{1}), + ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]//a"}), + // TEST PRETTY-PRINT WITH COMMENTS AND TABS + ("pretty_print", new object[]{true, true}), + ("compare_text", new object[]{"[\r\n\t[\r\n\t\t\"Я\",\r\n\t\t1,\r\n\t\t\"a\"\r\n\t],\r\n\t// foo\r\n\t[\r\n\t\t\"◐\",\r\n\t\t2,\r\n\t\t\"b\"\r\n\t],\r\n\t// bar\r\n\t[\r\n\t\t\"ồ\",\r\n\t\t3,\r\n\t\t\"c\"\r\n\t],\r\n\t// baz\r\n\t[\r\n\t\t\"ェ\",\r\n\t\t4,\r\n\t\t\"d\"\r\n\t],\r\n\t[\r\n\t\t\"草\",\r\n\t\t5,\r\n\t\t\"e\"\r\n\t],\r\n\t[\r\n\t\t\"😀\",\r\n\t\t6,\r\n\t\t\"f\"\r\n\t]\r\n]\r\n//a\r\n"}), + // TEST PRETTY-PRINT WITH COMMENTS ONLY + ("pretty_print", new object[]{false, true}), + ("compare_text", new object[]{"[\r\n [\r\n \"Я\",\r\n 1,\r\n \"a\"\r\n ],\r\n // foo\r\n [\r\n \"◐\",\r\n 2,\r\n \"b\"\r\n ],\r\n // bar\r\n [\r\n \"ồ\",\r\n 3,\r\n \"c\"\r\n ],\r\n // baz\r\n [\r\n \"ェ\",\r\n 4,\r\n \"d\"\r\n ],\r\n [\r\n \"草\",\r\n 5,\r\n \"e\"\r\n ],\r\n [\r\n \"😀\",\r\n 6,\r\n \"f\"\r\n ]\r\n]\r\n//a\r\n" + }), + // TEST PRETTY-PRINT WITH TABS ONLY + ("pretty_print", new object[]{true}), + ("compare_text", new object[]{"[\r\n\t[\"Я\", 1, \"a\"],\r\n\t[\"◐\", 2, \"b\"],\r\n\t[\"ồ\", 3, \"c\"],\r\n\t[\"ェ\", 4, \"d\"],\r\n\t[\"草\", 5, \"e\"],\r\n\t[\"😀\", 6, \"f\"]\r\n]"}), + // TEST TREE ON WHOLE DOCUMENT + ("overwrite", new object[]{"[\r\n [\"Я\", 1, \"a\"], // foo\r\n [\"◐\", 2, \"b\"], // bar\r\n [\"ồ\", 3, \"c\"], // baz\r\n [\"ェ\", 4, \"d\"],\r\n [\"草\", 5, \"e\"],\r\n [\"😀\", 6, \"f\"]\r\n]"}), + ("tree_open", new object[]{}), + ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"◐\"" } }), + ("compare_selections", new object[]{new string[] {"36,36"} }), + ("compare_path_to_position", new object[]{36, "[1][0]"}), + ("treenode_click", new object[]{ new string[] { "5 : [3]", "1 : 6" } }), + ("compare_selections", new object[]{new string[] {"146,146"} }), + ("compare_path_to_position", new object[]{147, "[5][1]"}), + ("tree_query", new object[]{"@[:][1] = @ + 3" }), // mutate document + ("compare_text", new object[]{"[\r\n [\"Я\", 4, \"a\"],\r\n [\"◐\", 5, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n [\"草\", 8, \"e\"],\r\n [\"😀\", 9, \"f\"]\r\n]"}), + ("compare_path_to_position", new object[]{126, "[5][1]"}), + // TEST SELECT NON-JSON RANGE AND MAKE SURE WHOLE DOCUMENT STILL PARSED (WHEN NOT IN SELECTION MODE) + ("select", new object[]{new string[] {"1,7"} }), + ("compress", new object[]{}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],[\"草\",8,\"e\"],[\"😀\",9,\"f\"]]"}), + // TEST SELECTING JSON (AND JSON CHILDREN) FROM TREEVIEW IN SELECTION-BASED DOC + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"2 : [3]"} }), // try selecting json and json children with array + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"29,34", "35,36", "37,40"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"28,41"} }), + ("treenode_click", new object[]{new string[] {"3 : [3]", "0 : \"ェ\"" } }), // try selecting treenode json with scalar + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"43,48"} }), + ("overwrite", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\"😀\",9,\"f\"]]"}), // try selecting treenode json and json children with object + ("tree_query", new object[]{"@"}), + ("treenode_click", new object[]{new string[] {"4 : {3}"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"56,92"} }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"63,66", "71,74", "85,91"} }), + ("select", new object[]{new string[]{"0,0"} }), + ("tree_query", new object[]{"@![4][@[1] >= 7]"}), // try selecting treenode json children when parent is RemesPath query node + ("treenode_click", new object[]{new string[]{ } }), + ("select_treenode_json_children", new object[]{}), + ("pretty_print", new object[]{}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\r\n \"ェ\",\r\n 7,\r\n \"d\"\r\n],{\"草\":[8],\"e\":-.5, gor:/**/ '😀'},[\r\n \"😀\",\r\n 9,\r\n \"f\"\r\n]]"}), + // TEST NON-MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS + ("select_whole_doc", new object[]{}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), + ("tree_query", new object[]{"var a = @[0];\r\nvar b = @[1];\r\nvar c = @[-1];\r\nvar d = a + b * s_len(str(c));"}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), + ("treenode_click", new object[]{new string[] { "0 : \"Я◐◐\"" } }), + ("treenode_click", new object[]{new string[] {"1 : 9"} }), + ("treenode_click", new object[]{new string[] { "2 : \"ab\"" } }), + ("tree_query", new object[]{"var mod_3s = (@[:][type(@) != object])[:]->append(@, @[1] % 3);\r\n" + + "var gb_m3 = group_by(mod_3s, -1);\r\n" + + "gb_m3.`1`"}), + ("compare_text", new object[]{"[[\"Я\",4,\"a\"],[\"◐\",5,\"b\"],[\"ồ\",6,\"c\"],[\"ェ\",7,\"d\"],{\"e\":-0.5,\"gor\":\"😀\",\"草\":[8]},[\"😀\",9,\"f\"]]"}), + ("treenode_click", new object[]{new string[] { "0 : [4]", "2 : \"a\"" } }), + ("treenode_click", new object[]{new string[] { "1 : [4]", "1 : 7" } }), + // TEST MUTATING MULTI-STATEMENT QUERIES ON A DOCUMENT THAT DOES NOT USE SELECTIONS + ("tree_query", new object[]{"var a = @[0];\r\n" + + "var b = @[1];\r\n" + + "var c = @[-1];\r\n" + + "var d = a + b * s_len(str(c));\r\n" + + "a[0] = @ + d[0];\r\nb[1] = @ + c[1];\r\nc[2] = @ + a[0]"}), + ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 14, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}), + ("treenode_click", new object[]{new string[]{"0 : [3]", "0 : \"ЯЯ◐◐\"" } }), + ("compare_selections", new object[]{new string[]{ "8,8" } }), + ("treenode_click", new object[]{new string[] {"1 : [3]", "1 : 14" } }), + ("compare_selections", new object[]{new string[]{ "44,44" } }), + ("treenode_click", new object[]{new string[]{"5 : [3]", "2 : \"fЯЯ◐◐\"" } }), + ("compare_selections", new object[]{new string[]{ "160,160" } }), + ("tree_query", new object[]{"var bumba = @[1][1]; bumba = @ / 7; bumba"}), + ("compare_text", new object[]{"[\r\n [\"ЯЯ◐◐\", 4, \"a\"],\r\n [\"◐\", 2.0, \"b\"],\r\n [\"ồ\", 6, \"c\"],\r\n [\"ェ\", 7, \"d\"],\r\n {\"e\": -0.5, \"gor\": \"😀\", \"草\": [8]},\r\n [\"😀\", 9, \"fЯЯ◐◐\"]\r\n]"}), + ("treenode_click", new object[]{ new string[] { } }), + ("compare_selections", new object[]{new string[] {"44,44"} }), + ("tree_compare_query_result", new object[]{"2.0"}), + // TEST THAT JAVASCRIPT AND PYTHON COMMENTS AT EOF DON'T CAUSE PROBLEMS + ("overwrite", new object[]{"\r\n[1,2,\r\n3]#"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,3]"}), + ("overwrite", new object[]{"[1,2,3]#"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,3]"}), + ("overwrite", new object[]{"[1\r\n ,2,\r\n3]//"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,3]"}), + ("overwrite", new object[]{"[1,2,\r\n3]#bar"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,3]"}), + ("overwrite", new object[]{"[1,2,-9e15]\r\n//foo"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[1,2,-9E+15]"}), + // TEST PARSE JSON LINES + ("overwrite", new object[]{"[1,2,3]\r\n{\"a\": 1, \"b\": [-3,-4]}\r\n-7\r\nfalse"}), + ("tree_open", new object[]{}), // to close the tree so it can be reopened + ("tree_open", new object[]{DocumentType.JSONL}), + ("tree_query", new object[]{"@..* [abs(+@) < 3] = @ / 4"}), // divide values in open range (-3, 3) by 4; this should keep the file in JSON Lines format + ("compare_text", new object[]{"[0.25,0.5,3]\n{\"a\":0.25,\"b\":[-3,-4]}\n-7\n0.0"}), + ("tree_query", new object[]{"@"}), // re-parse document + ("treenode_click", new object[]{new string[] {"3 : 0.0"} }), + ("compare_selections", new object[]{new string[] {"39,39"} }), + ("compare_path_to_position", new object[]{32, "[1].b[1]"}), + // TEST PARSE INI FILE + ("overwrite", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\n bar=2\r\n [дaz]\r\n baz=3\r\n ;b\r\n baz2 = 7 \r\n[quz]\r\nquz=4\r\n;c"}), + ("set_document_type", new object[]{"INI"}), + ("tree_query", new object[]{"@..g`z`"}), + ("treenode_click", new object[]{new string[] {"0 : {2}", "baz2 : \"7 \""} }), + ("compare_selections", new object[]{new string[]{"63,63" } }), + ("compare_path_to_position", new object[]{81, ".quz.quz"}), + ("tree_query", new object[]{"@.bar.bar = @ * int(@)"}), // edit one value + ("compare_text", new object[]{"[foồ]\r\n;a\r\nfoo=1\r\n[bar]\r\nbar=22\r\n[дaz]\r\nbaz=3\r\n;b\r\nbaz2=7 \r\n[quz]\r\nquz=4\r\n;c\r\n"}), + // TEST RUNNING SAME QUERY MULTIPLE TIMES ON SAME INPUT DOES NOT HAVE DIFFERENT RESULTS + // test when mutating a compile-time constant array + ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}), + ("tree_query", new object[]{"var onetwo = j`[1,1]`; onetwo[1] = @ + 1; onetwo"}), + ("treenode_click", new object[]{new string[] {"1 : 2"} }), + // test when mutating a compile-time constant loop variable + ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}), + ("tree_query", new object[]{"for onetwo = j`[1,1]`; onetwo = @ + 1; end for;"}), + ("treenode_click", new object[]{new string[] {"0 : 2"} }), + ("treenode_click", new object[]{new string[] {"1 : 2"} }), + // test when mutating projections (both object and array) + ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}), + ("tree_query", new object[]{"var onetwo = @{1, 1}; var mappy = @{a: 1}; onetwo[1] = @ + 1; mappy.a = @ + 1; @{onetwo, mappy}"}), + ("treenode_click", new object[]{new string[] {"0 : [2]", "1 : 2"} }), + ("treenode_click", new object[]{new string[] {"1 : {1}", "a : 2"} }), + // test when mutating map projection + ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}), + ("tree_query", new object[]{"var x = @->j`[-1]`; x[0] = @ + 1; x"}), + ("treenode_click", new object[]{new string[] {"0 : 0"} }), + // TEST ASSIGNMENT OPERATIONS ON MULTIPLE SELECTIONS (back to file with three arrays that were sorted by the sort form) + ("file_open", new object[]{0}), + ("tree_query", new object[]{"@[:][is_num(@)] = @ / 4"}), + ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n]\r\n[\r\n 11.0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), + ("treenode_click", new object[]{new string[] {"37,72 : [3]", "1 : 1.25"} }), + ("compare_selections", new object[]{ new string[] { "55,55" } }), + // TEST DELETION THAT STARTS BEFORE A SELECTION AND ENDS INSIDE IT + ("insert_text", new object[]{47, "["}), + ("delete_text", new object[]{ 35, 12 }), + ("compare_text", new object[]{"[\r\n 0.25,\r\n 0.75,\r\n 0.5\r\n][0,\r\n 1.25,\r\n 1.5\r\n]\r\n[\r\n \"boo\",\r\n \"a\",\r\n \"c\"\r\n]"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[0.25,0.75,0.5][0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), + // TEST INSERTION THAT BEGINS ON THE FIRST CHAR OF A SELECTION + ("insert_text", new object[]{15, "blah"}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25,1.5]\r\n[\"boo\",\"a\",\"c\"]"}), + // TEST DELETION THAT STARTS INSIDE A SELECTION AND ENDS AFTER IT + ("insert_text", new object[]{26, "]"}), + ("delete_text", new object[]{27, 7}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), + ("compress", new object[]{}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), + // TEST QUERY THAT PRODUCES OBJECT WITH NON-"START,END" KEYS ON A FILE WITH SELECTIONS + ("tree_query", new object[]{"j`{\"a\": \"foo\", \"b\\n\": [1, 2]}`"}), + ("treenode_click", new object[]{new string[] {"a : \"foo\""} }), + ("treenode_click", new object[]{new string[] {"b\\n : [2]", "1 : 2"} }), + // TEST MULTI-STATEMENT QUERY THAT DOESN'T MUTATE ON A FILE WITH SELECTIONS + ("tree_query", new object[]{"var s = str(@);\r\n" + + "var sl = s_len(s);\r\n" + + "(s + ` `) * sl"}), + ("treenode_click", new object[]{new string[] {"27,42 : [3]", "0 : \"boo boo boo \""} }), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), + ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" + + "var s_lens = s_len(this_s[:][1]);\r\n" + + "var min_slen = min(s_lens);\r\n" + + "var shortest_strs = this_s[s_lens == min_slen][0]"}), + ("compare_text", new object[]{"[0.25,0.75,0.5]blah[0,1.25][\"boo\",\"a\",\"c\"]"}), + ("treenode_click", new object[]{ new string[] { "0,15 : [1]", "0 : 0.5"} }), + ("compare_selections", new object[]{new string[]{"11,11" } }), + ("treenode_click", new object[]{ new string[] { "27,42 : [2]", "1 : \"c\""} }), + ("compare_selections", new object[]{new string[]{"38,38" } }), + // TEST MULTI-STATEMENT QUERY THAT *DOES* MUTATE ON A FILE WITH SELECTIONS + ("tree_query", new object[]{"var this_s = @[:]{@, str(@)};\r\n" + + "var s_lens = s_len(this_s[:][1]);\r\n" + + "var max_slen = max(s_lens);\r\n" + + "var longest_strs = this_s[s_lens == max_slen][0];\r\n" + + "longest_strs = str(@) + ` is champion!`;\r\n" + + "longest_strs"}), + ("compare_text", new object[]{"[\r\n \"0.25 is champion!\",\r\n \"0.75 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!\"\r\n][\r\n \"boo is champion!\",\r\n \"a\",\r\n \"c\"\r\n]"}), + ("treenode_click", new object[]{ new string[] { "69,106 : [1]", "0 : \"1.25 is champion!\""} }), + ("treenode_click", new object[]{ new string[] { "106,154 : [1]", "0 : \"boo is champion!\""} }), + ("tree_compare_query_result", new object[]{"{\"0,65\": [\"0.25 is champion!\", \"0.75 is champion!\"], \"106,154\": [\"boo is champion!\"], \"69,106\": [\"1.25 is champion!\"]}"}), + // TEST PRINTING SELECTION-BASED FILE WITH TABS + ("pretty_print", new object[]{true}), + ("compare_text", new object[]{"[\r\n\t\"0.25 is champion!\",\r\n\t\"0.75 is champion!\",\r\n\t0.5\r\n]blah[\r\n\t0,\r\n\t\"1.25 is champion!\"\r\n][\r\n\t\"boo is champion!\",\r\n\t\"a\",\r\n\t\"c\"\r\n]"}), + // TEST QUERIES WITH LOOP VARIABLES ON SELECTION-BASED FILE + ("tree_query", new object[]{// find all the strings in the array, then add the i^th string in the array to the i^th-from-last string in the array + "var strs = @[:][is_str(@)];\r\n" + + "var strs_cpy = str(strs);\r\n" + + // need to iterate through (the i^th string, a *copy* of the i^th string, and a *copy* of the i^th-from-last string), + // because if we don't we will mutate some strings multiple times due to the in-place nature of mutation + "for s = zip(strs, strs_cpy, strs_cpy[::-1]);\r\n" + + " s[0] = s[1] + s[2];\r\n" + + "end for" + }), + ("compare_text", new object[]{"[\r\n \"0.25 is champion!0.75 is champion!\",\r\n \"0.75 is champion!0.25 is champion!\",\r\n 0.5\r\n]blah[\r\n 0,\r\n \"1.25 is champion!1.25 is champion!\"\r\n][\r\n \"boo is champion!c\",\r\n \"aa\",\r\n \"cboo is champion!\"\r\n]"}), + // TEST REGEX SEARCH FORM (REGEX MODE) + ("file_open", new object[]{2, "txt"}), + ("overwrite", new object[]{"\u200a\u202f\n foo: 1\nBA\u042f: \u00a0-3\nbaz: +85"}), + ("regex_search", new object[]{ + "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(INT)\",\"ignoreCase\":true,\"fullMatch\":false,\"numCols\":[1]}" + }), + ("treenode_click", new object[]{new string[] {"0 : [2]"} }), + ("compare_selections", new object[]{new string[] {"7,7"} }), + ("treenode_click", new object[]{new string[] {"1 : [2]", "1 : -3"} }), + ("compare_selections", new object[]{new string[] {"23,23"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"23,25"} }), + ("treenode_click", new object[]{new string[] {"2 : [2]", "1 : 85"} }), + ("compare_selections", new object[]{new string[] {"31,31"} }), + ("regex_search", new object[]{ + "{\"csv\":false,\"regex\":\"^\\\\x20*([a-zЯ]+):\\\\s+(NUMBER)\",\"ignoreCase\":false,\"fullMatch\":true}" + }), + ("treenode_click", new object[]{new string[] {"1 : [3]", "0 : \"baz: +85\""} }), + ("treenode_click", new object[]{new string[] {"0 : [3]", "1 : \"foo\""} }), + ("treenode_click", new object[]{new string[] {"0 : [3]", "2 : \"1\""} }), + ("tree_query", new object[]{ + "@ = s_sub(@,\r\n" + + " g`(?i)^\\x20*([a-zЯ]+):\\s+(INT)`,\r\n" + + " ifelse(float(@[2]) < 0,\r\n" + + " str(loop()) + @[0],\r\n" + + " str(loop()) + `. ` + @[1] + ` (` + str(int(@[2])) + `)`\r\n" + + " )\r\n" + + ")"}), + ("compare_text", new object[]{"\u200a\u202f\n1. foo (1)\n2BA\u042f: \u00a0-3\n3. baz (85)"}), + // TEST REGEX SEARCH FORM (CSV MODE) + ("overwrite", new object[]{ + "ab\tc\t😀\tf\r\n" + + "'fo\nö'\t7\tbar\t-8.5\r\n" + + "baz\t19\t''\t-4e3\r\n" + + "'zorq'\t\tkywq\t'.75'" + }), + ("regex_search", new object[] + { + "{\"csv\":true,\"delim\":\"\\\\t\",\"quote\":\"'\",\"newline\":\"\\r\\n\",\"header\":\"d\",\"nColumns\":4,\"numCols\":[-1,1]}" + }), + ("treenode_click", new object[]{new string[]{"0 : {4}", "ab : \"fo\\nö\"" } }), + ("treenode_click", new object[]{new string[]{"1 : {4}", "c : 19"} }), + ("treenode_click", new object[]{new string[]{"1 : {4}", "😀 : \"\"" } }), + ("treenode_click", new object[]{new string[]{"2 : {4}", "f : \".75\""} }), + ("compare_selections", new object[]{new string[] {"62,62"} }), + ("regex_search", new object[] + { + "{\"csv\": true, \"delim\": \"\\\\t\", \"quote\": \"'\", \"newline\": \"\\r\\n\", \"header\": 1, \"nColumns\": 4, \"numCols\": [1]}" + }), + ("treenode_click", new object[]{new string[] {"0 : [4]", "3 : \"f\""} }), + ("treenode_click", new object[]{new string[] {"1 : [4]", "1 : 7"} }), + ("treenode_click", new object[]{new string[] {"2 : [4]", "3 : \"-4e3\""} }), + ("treenode_click", new object[]{new string[] {"3 : [4]", "0 : \"zorq\""} }), + // TEST SELECT ALL CHILDREN IN CSV FILE + ("tree_query", new object[]{"s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h)[:][0]"}), + ("treenode_click", new object[]{new string[]{ } }), // click root + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"0,2", "13,20", "33,36", "49,55"} }), + ("treenode_click", new object[]{new string[]{ "1 : \"fo\\nö\"" } }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"13,20"} }), + // MUTATE CSV FILE WITH QUERY + ("tree_query", new object[]{ + "var c = s_csv(@, 4, `\\t`, `\\r\\n`, `'`, h, 1);\r\n" + + "c[:][1][is_num(@)] = @/4;\r\n" + + "@ = to_csv(c,`,`, `\\n`, `\"`)"}), + ("compare_text", new object[]{"ab,c,😀,f\n\"fo\nö\",1.75,bar,-8.5\nbaz,4.75,,-4e3\nzorq,,kywq,.75\n"}), + // TEST REGEX SEARCH FORM (SINGLE-CAPTURE-GROUP CSV AND REGEX MODES) + ("overwrite", new object[]{" 草1\nェ2\n◐3\n'4"}), // last row has CSV quote char to make sure CSV delimiter is forgotten now that a non-csv search was made + ("regex_search", new object[]{ + "{\"csv\":false,\"regex\":\"^\\\\S\\\\d\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" + }), + ("treenode_click", new object[]{new string[]{ } }), // click root + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"6,10", "11,15", "16,18"} }), + ("treenode_click", new object[]{new string[] { "1 : \"◐3\"" } }), + ("compare_selections", new object[]{new string[] {"11,11"} }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[] {"11,15"} }), + ("regex_search", new object[]{ + "{\"csv\":false,\"regex\":\"^\\\\S(\\\\d)\\\\r?$\",\"ignoreCase\":true,\"fullMatch\":false}" // this time capture just the number after the nonspace + }), + ("treenode_click", new object[]{new string[] { "1 : \"3\"" } }), + ("compare_selections", new object[]{new string[] {"14,14"} }), + ("regex_search", new object[] + { + "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": \"\\n\", \"header\": 0, \"nColumns\": 1}" + }), + ("treenode_click", new object[]{new string[] { "0 : \"ェ2\"" } }), + ("compare_selections", new object[]{new string[] {"6,6"} }), + ("regex_search", new object[] + { + "{\"csv\": true, \"delim\": \",\", \"quote\": \"'\", \"newline\": 1, \"header\": 2, \"nColumns\": 1}" + }), + ("treenode_click", new object[]{new string[] { "0 : {1}", " 草1 : \"ェ2\"" } }), + ("compare_selections", new object[]{new string[] {"6,6"} }), + // TEST REGEX SEARCH ON FILE WITH MULTIPLE SELECTIONS + ("overwrite", new object[]{"1 2\r[3]ö4\r\"\u00a0\r5 6\r'foo'"}), + ("tree_query", new object[]{"s_csv(@, 1, `,`, `\\r`, `\"`, h)"}), + ("treenode_click", new object[]{new string[] {} }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[] {"0,3", "4,10", "15,18", "19,24"} }), + ("tree_open", new object[]{}), + ("tree_open", new object[]{}), + ("set_document_type", new object[]{ "REGEX" }), // re-parse document in multi-selection mode + ("treenode_click", new object[]{new string[]{ "15,18 : \"5 6\"" } }), + ("compare_selections", new object[]{new string[] {"15,15"} }), + ("tree_query", new object[]{"s_fa(@, g`\\d+`,, 0)"}), + ("treenode_click", new object[]{ new string[] { "4,10 : [2]", "1 : 4" } }), + ("select_treenode_json", new object[]{}), + ("compare_selections", new object[]{new string[]{"9,10" } }), + ("treenode_click", new object[]{ new string[] { "15,18 : [2]" } }), + ("select_treenode_json_children", new object[]{}), + ("compare_selections", new object[]{new string[]{"15,16", "17,18" } }), + // TEST REGEX EDITING ON FILE WITH MULTIPLE SELECTIONS + ("select", new object[]{new string[] {"0,0"} }), + ("tree_query", new object[]{ "@ = s_sub(@, g`(INT)`, str(int(@[0])->ifelse(@ > 3, -@, @)))" }), + ("compare_text", new object[]{"1 2\r[3]ö-4\r\"\u00a0\r-5 -6\r'foo'" }), + ("compare_selections", new object[]{new string[] {"0,3", "4,11", "16,21", "22,27"} }), + // TEST DOCUMENT TYPE CHANGE BUTTON IN TREEVIEW + ("overwrite", new object[]{"[1, 2]\r\n{\"ö\": \"3 4\"}\r\n\"\"\r\n{\"5\": [6]}\r\n'foo'"}), + ("set_document_type", new object[]{"JSON"}), + ("treenode_click", new object[]{new string[] {"0 : 1"} }), + ("treenode_click", new object[]{new string[] {"1 : 2"} }), + ("set_document_type", new object[]{"JSONL"}), + ("treenode_click", new object[]{new string[] {"3 : {1}", "5 : [1]", "0 : 6"} }), + ("compare_selections", new object[]{new string[] {"34,34"} }), + ("treenode_click", new object[]{new string[] {"4 : \"foo\""} }), + ("set_document_type", new object[]{"REGEX"}), + ("tree_query", new object[]{"s_csv(@, 1,`\\t`,`\\r\\n`,`'` ,h)"}), + ("treenode_click", new object[]{new string[] { "1 : \"{\\\"ö\\\": \\\"3 4\\\"}\"" } }), + ("treenode_click", new object[]{new string[] { "3 : \"{\\\"5\\\": [6]}\"" } }), + }; - public static bool Test() - { var messages = new List(); int failures = 0; string previouslyOpenFname = Npp.notepad.GetCurrentFilePath(); @@ -735,30 +786,26 @@ public static bool Test() // this message box doesn't block the main thread, but it introduces some asynchronous behavior // that was probably responsible for crashing the UI tests Main.hasWarnedSelectionsForgotten = true; - if (!hasRunTestsThisSession) + // add command to overwrite with a lot of arrays and select every valid json + try { - hasRunTestsThisSession = true; - // add command to overwrite with a lot of arrays and select every valid json - try - { - string oneArray2000xStr = (string)remesParser.Search("@ * 2000", new JNode("[1]\r\n")).value; - JArray oneArray2000x = (JArray)jsonParser.ParseJsonLines(oneArray2000xStr); - string[] oneArray2000xSelections = ((JArray)remesParser.Search("(range(2000) * 5)[:]{@, @ + 3}{s_join(`,`, str(@))}[0]", new JNode())).children - .Select(x => (string)x.value) - .ToArray(); - string[] oneArray2000xPPrintSelections = ((JArray)remesParser.Search("(range(2000) * 13)[:]{@, @ + 11}{s_join(`,`, str(@))}[0]", new JNode())).children - .Select(x => (string)x.value) - .ToArray(); - testcases.Add(("overwrite", new object[] { oneArray2000xStr })); - testcases.Add(("select_every_valid", new object[] { })); - testcases.Add(("compare_selections", new object[] { oneArray2000xSelections })); - testcases.Add(("pretty_print", new object[] { })); - testcases.Add(("compare_selections", new object[] { oneArray2000xPPrintSelections })); - } - catch (Exception ex) - { - messages.Add($"Failed to add testcases due to exception {ex}"); - } + string oneArray2000xStr = (string)remesParser.Search("@ * 2000", new JNode("[1]\r\n")).value; + JArray oneArray2000x = (JArray)jsonParser.ParseJsonLines(oneArray2000xStr); + string[] oneArray2000xSelections = ((JArray)remesParser.Search("(range(2000) * 5)[:]{@, @ + 3}{s_join(`,`, str(@))}[0]", new JNode())).children + .Select(x => (string)x.value) + .ToArray(); + string[] oneArray2000xPPrintSelections = ((JArray)remesParser.Search("(range(2000) * 13)[:]{@, @ + 11}{s_join(`,`, str(@))}[0]", new JNode())).children + .Select(x => (string)x.value) + .ToArray(); + testcases.Add(("overwrite", new object[] { oneArray2000xStr })); + testcases.Add(("select_every_valid", new object[] { })); + testcases.Add(("compare_selections", new object[] { oneArray2000xSelections })); + testcases.Add(("pretty_print", new object[] { })); + testcases.Add(("compare_selections", new object[] { oneArray2000xPPrintSelections })); + } + catch (Exception ex) + { + messages.Add($"Failed to add testcases due to exception {ex}"); } // run all the commands int lastFailureIndex = messages.Count; diff --git a/docs/README.md b/docs/README.md index f000b11..714b31c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -115,6 +115,26 @@ When you parse a document that contains syntax errors, you may be asked if you w In [v5.3.0](/CHANGELOG.md#530---2023-06-10), a form was added to display errors. Prior to that, errors were shown as text in a new buffer. +### Document type box *(added in v6.0)* ### + +*Beginning in version [v6.0](/CHANGELOG.md#600---unreleased-2023-mm-dd),* the tree view has a document type box just above the tree itself. + +This box has four options (auto): +* `JSON mode`: parse document (or each selection) as JSON +* `JSONL mode`: parse document (or each selection) as [JSON lines](#json-lines-documents) +* `INI mode`: parse document (or each selection) as an [INI file](#parsing-ini-files) +* `REGEX mode`: the document (or each selection) is converted to a JSON string containing its text, which can then be [searched and edited with regex and RemesPath](#regex-search-form). + +Observe the three images below to see how the selected box causes the same document to be interpreted in three different ways (`INI mode` not shown, because that's not a valid INI file). + +![Document type box - JSON mode example](/docs/document%20type%20box%20example%20-%20JSON%20mode.PNG) + +![Document type box - JSON Lines mode example](/docs/document%20type%20box%20example%20-%20JSONL%20mode.PNG) + +![Document type box - REGEX mode example](/docs/document%20type%20box%20example%20-%20REGEX%20mode.PNG) + +While this box is focused, you can scroll through these options, or navigate to a different option by typing the first key of its name. + ### Working with selections ### [Starting in version v5.5](/CHANGELOG.md#550---2023-08-13), you can work with one or more selections rather than treating the entire file as JSON. diff --git a/docs/RemesPath.md b/docs/RemesPath.md index 41eac20..37f7864 100644 --- a/docs/RemesPath.md +++ b/docs/RemesPath.md @@ -516,6 +516,19 @@ Returns an array of integers. * `range(3, 1, -1)` returns `[3, 2]`. * `range(0, 6, 3)` returns `[0, 3]`. +--- +`set(x: array) -> object` + +*Added in [v6.0](/CHANGELOG.md#600---unreleased-2023-mm-dd)* + +Returns an object mapping each unique string representation of an element in `x` to null. This may be preferable to `unique` because of the O(1) average-case lookup performance in an object. + +Example: ``set(j`["a", "b", "a", 1, 2.0, null, 1, null]`)`` returns `{"a": null, "b": null, "1": null, "2.0": null, "null": null}` + +Issues with this function that may make the `unique` function preferable: +* two different elements may have the same string representation for the purposes of this function (e.g., `null` and `"null"`, `2.0` and `"2.0"`) +* strings with characters that must be escaped (e.g., `\`, `"`) have those characters escaped before they become keys in the object returned by `set` + ---- `s_join(sep: string, x: array) -> string` @@ -524,11 +537,16 @@ Every element of `x` must be a string. Returns x string-joined with sep (i.e., returns a string that begins with `x[0]` and has `sep` between `x[i - 1]` and `x[i]` for `1 <= i <= len(x)`) ---- -`sort_by(x: array, k: string | int, descending: bool = false)` +`sort_by(x: array, k: string | int | function, descending: bool = false)` -`x` must be an array of arrays (in which case `k` must be an int) or an array of objects (in which case `k` must be a string). +`x` must be: +* an array of arrays (if `k` is an integer) +* an array of objects (if `k` is a string) +* any array (if `k` is a function) -Returns a new array of subarrays/subobjects `subitbl` such that `subitbl[k]` is sorted ascending. +Returns: +* a new array of subarrays/subobjects `subitbl` such that `subitbl[k]` is sorted (if `k` is an integer or string) +* a new array of children `child` such that `k(child)` is sorted (if `k` is a function) Analogous to SQL `ORDER BY`. @@ -538,8 +556,8 @@ Prior to [v5.5.0](/CHANGELOG.md#550---2023-08-13), Python-style negative indices __Examples:__ * With `[[1, 2], [2, 0], [3, -1]]` as input, `sort_by(@, 1)` returns `[[3,-1],[2,0],[1,2]]` because it sorts ascending by the second element. -* With `[{"a": 1, "b": 3}, {"a": 2, "b": 2}, {"a": 3, "b": 1}]` as input, `sort_by(@, a, true)` returns `[{"a":3,"b":1},{"a":2,"b":2},{"a":1,"b":3}]` because it sorts ascending by key `a`. -* With `["a", "bbb", "cc"]` as input, `sort_by(@, s_len(@))` returns `["a", "cc", "bbb"]`, because the children are ordered by string length ascending +* With `[{"a": 1, "b": 3}, {"a": 2, "b": 2}, {"a": 3, "b": 1}]` as input, `sort_by(@, a, true)` returns `[{"a":3,"b":1},{"a":2,"b":2},{"a":1,"b":3}]` because it sorts descending by key `a`. +* With `["a", "bbb", "cc"]` as input, `sort_by(@, s_len(@))` returns `["a", "cc", "bbb"]`, because the children are sorted ascending by string length. ---- `sorted(x: array, descending: bool = false)` diff --git a/docs/document type box example - JSON mode.PNG b/docs/document type box example - JSON mode.PNG new file mode 100644 index 0000000..ff5ab0f Binary files /dev/null and b/docs/document type box example - JSON mode.PNG differ diff --git a/docs/document type box example - JSONL mode.PNG b/docs/document type box example - JSONL mode.PNG new file mode 100644 index 0000000..a2bfe93 Binary files /dev/null and b/docs/document type box example - JSONL mode.PNG differ diff --git a/docs/document type box example - REGEX mode.PNG b/docs/document type box example - REGEX mode.PNG new file mode 100644 index 0000000..27b3ed3 Binary files /dev/null and b/docs/document type box example - REGEX mode.PNG differ diff --git a/most recent errors.txt b/most recent errors.txt index 7034631..b3ef6bb 100644 --- a/most recent errors.txt +++ b/most recent errors.txt @@ -1,4 +1,4 @@ -Test results for JsonTools v5.8.0.15 on Notepad++ 8.5.8 64bit +Test results for JsonTools v5.8.0.17 on Notepad++ 8.5.8 64bit NOTE: Ctrl-F (regular expressions *on*) for "Failed [1-9]\d*" to find all failed tests Tests failed: YAML dumper ========================= @@ -114,7 +114,7 @@ Testing RemesPath parser and compiler The queried JSON in the RemesParser tests is named foo:{"foo": [[0, 1, 2], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]], "bar": {"a": false, "b": ["a`g", "bah"]}, "baz": "z", "quz": {}, "jub": [], "guzo": [[[1]], [[2], [3]]], "7": [{"foo": 2}, 1], "_": {"0": 0}} Failed 0 tests. -Passed 453 tests. +Passed 454 tests. ========================= Testing RemesPath throws errors on bad inputs ========================= @@ -133,7 +133,7 @@ Testing RemesPath produces sane outputs on randomly generated queries Fuzz tests query {"a": [-4, -3., -2., -1, 0, 1, 2., 3., 4], "bc": NaN,"c`d": "df", "q": ["", "a", "jk", "ian", "", "32", "u", "aa", "moun"],"f": 1,"g": 1,"h": 1,"i": 1,"j": 1} -Ran 10000 fuzz tests +Ran 5000 fuzz tests Failed 0 fuzz tests ========================= Testing multi-statement queries in RemesPath @@ -195,40 +195,40 @@ Testing UI tests ========================= Failed 0 tests -Passed 298 tests +Passed 330 tests ========================= Testing JsonParser performance ========================= Preview of json: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... -To convert JSON string of size 89556 into JNode took 2.705 +/- 1.982 ms over 32 trials -Load times (ms): 2, 2, 1, 3, 1, 2, 5, 1, 1, 1, 3, 1, 1, 4, 1, 1, 11, 4, 1, 1, 4, 1, 1, 1, 2, 4, 1, 2, 2, 2, 2, 1 +To convert JSON string of size 89556 into JNode took 2.432 +/- 1.576 ms over 32 trials +Load times (ms): 1, 2, 9, 1, 2, 3, 1, 1, 2, 3, 1, 1, 4, 1, 1, 1, 2, 1, 1, 4, 1, 1, 1, 3, 1, 1, 4, 1, 1, 1, 3, 1 ========================= Performance tests for RemesPath (float arithmetic) ========================= -Compiling query "@[@[:].a * @[:].t < @[:].e]" into took 2.482 +/- 15.103 microseconds over 40 trials -To run pre-compiled query "@[@[:].a * @[:].t < @[:].e]" on JNode from JSON of size 89556 into took 0.029 +/- 0.013 ms over 40 trials -Query times (ms): 0.058, 0.058, 0.023, 0.023, 0.023, 0.023, 0.023, 0.025, 0.023, 0.023, 0.023, 0.023, 0.023, 0.024, 0.024, 0.023, 0.023, 0.023, 0.023, 0.024, 0.023, 0.023, 0.023, 0.022, 0.023, 0.039, 0.074, 0.04, 0.035, 0.025, 0.034, 0.075, 0.024, 0.024, 0.034, 0.023, 0.023, 0.028, 0.025, 0.024 +Compiling query "@[@[:].a * @[:].t < @[:].e]" into took 2.335 +/- 14.15 microseconds over 40 trials +To run pre-compiled query "@[@[:].a * @[:].t < @[:].e]" on JNode from JSON of size 89556 into took 0.115 +/- 0.557 ms over 40 trials +Query times (ms): 0.07, 0.036, 0.025, 0.023, 0.023, 0.023, 0.023, 0.029, 0.023, 0.024, 0.023, 0.023, 0.023, 0.028, 0.023, 0.023, 0.023, 0.024, 0.023, 0.026, 0.023, 0.024, 0.023, 0.023, 0.024, 0.026, 0.023, 0.023, 0.024, 0.023, 0.023, 0.025, 0.024, 0.023, 0.023, 0.023, 0.024, 0.025, 0.024, 3.594 Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... ========================= Performance tests for RemesPath (string operations) ========================= -Compiling query "@[@[:].z =~ `(?i)[a-z]{5}`]" into took 2.975 +/- 17.954 microseconds over 40 trials -To run pre-compiled query "@[@[:].z =~ `(?i)[a-z]{5}`]" on JNode from JSON of size 89556 into took 0.112 +/- 0.02 ms over 40 trials -Query times (ms): 0.191, 0.165, 0.112, 0.114, 0.112, 0.116, 0.104, 0.111, 0.102, 0.107, 0.128, 0.113, 0.109, 0.111, 0.112, 0.111, 0.111, 0.113, 0.149, 0.112, 0.095, 0.098, 0.106, 0.098, 0.103, 0.107, 0.119, 0.106, 0.105, 0.103, 0.108, 0.123, 0.115, 0.116, 0.104, 0.121, 0.106, 0.111, 0.072, 0.064 +Compiling query "@[@[:].z =~ `(?i)[a-z]{5}`]" into took 2.068 +/- 12.431 microseconds over 40 trials +To run pre-compiled query "@[@[:].z =~ `(?i)[a-z]{5}`]" on JNode from JSON of size 89556 into took 0.055 +/- 0.01 ms over 40 trials +Query times (ms): 0.111, 0.055, 0.054, 0.054, 0.082, 0.054, 0.053, 0.054, 0.054, 0.053, 0.051, 0.052, 0.052, 0.052, 0.051, 0.052, 0.052, 0.052, 0.052, 0.051, 0.052, 0.051, 0.052, 0.052, 0.052, 0.052, 0.052, 0.053, 0.052, 0.053, 0.052, 0.053, 0.052, 0.052, 0.052, 0.052, 0.052, 0.053, 0.052, 0.052 Preview of result: [{"A": "\n]o1VQ5t6g", "a": 4710024278, "b": 3268860721, "B": "g4Y7+ew^.v", "C": "NK nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" into took 5.272 +/- 31.406 microseconds over 40 trials +ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" into took 4.99 +/- 29.866 microseconds over 40 trials To run pre-compiled query "var qmask = @[:].q; var nmax_q = max(@[qmask].n); var nmax_notq = max(@[not qmask].n); -ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" on JNode from JSON of size 89556 into took 0.033 +/- 0.042 ms over 40 trials -Query times (ms): 0.088, 0.027, 0.028, 0.025, 0.018, 0.048, 0.018, 0.017, 0.017, 0.287, 0.026, 0.026, 0.024, 0.025, 0.025, 0.023, 0.024, 0.025, 0.025, 0.032, 0.026, 0.016, 0.016, 0.016, 0.021, 0.023, 0.024, 0.04, 0.025, 0.025, 0.026, 0.025, 0.024, 0.026, 0.024, 0.025, 0.025, 0.025, 0.025, 0.026 +ifelse(nmax_q > nmax_notq, `when q=true, nmax = ` + str(nmax_q), `when q=false, nmax= ` + str(nmax_notq))" on JNode from JSON of size 89556 into took 0.025 +/- 0.031 ms over 40 trials +Query times (ms): 0.076, 0.026, 0.028, 0.038, 0.024, 0.048, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.206, 0.031, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.015, 0.015, 0.016, 0.015, 0.016 Preview of result: "when q=false, nmax= 9830935647.0" ... ========================= @@ -267,11 +267,11 @@ Performance tests for RemesPath (references to compile-time constant variables) Compiling query "var X = X; var onetwo = j`[1, 2]`; -@[:]->at(@, X)->at(@, onetwo)" into took 3.157 +/- 19.094 microseconds over 40 trials +@[:]->at(@, X)->at(@, onetwo)" into took 2.65 +/- 15.941 microseconds over 40 trials To run pre-compiled query "var X = X; var onetwo = j`[1, 2]`; -@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.016 +/- 0.01 ms over 40 trials -Query times (ms): 0.054, 0.026, 0.016, 0.02, 0.013, 0.012, 0.013, 0.013, 0.015, 0.05, 0.028, 0.013, 0.012, 0.013, 0.039, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.013, 0.013, 0.012 +@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.014 +/- 0.005 ms over 40 trials +Query times (ms): 0.038, 0.013, 0.013, 0.012, 0.012, 0.012, 0.012, 0.012, 0.013, 0.03, 0.012, 0.012, 0.013, 0.012, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.012, 0.012, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.013, 0.012, 0.013, 0.013, 0.012, 0.013, 0.012, 0.013, 0.012, 0.012, 0.012, 0.012 Preview of result: [[1695727848, 0.287562638736685], [2126430375, 0.00767794129708177], [5310550656, 0.380769772645687], [2519183283, 0.153176220930558], [6610062385, 0.662996225870666], [987168256, 0.924410189999928], [6615003609, 0.917112691225947], [4465232046, 0.684311931851536], [8654414565, 0.631485392105992], [ ... ========================= @@ -280,29 +280,29 @@ Performance tests for RemesPath (references to variables that are not compile-ti Compiling query "var X = @->`X`; var onetwo = @{1, 2}; -@[:]->at(@, X)->at(@, onetwo)" into took 4.12 +/- 24.849 microseconds over 40 trials +@[:]->at(@, X)->at(@, onetwo)" into took 2.662 +/- 15.939 microseconds over 40 trials To run pre-compiled query "var X = @->`X`; var onetwo = @{1, 2}; -@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.031 +/- 0.011 ms over 40 trials -Query times (ms): 0.091, 0.029, 0.029, 0.028, 0.028, 0.04, 0.028, 0.058, 0.028, 0.028, 0.028, 0.028, 0.027, 0.028, 0.028, 0.027, 0.028, 0.028, 0.027, 0.028, 0.028, 0.028, 0.028, 0.028, 0.027, 0.029, 0.027, 0.028, 0.028, 0.028, 0.028, 0.034, 0.028, 0.027, 0.029, 0.028, 0.028, 0.028, 0.028, 0.027 +@[:]->at(@, X)->at(@, onetwo)" on JNode from JSON of size 89556 into took 0.017 +/- 0.005 ms over 40 trials +Query times (ms): 0.051, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.015, 0.016, 0.016, 0.016, 0.016, 0.016, 0.016, 0.017, 0.017, 0.016, 0.016 Preview of result: [[1695727848, 0.287562638736685], [2126430375, 0.00767794129708177], [5310550656, 0.380769772645687], [2519183283, 0.153176220930558], [6610062385, 0.662996225870666], [987168256, 0.924410189999928], [6615003609, 0.917112691225947], [4465232046, 0.684311931851536], [8654414565, 0.631485392105992], [ ... ========================= Performance tests for RemesPath (simple string mutations) ========================= -Compiling query "@[:].z = s_sub(@, g, B)" into took 2.535 +/- 15.319 microseconds over 40 trials -To run pre-compiled query "@[:].z = s_sub(@, g, B)" on JNode from JSON of size 89556 into took 0.025 +/- 0.007 ms over 40 trials -Query times (ms): 0.054, 0.025, 0.022, 0.022, 0.021, 0.024, 0.027, 0.026, 0.022, 0.031, 0.022, 0.02, 0.021, 0.021, 0.019, 0.024, 0.022, 0.023, 0.026, 0.023, 0.024, 0.023, 0.024, 0.03, 0.021, 0.024, 0.02, 0.02, 0.021, 0.02, 0.02, 0.035, 0.032, 0.035, 0.022, 0.028, 0.031, 0.032, 0.016, 0.015 +Compiling query "@[:].z = s_sub(@, g, B)" into took 1.79 +/- 10.842 microseconds over 40 trials +To run pre-compiled query "@[:].z = s_sub(@, g, B)" on JNode from JSON of size 89556 into took 0.014 +/- 0.007 ms over 40 trials +Query times (ms): 0.024, 0.011, 0.011, 0.01, 0.009, 0.01, 0.011, 0.01, 0.01, 0.01, 0.014, 0.013, 0.014, 0.013, 0.015, 0.012, 0.011, 0.012, 0.012, 0.01, 0.012, 0.011, 0.011, 0.011, 0.01, 0.01, 0.014, 0.013, 0.016, 0.01, 0.011, 0.011, 0.011, 0.011, 0.013, 0.044, 0.029, 0.025, 0.024, 0.015 Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... ========================= Performance tests for RemesPath (simple number mutations) ========================= -Compiling query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" into took 2.815 +/- 16.955 microseconds over 40 trials -To run pre-compiled query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" on JNode from JSON of size 89556 into took 0.09 +/- 0.36 ms over 40 trials -Query times (ms): 0.044, 0.029, 0.026, 0.024, 0.025, 0.026, 0.051, 0.034, 0.04, 0.027, 0.024, 0.027, 0.026, 0.026, 0.024, 0.023, 0.024, 0.024, 0.034, 0.032, 0.066, 0.044, 0.027, 0.026, 0.026, 0.024, 0.025, 0.025, 0.023, 0.024, 0.023, 0.024, 0.03, 0.088, 2.334, 0.047, 0.034, 0.038, 0.036, 0.035 +Compiling query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" into took 18.682 +/- 116.032 microseconds over 40 trials +To run pre-compiled query "@[:].x = ifelse(@ < 0.5, @ + 3, @ - 3)" on JNode from JSON of size 89556 into took 0.027 +/- 0.009 ms over 40 trials +Query times (ms): 0.036, 0.022, 0.021, 0.021, 0.02, 0.021, 0.021, 0.02, 0.021, 0.02, 0.02, 0.02, 0.029, 0.025, 0.021, 0.042, 0.03, 0.057, 0.047, 0.029, 0.022, 0.021, 0.022, 0.021, 0.028, 0.027, 0.026, 0.021, 0.021, 0.02, 0.021, 0.042, 0.042, 0.038, 0.042, 0.026, 0.025, 0.027, 0.028, 0.025 Preview of result: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... ========================= @@ -312,12 +312,12 @@ Performance tests for RemesPath (mutations with a for loop) Compiling query "var xhalf = @[:].x < 0.5; for lx = zip(@[:].l, xhalf); lx[0] = ifelse(lx[1], foo, bar); -end for;" into took 6.272 +/- 38.195 microseconds over 40 trials +end for;" into took 4.422 +/- 26.802 microseconds over 40 trials To run pre-compiled query "var xhalf = @[:].x < 0.5; for lx = zip(@[:].l, xhalf); lx[0] = ifelse(lx[1], foo, bar); -end for;" on JNode from JSON of size 89556 into took 0.064 +/- 0.023 ms over 40 trials -Query times (ms): 0.092, 0.101, 0.116, 0.053, 0.063, 0.059, 0.087, 0.063, 0.062, 0.063, 0.064, 0.058, 0.057, 0.034, 0.038, 0.085, 0.085, 0.067, 0.062, 0.049, 0.044, 0.043, 0.043, 0.043, 0.041, 0.055, 0.07, 0.049, 0.05, 0.051, 0.049, 0.091, 0.068, 0.062, 0.061, 0.102, 0.14, 0.052, 0.038, 0.051 +end for;" on JNode from JSON of size 89556 into took 0.053 +/- 0.023 ms over 40 trials +Query times (ms): 0.061, 0.038, 0.037, 0.037, 0.036, 0.036, 0.037, 0.035, 0.049, 0.09, 0.081, 0.055, 0.04, 0.039, 0.043, 0.044, 0.04, 0.041, 0.04, 0.04, 0.04, 0.042, 0.089, 0.066, 0.044, 0.043, 0.038, 0.044, 0.037, 0.034, 0.035, 0.141, 0.089, 0.06, 0.053, 0.056, 0.065, 0.071, 0.061, 0.099 Preview of result: [["bar", false], ["bar", false], ["foo", true], ["foo", true], ["foo", true], ["foo", true], ["foo", true], ["bar", false], ["bar", false], ["bar", false], ["foo", true], ["foo", true], ["bar", false], ["bar", false], ["foo", true], ["bar", false], ["bar", false], ["bar", false], ["foo", true], ["ba ... ========================= @@ -326,18 +326,18 @@ Testing performance of JSON compression and pretty-printing Preview of json: [{"A": "Ky'c^g#~)0", "a": 1850111954, "b": 9318359041, "B": "Oyi:/ xxe2", "C": "sKCSa_^7Gg", "c": 7974777124, "d": 2670309238, "D": "0d_K)HmX!.", "E": ".uM*Z{0EJ_", "e": 6958410336, "f": 8050244728, "F": "1%SG_A!xB\t", "g": 3799657125, "G": "il1^k\\\nat*", "H": {"a": 6079042826, "b": 7292804611, "c" ... -To compress JNode from JSON string of 89556 took 3.984 +/- 0.471 ms over 64 trials (minimal whitespace, sort_keys=TRUE) -To compress JNode from JSON string of 89556 took 2.148 +/- 0.475 ms over 64 trials (minimal whitespace, sort_keys=FALSE) -To Google-style pretty-print JNode from JSON string of 89556 took 4.269 +/- 0.549 ms over 64 trials (sort_keys=true, indent=4) -To Whitesmith-style pretty-print JNode from JSON string of 89556 took 4.328 +/- 0.448 ms over 64 trials (sort_keys=true, indent=4) -To PPrint-style pretty-print JNode from JSON string of 89556 took 7.543 +/- 1.133 ms over 64 trials (sort_keys=true, indent=4) +To compress JNode from JSON string of 89556 took 3.923 +/- 0.473 ms over 64 trials (minimal whitespace, sort_keys=TRUE) +To compress JNode from JSON string of 89556 took 2.144 +/- 0.267 ms over 64 trials (minimal whitespace, sort_keys=FALSE) +To Google-style pretty-print JNode from JSON string of 89556 took 4.823 +/- 0.912 ms over 64 trials (sort_keys=true, indent=4) +To Whitesmith-style pretty-print JNode from JSON string of 89556 took 4.274 +/- 0.587 ms over 64 trials (sort_keys=true, indent=4) +To PPrint-style pretty-print JNode from JSON string of 89556 took 6.174 +/- 0.485 ms over 64 trials (sort_keys=true, indent=4) ========================= Testing performance of JsonSchemaValidator and random JSON creation ========================= -To create a random set of tweet JSON of size 140467 (15 tweets) based on the matching schema took 6.966 +/- 3.393 ms over 64 trials -To compile the tweet schema to a validation function took 0.232 +/- 0.057 ms over 64 trials -To validate tweet JSON of size 140467 (15 tweets) based on the compiled schema took 1.13 +/- 0.276 ms over 64 trials +To create a random set of tweet JSON of size 169566 (15 tweets) based on the matching schema took 6.868 +/- 3.266 ms over 64 trials +To compile the tweet schema to a validation function took 0.389 +/- 0.786 ms over 64 trials +To validate tweet JSON of size 169566 (15 tweets) based on the compiled schema took 1.064 +/- 0.172 ms over 64 trials ========================= Testing JSON grepper's API request tool =========================