Skip to content
This repository has been archived by the owner on Jun 2, 2023. It is now read-only.

Commit

Permalink
#116 Artemis: add support for V1 ASTs, numeric Lua table indices, bra…
Browse files Browse the repository at this point in the history
…cketed Lua strings
  • Loading branch information
www committed Feb 12, 2023
1 parent bc59a89 commit e0cb1f5
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 83 deletions.
204 changes: 132 additions & 72 deletions VNTextPatch.Shared/Scripts/Artemis/ArtemisAstScript.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -40,77 +41,42 @@ public void Load(ScriptLocation location)

public IEnumerable<ScriptString> GetStrings()
{
foreach (LuaTable line in GetLineTables())
foreach ((ILuaNode node, ScriptStringType type) in GetStringNodes())
{
StringBuilder text = new StringBuilder();
foreach (ILuaNode item in line)
{
switch (item)
{
case LuaAttribute attr:
if (attr.Name == "name" && attr.Value is LuaTable names)
{
foreach (LuaString name in names.OfType<LuaString>())
{
yield return new ScriptString(name.Value, ScriptStringType.CharacterName);
}
}
else
{
throw new InvalidDataException($"Encountered unknown attribute \"{attr.Name}\" in message");
}
break;

case LuaString str:
text.Append(str.Value);
break;

case LuaTable table:
if (table.Count == 1 && table[0] is LuaString cmdName && cmdName.Value == "rt2")
text.AppendLine();
else
TableToCommand(table, text);

break;

default:
throw new InvalidDataException("Message contains invalid item types");
}
}
yield return new ScriptString(text.ToString().Trim(), ScriptStringType.Message);
string text = node switch
{
LuaString str => str.Value,
LuaTable table => SerializeMessage(table),
_ => throw new InvalidDataException()
};
yield return new ScriptString(text, type);
}
}

public void WritePatched(IEnumerable<ScriptString> strings, ScriptLocation location)
{
using IEnumerator<ScriptString> stringEnumerator = strings.GetEnumerator();

foreach (LuaTable line in GetLineTables())
foreach ((ILuaNode node, ScriptStringType type) in GetStringNodes())
{
line.Clear();
if (!stringEnumerator.MoveNext())
throw new InvalidDataException("Not enough strings in translation");
throw new InvalidDataException("Too few strings in translation");

LuaTable names = null;
while (stringEnumerator.Current.Type == ScriptStringType.CharacterName)
{
names ??= new LuaTable();
names.Add(new LuaString(stringEnumerator.Current.Text));
if (!stringEnumerator.MoveNext())
throw new InvalidDataException("Not enough strings in translation");
}
if (names != null)
line.Add(new LuaAttribute("name", names));
if (stringEnumerator.Current.Type != type)
throw new InvalidDataException("Translation type mismatch");

string text = ProportionalWordWrapper.Default.Wrap(stringEnumerator.Current.Text, CommandRegex);
foreach ((string segment, Match match) in StringUtil.GetMatchingAndSurroundingTexts(text, new Regex(@"\r\n|\[.+?\]")))
string text = stringEnumerator.Current.Text;

switch (node)
{
if (segment != null)
line.Add(new LuaString(segment));
else if (match.Value == "\r\n")
line.Add(new LuaTable { new LuaString("rt2") });
else
line.Add(CommandToTable(match.Value));
case LuaString str:
str.Value = stringEnumerator.Current.Text;
break;

case LuaTable table:
text = ProportionalWordWrapper.Default.Wrap(text, CommandRegex);
DeserializeMessage(text, table);
break;
}
}

Expand All @@ -125,29 +91,123 @@ public void WritePatched(IEnumerable<ScriptString> strings, ScriptLocation locat
}
}

private IEnumerable<LuaTable> GetLineTables()
private IEnumerable<(ILuaNode, ScriptStringType)> GetStringNodes()
{
LuaNumber version = _rootNodes.OfType<LuaAttribute>()
.FirstOrDefault(a => a.Name == "astver")
?.Value as LuaNumber;
return version?.Value switch
{
null => GetStringNodesV1(),
"2.0" => GetStringNodesV2(),
_ => throw new NotSupportedException($".ast with version {version} is not supported")
};
}

private IEnumerable<(ILuaNode, ScriptStringType)> GetStringNodesV1()
{
LuaTable texts = _ast["text"] as LuaTable;
if (texts == null)
yield break;

foreach (LuaTable text in texts.OfType<LuaAttribute>()
.Select(a => a.Value)
.OfType<LuaTable>())
{
LuaString name = (text["name"] as LuaTable)?["name"] as LuaString;
if (name != null)
yield return (name, ScriptStringType.CharacterName);

LuaTable select = text["select"] as LuaTable;
if (select != null)
{
foreach (LuaString choice in select.OfType<LuaString>())
{
yield return (choice, ScriptStringType.Message);
}
}

LuaTable message = text.OfType<LuaTable>().FirstOrDefault();
if (message != null)
yield return (message, ScriptStringType.Message);
}
}

private IEnumerable<(ILuaNode, ScriptStringType)> GetStringNodesV2()
{
foreach (LuaTable block in _ast.OfType<LuaAttribute>()
.Select(a => a.Value)
.OfType<LuaTable>())
{
LuaTable text = block["text"] as LuaTable;
if (text == null)
continue;
LuaTable select = (block["select"] as LuaTable)?["ja"] as LuaTable;
if (select != null)
{
foreach (LuaString choice in select.OfType<LuaString>())
{
yield return (choice, ScriptStringType.Message);
}
}

LuaTable text = (block["text"] as LuaTable)?["ja"] as LuaTable;
if (text != null && text.Count == 1)
{
LuaTable message = text[0] as LuaTable;
if (message == null)
continue;

LuaTable names = message["name"] as LuaTable;
if (names != null)
{
foreach (LuaString name in names.OfType<LuaString>())
{
yield return (name, ScriptStringType.CharacterName);
}
}

yield return (message, ScriptStringType.Message);
}
}
}

