Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"enableAllProjectMcpServers": false,
"permissions": {
"allow": [
"Bash(find:*)"
]
}
}
40 changes: 40 additions & 0 deletions CommandForgeGenerator.Tests/GenerateTestCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace CommandForgeGenerator.Tests;

public class GenerateTestCode
{
public const string TextCommand = """
#if ENABLE_COMMAND_FORGE_GENERATOR
namespace CommandForgeGenerator.Command
{
public partial class TextCommand : ICommandForgeCommand
{
public const string Type = "text";
public readonly CommandId CommandId;

public readonly string Character;
public readonly string Body;


public static TextCommand Create(int commandId, global::Newtonsoft.Json.Linq.JToken json)
{

var Character = (string)json["character"];
var Body = (string)json["body"];


return new TextCommand(commandId, Character, Body);
}

public TextCommand(int commandId, string Character, string Body)
{
CommandId = (CommandId)commandId;

this.Character = Character;
this.Body = Body;

}
}
}
#endif
""";
}
3 changes: 2 additions & 1 deletion CommandForgeGenerator.Tests/Test.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System.Linq;
using CommandForgeGenerator.Generator.CodeGenerate;
using CommandForgeGenerator.Generator.Json;
using CommandForgeGenerator.Generator.Semantic;
Expand Down Expand Up @@ -64,6 +65,7 @@ public void GenerateTest()
var codeFiles = CodeGenerator.Generate(commandsSchema);

Assert.Equal(16, codeFiles.Count);
Assert.Equal(GenerateTestCode.TextCommand, codeFiles.FirstOrDefault(c => c.FileName == "TextCommand.g.cs").Code);

#region Internal

Expand All @@ -84,7 +86,6 @@ string GetSampleYaml()
body:
type: string
multiline: true
required: true

- id: emote
label: エモート
Expand Down
123 changes: 90 additions & 33 deletions CommandForgeGenerator/CodeGenerate/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ private static string GeneratePropertiesCode(List<CommandProperty> commandProper
properties.AppendLine();
foreach (var property in commandProperties)
{
var type = GetTypeCode(property.Type);
var type = GetTypeCode(property.Type, property.IsRequired);
properties.AppendLine($"public readonly {type} {property.CodeProperty};");
}

Expand All @@ -114,39 +114,82 @@ private static string GenerateCreateMethodTempVariables(List<CommandProperty> co
properties.AppendLine();
foreach (var property in commandProperties)
{
var type = GetTypeCode(property.Type);
if (property.Type is CommandPropertyType.CommandId)
{
properties.AppendLine($"var {property.CodeProperty} = (CommandId)((int)json[\"{property.Name}\"]);");
}
else if (property.Type is CommandPropertyType.Vector2)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector2((float){property.CodeProperty}Array[0], (float){property.CodeProperty}Array[1]);");
}
else if (property.Type is CommandPropertyType.Vector3)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector3((float){property.CodeProperty}Array[0], (float){property.CodeProperty}Array[1], (float){property.CodeProperty}Array[2]);");
}
else if (property.Type is CommandPropertyType.Vector4)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector4((float){property.CodeProperty}Array[0], (float){property.CodeProperty}Array[1], (float){property.CodeProperty}Array[2], (float){property.CodeProperty}Array[3]);");
}
else if (property.Type is CommandPropertyType.Vector2Int)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector2Int((int){property.CodeProperty}Array[0], (int){property.CodeProperty}Array[1]);");
}
else if (property.Type is CommandPropertyType.Vector3Int)
var type = GetTypeCode(property.Type, property.IsRequired);

