Skip to content

Commit e71f78e

Browse files
committed
LuaTableWriter allows "Name = Value" expression.
Retarget to .NET Standard 1.1 & 2.0.
1 parent b64d905 commit e71f78e

File tree

9 files changed

+126
-16
lines changed

9 files changed

+126
-16
lines changed

Luaon.NET/Exceptions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Runtime.Serialization;
43
using System.Security;
54
using System.Text;
5+
#if NETSTANDARD2_0
6+
using System.Runtime.Serialization;
7+
#endif
68

79
namespace Luaon
810
{
911
/// <summary>
1012
/// Base type of Luaon.NET exceptions.
1113
/// </summary>
14+
#if NETSTANDARD2_0
1215
[Serializable]
16+
#endif
1317
public class LuaonException : Exception
1418
{
1519

@@ -25,15 +29,19 @@ public LuaonException(string message, Exception inner) : base(message, inner)
2529
{
2630
}
2731

32+
#if NETSTANDARD2_0
2833
[SecurityCritical]
2934
protected LuaonException(
3035
SerializationInfo info,
3136
StreamingContext context) : base(info, context)
3237
{
3338
}
39+
#endif
3440
}
3541

42+
#if NETSTANDARD2_0
3643
[Serializable]
44+
#endif
3745
public class LuaTableWriterException : LuaonException
3846
{
3947

@@ -54,6 +62,7 @@ public LuaTableWriterException(string message, Exception inner) : base(message,
5462
{
5563
}
5664

65+
#if NETSTANDARD2_0
5766
[SecurityCritical]
5867
protected LuaTableWriterException(
5968
SerializationInfo info,
@@ -68,6 +77,7 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont
6877
base.GetObjectData(info, context);
6978
info.AddValue("Path", Path);
7079
}
80+
#endif
7181

7282
/// <summary>
7383
/// Gets the Lua property path where the exception happens.

Luaon.NET/Linq/LField.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ public override void WriteTo(LuaTableTextWriter writer)
7575
writer.WriteKey((string)Name);
7676
break;
7777
default:
78+
#if NETSTANDARD2_0
7879
Debug.Fail("Invalid Name.TokenType.");
80+
#else
81+
Debug.Assert(false);
82+
#endif
7983
break;
8084
}
8185
}

Luaon.NET/Linq/LTableStore.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ protected override void SetItem(int index, LField item)
3535
base.SetItem(index, item);
3636
if (oldField != item && oldField.Name != item.Name)
3737
{
38-
if (fieldsDict.Remove(oldField.Name, out var itemInDict))
38+
if (fieldsDict.TryGetValue(oldField.Name, out var itemInDict) && fieldsDict.Remove(oldField.Name))
3939
{
4040
// There might be duplicate keys…
4141
if (oldField != itemInDict) fieldsDict.Add(itemInDict.Name, itemInDict);
@@ -49,7 +49,7 @@ protected override void RemoveItem(int index)
4949
{
5050
var oldField = Items[index];
5151
base.RemoveItem(index);
52-
if (fieldsDict.Remove(oldField.Name, out var itemInDict))
52+
if (fieldsDict.TryGetValue(oldField.Name, out var itemInDict) && fieldsDict.Remove(oldField.Name))
5353
{
5454
// There might be duplicate keys…
5555
if (oldField != itemInDict) fieldsDict.Add(itemInDict.Name, itemInDict);
@@ -102,8 +102,11 @@ public LField FieldFromName(int name, LValue nameRef, bool allowsCreation)
102102
positionalIndex++;
103103
if (name == positionalIndex) return i;
104104
}
105-
105+
#if NETSTANDARD2_0
106106
Debug.Fail("Should not execute to here.");
107+
#else
108+
Debug.Assert(false);
109+
#endif
107110
return null;
108111
}
109112

Luaon.NET/LuaConvert.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using System.Diagnostics;
44
using System.Globalization;
55
using System.IO;
6-
using System.Numerics;
7-
using System.Reflection.Metadata;
86
using System.Text;
97

108
namespace Luaon
@@ -42,6 +40,53 @@ public static class LuaConvert
4240
/// </summary>
4341
public static readonly string NaN = "0/0";
4442

43+
private static readonly HashSet<string> reservedKeywords = new HashSet<string>
44+
{
45+
"and", "break", "do", "else", "elseif",
46+
"end", "false", "for", "function", "if",
47+
"in", "local", "nil", "not", "or",
48+
"repeat", "return", "then", "true", "until", "while"
49+
};
50+
51+
private const int MIN_KEYWORD_LENGTH = 2;
52+
private const int MAX_KEYWORD_LENGTH = 8;
53+
54+
/// <summary>
55+
/// Determines whether the specified expression can be a valid identifier.
56+
/// </summary>
57+
/// <param name="expression">The name expression to be checked.</param>
58+
/// <returns>
59+
/// <c>true</c> if <paramref name="expression"/> can be a valid Name
60+
/// (as defined in <a href="http://www.lua.org/manual/5.1/manual.html#2.1">Chapter 2.1, Lua Manual</a>)
61+
/// and does not conflict with any reserved keywords.
62+
/// </returns>
63+
public static bool IsValidIdentifier(string expression)
64+
{
65+
if (expression == null) throw new ArgumentNullException(nameof(expression));
66+
if (expression.Length == 0) return false;
67+
if (expression.Length >= MIN_KEYWORD_LENGTH && expression.Length <= MAX_KEYWORD_LENGTH
68+
&& expression[0] > 'a' && expression[0] < 'z')
69+
{
70+
// Check for keyword
71+
if (reservedKeywords.Contains(expression)) return false;
72+
}
73+
// Lua only allows ASCII characters as identifiers
74+
if (!(expression[0] == '_'
75+
|| expression[0] >= 'A' && expression[0] <= 'Z'
76+
|| expression[0] >= 'a' && expression[0] <= 'z')) return false;
77+
for (int i = 0; i < expression.Length; i++)
78+
{
79+
if (!(expression[i] == '_'
80+
|| expression[i] >= 'A' && expression[i] <= 'Z'
81+
|| expression[i] >= 'a' && expression[i] <= 'z'
82+
|| expression[i] >= '0' && expression[i] <= '9')) return false;
83+
}
84+
85+
return true;
86+
}
87+
88+
#region ToString methods
89+
4590
/// <summary>
4691
/// Converts the <see cref="bool"/> value into Lua representation.
4792
/// </summary>
@@ -360,6 +405,8 @@ public static string ToString(object value)
360405
throw new ArgumentException($"Unsupported type: {ut}.");
361406
}
362407

408+
#endregion
409+
363410
internal static void WriteEscapedString(string value, StringDelimiterInfo delimiter, TextWriter writer)
364411
{
365412
Debug.Assert(delimiter != null);

Luaon.NET/LuaTableTextWriter.cs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4-
using System.ComponentModel.Design;
54
using System.Diagnostics;
65
using System.IO;
76
using System.Linq;
@@ -40,6 +39,7 @@ private enum Token
4039
private LuaContainerContext currentContext;
4140
private State currentState = State.Start;
4241
private int _Indentation;
42+
private int _MaxUnquotedNameLength = 64;
4343

4444
// NextState[State][Token]
4545
private static readonly State[][] NextStateTable =
@@ -75,6 +75,23 @@ public LuaTableTextWriter(TextWriter writer)
7575
/// </summary>
7676
public bool CloseWriter { get; set; }
7777

78+
/// <summary>
79+
/// Gets/sets the maximum allowed table field name that will be checked for chance of
80+
/// using <c>Name = </c> expression instad of <c>["key"] = </c>, to reduce the
81+
/// generated code length and improve redability.
82+
/// </summary>
83+
/// <value>A positive value, or <c>0</c> to disable this feature.</value>
84+
/// <remarks>The default value is <c>64</c>.</remarks>
85+
public int MaxUnquotedNameLength
86+
{
87+
get { return _MaxUnquotedNameLength; }
88+
set
89+
{
90+
if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
91+
_MaxUnquotedNameLength = value;
92+
}
93+
}
94+
7895
protected bool IsClosed { get; private set; }
7996

8097
public Formatting Formatting
@@ -148,7 +165,11 @@ private LuaContainerType Pop()
148165
currentState = State.Key;
149166
break;
150167
default:
168+
#if NETSTANDARD2_0
151169
Debug.Fail("Invalid ContainerType.");
170+
#else
171+
Debug.Assert(false);
172+
#endif
152173
break;
153174
}
154175
// Return the poped container type
@@ -221,14 +242,30 @@ public virtual void WriteEndKey()
221242
/// <summary>
222243
/// Writes a Lua table field key.
223244
/// </summary>
245+
/// <param name="key">The key content to write.</param>
224246
public virtual void WriteKey(string key)
225247
{
226248
if (key == null) throw new ArgumentNullException(nameof(key));
227-
WriteStartKey();
228-
WriteLiteral(key);
229-
WriteEndKey();
230-
currentContext.Key = key;
231-
currentContext.KeyIsExpression = false;
249+
if (key.Length <= MaxUnquotedNameLength && LuaConvert.IsValidIdentifier(key))
250+
{
251+
AssertContainerType(LuaContainerType.Table);
252+
DelimitLastValue(Token.KeyStart);
253+
GotoNextState(Token.Literal);
254+
Writer.Write(key);
255+
if ((_Formatting & Formatting.Prettified) == Formatting.Prettified)
256+
Writer.Write(" = ");
257+
else
258+
Writer.Write("=");
259+
GotoNextState(Token.KeyEnd);
260+
}
261+
else
262+
{
263+
WriteStartKey();
264+
WriteLiteral(key);
265+
WriteEndKey();
266+
currentContext.Key = key;
267+
currentContext.KeyIsExpression = false;
268+
}
232269
}
233270

234271
/// <summary>

Luaon.NET/Luaon.NET.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netcoreapp2.0</TargetFramework>
4+
<TargetFrameworks>netstandard1.1;netstandard2.0</TargetFrameworks>
55
<RootNamespace>Luaon</RootNamespace>
66
</PropertyGroup>
77

Luaon.NET/Utility.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.Globalization;
55
using System.IO;
6+
using System.Reflection;
67
using System.Text;
78

89
namespace Luaon
@@ -17,7 +18,11 @@ public static StringWriter CreateStringWriter(int minCapacity = 0)
1718

1819
public static Type GetUnderlyingType(Type t)
1920
{
21+
#if NETSTANDARD2_0
2022
if (t.IsEnum) return Enum.GetUnderlyingType(t);
23+
#else
24+
if (t.GetTypeInfo().IsEnum) return Enum.GetUnderlyingType(t);
25+
#endif
2126
var ut = Nullable.GetUnderlyingType(t);
2227
if (ut != null) return ut;
2328
return t;

XUnitTestProject1/Tests/LinqTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public void LTableTest()
4747
Assert.Equal(1, table["Child"][1]);
4848
Assert.Equal(2, table["Child"][2]);
4949
Assert.Equal(3, table["Child"][3]);
50-
Assert.Equal("{[\"Test1\"]=1,2,[\"Test2\"]=3,4.5,[20]=5,[true]=\"6\",[\"Child\"]={1,2,3,4,5}}",
50+
Assert.Equal("{Test1=1,2,Test2=3,4.5,[20]=5,[true]=\"6\",Child={1,2,3,4,5}}",
5151
table.ToString(Formatting.None));
5252
}
5353

XUnitTestProject1/Tests/LuaTableTextWriterTest.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ public void WriteKeyedTest()
5252
tw.WriteLiteral(456);
5353
tw.WriteKey(TimeSpan.FromHours(3.2));
5454
tw.WriteNil();
55+
tw.WriteKey("while");
56+
tw.WriteNil();
57+
tw.WriteKey("function");
58+
tw.WriteNil();
5559

5660
// Though it's meaningless…
5761
tw.WriteStartKey();
@@ -64,7 +68,7 @@ public void WriteKeyedTest()
6468

6569
tw.WriteEndTable();
6670
tw.Flush();
67-
Assert.Equal("{[\"Test\"]=\"value\",[123]=456,[\"03:12:00\"]=nil,[{1,2}]=123}", sw.ToString());
71+
Assert.Equal("{Test=\"value\",[123]=456,[\"03:12:00\"]=nil,[\"while\"]=nil,[\"function\"]=nil,[{1,2}]=123}", sw.ToString());
6872
}
6973
}
7074

@@ -94,7 +98,7 @@ public void WriteNestedTableTest()
9498

9599
tw.WriteEndTable();
96100
tw.Flush();
97-
Assert.Equal("{{1,2,3},4,[\"Named5\"]=5{6,[\"Named7\"]=7}}", sw.ToString());
101+
Assert.Equal("{{1,2,3},4,Named5=5{6,Named7=7}}", sw.ToString());
98102
}
99103
}
100104

0 commit comments

Comments
 (0)