private string SerializeMessage(LuaTable message)
{
StringBuilder text = new StringBuilder();
foreach (ILuaNode item in message)
{
switch (item)
{
case LuaString str:
text.Append(str.Value);
break;

case LuaTable table:
if (table.Count == 1 && table[0] is LuaString cmdName && cmdName.Value == "rt2")
text.AppendLine();
else
SerializeCommand(table, text);

LuaTable ja = text["ja"] as LuaTable;
if (ja == null || ja.Count != 1)
continue;
break;
}
}

LuaTable line = ja[0] as LuaTable;
if (line == null)
continue;
return text.ToString().Trim();
}

yield return line;
private void DeserializeMessage(string text, LuaTable table)
{
table.RemoveAll(n => !(n is LuaAttribute));
foreach ((string segment, Match match) in StringUtil.GetMatchingAndSurroundingTexts(text, new Regex(@"\r\n|\[.+?\]")))
{
if (segment != null)
table.Add(new LuaString(segment));
else if (match.Value == "\r\n")
table.Add(new LuaTable { new LuaString("rt2") });
else
table.Add(DeserializeCommand(match.Value));
}
}

private static void TableToCommand(LuaTable table, StringBuilder cmd)
private static void SerializeCommand(LuaTable table, StringBuilder cmd)
{
cmd.Append("[");

Expand All @@ -168,7 +228,7 @@ private static void TableToCommand(LuaTable table, StringBuilder cmd)
cmd.Append("]");
}

private static LuaTable CommandToTable(string cmd)
private static LuaTable DeserializeCommand(string cmd)
{
Match match = CommandRegex.Match(cmd);
if (!match.Success)
Expand Down
12 changes: 11 additions & 1 deletion VNTextPatch.Shared/Scripts/Artemis/LuaAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,17 @@ public override string ToString()

public void ToString(StringBuilder result, int indentLevel)
{
result.Append(Name);
if (char.IsDigit(Name[0]))
{
result.Append('[');
result.Append(Name);
result.Append(']');
}
else
{
result.Append(Name);
}

result.Append(" = ");
Value.ToString(result, indentLevel);
}
Expand Down
94 changes: 84 additions & 10 deletions VNTextPatch.Shared/Scripts/Artemis/LuaParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ public static ILuaNode Read(string text, ref int pos)
return ReadNumber(text, ref pos);

if (c == '"' || c == '\'')
return ReadString(text, ref pos);
return ReadQuotedString(text, ref pos);

if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')
if (c == '[' && pos + 1 < text.Length && (text[pos + 1] == '[' || text[pos + 1] == '='))
return ReadBracketedString(text, ref pos);

if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '[')
return ReadAttribute(text, ref pos);

if (c == '{')
Expand Down Expand Up @@ -49,7 +52,7 @@ private static LuaNumber ReadNumber(string text, ref int pos)
return new LuaNumber(text.Substring(startPos, pos - startPos));
}

private static LuaString ReadString(string text, ref int pos)
private static LuaString ReadQuotedString(string text, ref int pos)
{
int startPos = pos;
char quote = text[pos++];
Expand All @@ -64,18 +67,89 @@ private static LuaString ReadString(string text, ref int pos)
return new LuaString(StringUtil.UnescapeC(text.Substring(startPos + 1, pos - startPos - 2)));
}

private static LuaString ReadBracketedString(string text, ref int pos)
{
pos++;
int openBracketLevel = 0;
while (pos < text.Length && text[pos] == '=')
{
openBracketLevel++;
pos++;
}

if (pos == text.Length || text[pos] != '[')
throw new InvalidDataException("No second [ found in literal string");

pos++;
int startPos = pos;
int endPos = 0;
bool inClosingBracket = false;
int closeBracketLevel = 0;
while (true)
{
if (pos == text.Length)
throw new InvalidDataException("Unclosed literal string encountered");

char c = text[pos++];
if (c == ']')
{
if (!inClosingBracket || closeBracketLevel != openBracketLevel)
{
endPos = pos - 1;
inClosingBracket = true;
closeBracketLevel = 0;
}
else
{
break;
}
}
else if (c == '=')
{
if (inClosingBracket)
closeBracketLevel++;
}
else
{
inClosingBracket = false;
}
}

return new LuaString(text.Substring(startPos, endPos - startPos));
}

private static LuaAttribute ReadAttribute(string text, ref int pos)
{
int nameStartPos = pos;
while (pos < text.Length)
string name;
if (text[pos] == '[')
{
char c = text[pos];
if (char.IsLetterOrDigit(c) || c == '_')
pos++;
else
break;
pos++;
ILuaNode nameNode = Read(text, ref pos);
name = nameNode switch
{
LuaNumber num => num.Value,
LuaString str => str.Value,
_ => throw new InvalidDataException("Invalid data type in attribute name")
};
SkipWhitespace(text, ref pos);
if (pos == text.Length || text[pos] != ']')
throw new InvalidDataException("No \"]\" found after attribute name");

pos++;
}
else
{
while (pos < text.Length)
{
char c = text[pos];
if (char.IsLetterOrDigit(c) || c == '_')
pos++;
else
break;
}
name = text.Substring(nameStartPos, pos - nameStartPos);
}
string name = text.Substring(nameStartPos, pos - nameStartPos);

SkipWhitespace(text, ref pos);
if (pos == text.Length || text[pos++] != '=')
Expand Down

0 comments on commit e0cb1f5

Please sign in to comment.