if (property.IsRequired)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector3Int((int){property.CodeProperty}Array[0], (int){property.CodeProperty}Array[1], (int){property.CodeProperty}Array[2]);");
// required の場合は従来通り
if (property.Type is CommandPropertyType.CommandId)
{
properties.AppendLine($"var {property.CodeProperty} = (CommandId)((int)json[\"{property.Name}\"]);");
}
else if (property.Type is CommandPropertyType.Vector2)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector2((float){property.CodeProperty}Array[0], (float){property.CodeProperty}Array[1]);");
}
else if (property.Type is CommandPropertyType.Vector3)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector3((float){property.CodeProperty}Array[0], (float){property.CodeProperty}Array[1], (float){property.CodeProperty}Array[2]);");
}
else if (property.Type is CommandPropertyType.Vector4)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector4((float){property.CodeProperty}Array[0], (float){property.CodeProperty}Array[1], (float){property.CodeProperty}Array[2], (float){property.CodeProperty}Array[3]);");
}
else if (property.Type is CommandPropertyType.Vector2Int)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector2Int((int){property.CodeProperty}Array[0], (int){property.CodeProperty}Array[1]);");
}
else if (property.Type is CommandPropertyType.Vector3Int)
{
properties.AppendLine($"var {property.CodeProperty}Array = json[\"{property.Name}\"];");
properties.AppendLine($"var {property.CodeProperty} = new global::UnityEngine.Vector3Int((int){property.CodeProperty}Array[0], (int){property.CodeProperty}Array[1], (int){property.CodeProperty}Array[2]);");
}
else
{
properties.AppendLine($"var {property.CodeProperty} = ({type})json[\"{property.Name}\"];");
}
}
else
{
properties.AppendLine($"var {property.CodeProperty} = ({type})json[\"{property.Name}\"];");
// nullable の場合はnullチェックを追加
properties.AppendLine($"var {property.CodeProperty}Token = json[\"{property.Name}\"];");

if (property.Type is CommandPropertyType.CommandId)
{
properties.AppendLine($"{type} {property.CodeProperty} = {property.CodeProperty}Token?.Type == global::Newtonsoft.Json.Linq.JTokenType.Null ? null : (CommandId?)((int){property.CodeProperty}Token);");
}
else if (property.Type is CommandPropertyType.Vector2)
{
properties.AppendLine($"{type} {property.CodeProperty} = {property.CodeProperty}Token?.Type == global::Newtonsoft.Json.Linq.JTokenType.Null ? null : new global::UnityEngine.Vector2((float){property.CodeProperty}Token[0], (float){property.CodeProperty}Token[1]);");
}
else if (property.Type is CommandPropertyType.Vector3)
{
properties.AppendLine($"{type} {property.CodeProperty} = {property.CodeProperty}Token?.Type == global::Newtonsoft.Json.Linq.JTokenType.Null ? null : new global::UnityEngine.Vector3((float){property.CodeProperty}Token[0], (float){property.CodeProperty}Token[1], (float){property.CodeProperty}Token[2]);");
}
else if (property.Type is CommandPropertyType.Vector4)
{
properties.AppendLine($"{type} {property.CodeProperty} = {property.CodeProperty}Token?.Type == global::Newtonsoft.Json.Linq.JTokenType.Null ? null : new global::UnityEngine.Vector4((float){property.CodeProperty}Token[0], (float){property.CodeProperty}Token[1], (float){property.CodeProperty}Token[2], (float){property.CodeProperty}Token[3]);");
}
else if (property.Type is CommandPropertyType.Vector2Int)
{
properties.AppendLine($"{type} {property.CodeProperty} = {property.CodeProperty}Token?.Type == global::Newtonsoft.Json.Linq.JTokenType.Null ? null : new global::UnityEngine.Vector2Int((int){property.CodeProperty}Token[0], (int){property.CodeProperty}Token[1]);");
}
else if (property.Type is CommandPropertyType.Vector3Int)
{
properties.AppendLine($"{type} {property.CodeProperty} = {property.CodeProperty}Token?.Type == global::Newtonsoft.Json.Linq.JTokenType.Null ? null : new global::UnityEngine.Vector3Int((int){property.CodeProperty}Token[0], (int){property.CodeProperty}Token[1], (int){property.CodeProperty}Token[2]);");
}
else if (property.Type is CommandPropertyType.String)
{
properties.AppendLine($"{type} {property.CodeProperty} = {property.CodeProperty}Token?.Type == global::Newtonsoft.Json.Linq.JTokenType.Null ? null : (string?){property.CodeProperty}Token;");
}
else
{
properties.AppendLine($"{type} {property.CodeProperty} = {property.CodeProperty}Token?.Type == global::Newtonsoft.Json.Linq.JTokenType.Null ? null : ({type}){property.CodeProperty}Token;");
}
}
}

Expand All @@ -169,7 +212,7 @@ public static string GenerateConstructorPropertiesCode(List<CommandProperty> com
var properties = new StringBuilder();
foreach (var property in commandProperties)
{
var type = GetTypeCode(property.Type);
var type = GetTypeCode(property.Type, property.IsRequired);
properties.Append($", {type} {property.CodeProperty}");

}
Expand All @@ -189,9 +232,9 @@ private static string GenerateConstructSetPropertiesCode(List<CommandProperty> c
return construct.ToString();
}

private static string GetTypeCode(CommandPropertyType type)
private static string GetTypeCode(CommandPropertyType type, bool isRequired)
{
return type switch
var baseType = type switch
{
CommandPropertyType.String => "string",
CommandPropertyType.Int => "int",
Expand All @@ -205,6 +248,20 @@ private static string GetTypeCode(CommandPropertyType type)
CommandPropertyType.Vector3Int => "global::UnityEngine.Vector3Int",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};

// 値型でrequiredでない場合は nullable にする
if (!isRequired && type != CommandPropertyType.String)
{
return baseType + "?";
}

// 参照型でも明示的に nullable にする(C# 8.0以降)
if (!isRequired && type == CommandPropertyType.String)
{
return baseType + "?";
}

return baseType;
}

public static string GenerateLoaderCode(CommandsSemantics commandsSemantics)
Expand Down
9 changes: 8 additions & 1 deletion CommandForgeGenerator/Semantic/CommandSemanticsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
try
{
var jsonText = Yaml.ToJson(yamlText);
return JsonParser.Parse(JsonTokenizer.GetTokens(jsonText)) as JsonObject;

Check warning on line 39 in CommandForgeGenerator/Semantic/CommandSemanticsLoader.cs

View workflow job for this annotation

GitHub Actions / test

Possible null reference return.

Check warning on line 39 in CommandForgeGenerator/Semantic/CommandSemanticsLoader.cs

View workflow job for this annotation

GitHub Actions / test

Possible null reference return.

Check warning on line 39 in CommandForgeGenerator/Semantic/CommandSemanticsLoader.cs

View workflow job for this annotation

GitHub Actions / push

Possible null reference return.

Check warning on line 39 in CommandForgeGenerator/Semantic/CommandSemanticsLoader.cs

View workflow job for this annotation

GitHub Actions / push

Possible null reference return.
}
catch (Exception e)
{
Expand Down Expand Up @@ -90,7 +90,14 @@
_ => throw new Exception($"未知の property type \"{typeStr}\"")
};

properties.Add(new CommandProperty(mappedType, propName));
// required フィールドを読み取る
var isRequired = true;
if (propObj.Nodes.ContainsKey("required") && propObj["required"] is JsonBoolean requiredNode)
{
isRequired = requiredNode.Literal;
}

properties.Add(new CommandProperty(mappedType, propName, isRequired));
}

commands.Add(new CommandSemantics(commandName, properties));
Expand Down
4 changes: 3 additions & 1 deletion CommandForgeGenerator/Semantic/CommandsSemantics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ public CommandSemantics(string name, List<CommandProperty> properties)
public class CommandProperty{
public readonly string Name;
public readonly CommandPropertyType Type;
public readonly bool IsRequired;

public string CodeProperty => Name.ToUpper(0);

public CommandProperty(CommandPropertyType type, string name)
public CommandProperty(CommandPropertyType type, string name, bool isRequired)
{
Type = type;
Name = name;
IsRequired = isRequired;
}

}
Expand Down
4 changes: 2 additions & 2 deletions CommandForgeGenerator/Semantic/ReservedCommandSemantics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public static List<CommandSemantics> GetReservedCommand()
{
var groupStart = new CommandSemantics("group_start", new List<CommandProperty>()
{
new(CommandPropertyType.String, "groupName"),
new(CommandPropertyType.Bool, "isCollapsed"),
new(CommandPropertyType.String, "groupName", true),
new(CommandPropertyType.Bool, "isCollapsed", true),
});
var groupEnd = new CommandSemantics("group_end", new List<CommandProperty>());
return new List<CommandSemantics>(){groupStart, groupEnd};
Expand Down
Loading