Skip to content

Instantly share code, notes, and snippets.

@kb10uy
Created January 4, 2014 08:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kb10uy/8252913 to your computer and use it in GitHub Desktop.
Save kb10uy/8252913 to your computer and use it in GitHub Desktop.
KxConfigのソースと仕様書。 Irony偉大すぎる
/*
色々できるOOPっぽいコンフィグ
KxConfig V1.0
2014/01/04 kb10uy
コンパイルの仕方
1.適当なプロジェクトに放り込む
2.Irony( http://irony.codeplex.com/ )のプロジェクトを適当にビルドして、Irony.dllを参照に追加する
3.コンパイルする
*/
/*
ライセンス表示
Copyright (c) 20012 Roman Ivantsov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Irony.Parsing;
using Irony;
using System.IO;
namespace KxConfig
{
/// <summary>
/// 色々できるKxConfigを読み込むクラス。
/// </summary>
public class KxConfigFile
{
#region 非推奨な
static Dictionary<string, KxConfigValueType> typeset = new Dictionary<string, KxConfigValueType>
{
{ "Number" , KxConfigValueType.Number},
{ "String" , KxConfigValueType.String},
{ "Array" , KxConfigValueType.Array },
};
#endregion
Dictionary<string, KxConfigGroup> groups;
/// <summary>
/// ファイルの文字列そのまま
/// </summary>
public string RawString { get; protected set; }
/// <summary>
/// 所属する全てのグループ
/// </summary>
public IDictionary<string, KxConfigGroup> Groups
{
get { return groups; }
}
/// <summary>
/// 解析器
/// </summary>
public static KxConfigGrammar Grammar { get; protected set; }
/// <summary>
/// パーサ
/// </summary>
public static Parser Parser { get; protected set; }
/// <summary>
/// 生ツリー
/// </summary>
public ParseTree RawTree { get; protected set; }
static KxConfigFile()
{
Grammar = new KxConfigGrammar();
Parser = new Parser(Grammar);
}
/// <summary>
/// ファイルを読み込む
/// </summary>
/// <param name="file">ファイル名</param>
public void LoadFile(string file)
{
RawString = File.ReadAllText(file);
Parse();
}
private void Parse()
{
groups = new Dictionary<string, KxConfigGroup>();
RawTree = Parser.Parse(RawString);
if (RawTree == null) throw new InvalidDataException("無効なKxConfigです");
var gs = RawTree.Root.ChildNodes;
var dg = new KxConfigGroup(); //形式的にダミーを作ってそれから取得
foreach (var g in gs)
{
ParseGroup(ref dg, g);
}
groups = dg.gs;
}
/// <summary>
/// 任意のプロパティを直接取得。
/// Group.Group#Propertyのように指定。
/// したがって、"."や"#"を含む名前のプロパティは
/// 正常に取得できないおそれがある。
/// </summary>
/// <param name="path">プロパティフルパス</param>
/// <returns></returns>
public KxConfigValue this[string path]
{
get
{
KxConfigGroup cg;
var gl = path.Split('.');
if (gl.Length == 0) return null;
var p = gl.Last().Split('#');
if (p.Length <= 1) return null;
//直下のプロパティ用
if (gl.Length == 1) gl[0] = p[0];
cg = groups[gl[0]];
var cgl = gl.Skip(1).ToArray();
for (int i = 0; i < cgl.Length; i++)
{
cg = cg.Groups[cgl[i]];
}
return cg.Properties[p[1]].Value;
}
}
#region パース用内部関数
private void ParseGroup(ref KxConfigGroup par, ParseTreeNode groupnode)
{
if (groupnode.Term.Name != "Group") throw new InvalidDataException("Groupの形式ではありません");
var gname = groupnode.ChildNodes[0].Token.ValueString;
var cnodes = groupnode.ChildNodes[1].ChildNodes;
var pdg = new KxConfigGroup();
foreach (var i in cnodes)
{
switch (i.ChildNodes[0].Term.Name)
{
case "Property":
ParseProperty(ref pdg, i.ChildNodes[0]);
break;
case "Group":
ParseGroup(ref pdg, i.ChildNodes[0]);
break;
default:
throw new InvalidDataException("何かがおかしい");
}
}
par.Groups[gname] = pdg;
}
private void ParseProperty(ref KxConfigGroup par, ParseTreeNode propnode)
{
var prop = new KxConfigProperty();
var types = propnode.ChildNodes[0].ChildNodes[0].Token.ValueString;
var cty = typeset[types];
var name = propnode.ChildNodes[1].Token.ValueString;
prop.Name = name;
//値のパース
if (propnode.ChildNodes[2].ChildNodes[0].Term.Name != types)
throw new InvalidDataException(String.Format("型の不一致が発生しています : 宣言 {0} に対して {1} の値", types, propnode.ChildNodes[2].ChildNodes[0].Term.Name));
ParseValue(ref prop._value, propnode.ChildNodes[2].ChildNodes[0]);
par.Properties[name] = prop;
}
private void ParseValue(ref KxConfigValue par, ParseTreeNode valnode)
{
switch (valnode.Term.Name)
{
case "Number":
par.Type = KxConfigValueType.Number;
par.NumberValue = Convert.ToDouble(valnode.Token.ValueString);
break;
case "String":
par.Type = KxConfigValueType.String;
par.StringValue = valnode.Token.ValueString;
break;
case "Array":
par.Type = KxConfigValueType.Array;
ParseArrayValue(ref par._av, valnode.ChildNodes[0]);
break;
default:
throw new InvalidDataException("何故だッ!");
}
}
private void ParseArrayValue(ref List<KxConfigValue> par, ParseTreeNode valnode)
{
foreach (var i in valnode.ChildNodes)
{
KxConfigValue v = new KxConfigValue();
ParseValue(ref v, i.ChildNodes[0]);
par.Add(v);
}
}
#endregion
}
/// <summary>
/// KxConfigでのグループを表す。
/// </summary>
public class KxConfigGroup
{
internal Dictionary<string, KxConfigGroup> gs = new Dictionary<string, KxConfigGroup>();
internal Dictionary<string, KxConfigProperty> ps = new Dictionary<string, KxConfigProperty>();
/// <summary>
/// 所属する全てのグループ
/// </summary>
public IDictionary<string, KxConfigGroup> Groups
{
get { return gs; }
}
/// <summary>
/// 所属する全てのプロパティ
/// </summary>
public IDictionary<string, KxConfigProperty> Properties
{
get { return ps; }
}
/// <summary>
/// グループ名
/// </summary>
public string Name { get; set; }
}
/// <summary>
/// KxConfigでのプロパティを表す。
/// </summary>
public class KxConfigProperty
{
internal KxConfigValue _value = new KxConfigValue();
/// <summary>
/// プロパティ名
/// </summary>
public string Name { get; set; }
/// <summary>
/// プロパティの値
/// </summary>
public KxConfigValue Value
{
get { return _value; }
set { _value = value; }
}
}
/// <summary>
/// KxConfigでの値を表す。
/// 数値・文字列に関してはexplicitな型変換演算子が、
/// 配列型に関してはIEnumerable&lt;KxConfigValue&gt;が
/// 実装されている。
/// </summary>
public class KxConfigValue : IEnumerable<KxConfigValue>
{
internal List<KxConfigValue> _av = new List<KxConfigValue>();
/// <summary>
/// 値の型
/// </summary>
public KxConfigValueType Type { get; set; }
/// <summary>
/// 数値
/// </summary>
public double NumberValue { get; set; }
/// <summary>
/// 文字列
/// </summary>
public string StringValue { get; set; }
/// <summary>
/// 配列
/// </summary>
public IList<KxConfigValue> ArrayValue { get { return _av; } }
/// <summary>
/// 新しく(ry
/// </summary>
public KxConfigValue()
{
NumberValue = 0;
StringValue = "";
}
/// <summary>
/// 型変換
/// </summary>
/// <param name="t">--</param>
/// <returns>--</returns>
public static explicit operator double(KxConfigValue t)
{
return t.NumberValue;
}
/// <summary>
/// 型変換
/// </summary>
/// <param name="t">--</param>
/// <returns>--</returns>
public static explicit operator string(KxConfigValue t)
{
return t.StringValue;
}
/// <summary>
/// ArrayValueからKxConfigValueを取得
/// </summary>
/// <param name="id">インデックス</param>
/// <returns>階下のKxConfigValue</returns>
public KxConfigValue this[int id]
{
get { return ArrayValue[id]; }
}
#region IEnumerable<KxConfigValue>の実装
/// <summary>
/// コレクションを反復処理する列挙子を返します。
/// </summary>
/// <returns>コレクションを反復処理する列挙子</returns>
public IEnumerator<KxConfigValue> GetEnumerator()
{
return ArrayValue.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ArrayValue.GetEnumerator();
}
#endregion
}
/// <summary>
/// KxConfigValueの型を指定します
/// </summary>
public enum KxConfigValueType
{
/// <summary>
/// 数値
/// </summary>
Number,
/// <summary>
/// 文字列
/// </summary>
String,
/// <summary>
/// 配列
/// </summary>
Array,
}
/// <summary>
/// KxConfigの解析器
/// </summary>
[Language("KxConfig", "1.0", "多分便利なコンフィグ")]
public class KxConfigGrammar : Grammar
{
/// <summary>
/// 新しいインスタンスを以下略
/// </summary>
public KxConfigGrammar()
: base()
{
var Number = new NumberLiteral("Number");
var String = new StringLiteral("String", "\"", StringOptions.AllowsAllEscapes);
var CommentLine = new CommentTerminal("Comment", "#", "\n", "\r");
var CommentBlock = new CommentTerminal("Comment", "#<", ">");
//数値設定
Number.DefaultIntTypes = new[] { TypeCode.Int32 };
Number.DefaultFloatType = TypeCode.Double;
String.EscapeChar = '\\';
NonGrammarTerminals.Add(CommentBlock);
NonGrammarTerminals.Add(CommentLine);
var Value = new NonTerminal("Value");
var Values = new NonTerminal("Value\'s\'");
var ValueSet = new NonTerminal("Array");
var Property = new NonTerminal("Property");
var Prefix = new NonTerminal("Prefix");
var Node = new NonTerminal("Node");
var Nodes = new NonTerminal("Nodes");
//var PropertySet = new NonTerminal("PropertySet");
var Group = new NonTerminal("Group");
var ConfigRoot = new NonTerminal("Root");
Value.Rule = Number | String | ValueSet;
Values.Rule = MakeStarRule(Values, ToTerm(","), Value);
ValueSet.Rule = ToTerm("[") + Values + "]";
Prefix.Rule = ToTerm("Number") | "String" | "Array" ;
Property.Rule = Prefix + ":" + String + "=" + Value + ";";
Node.Rule = Property | Group;
Nodes.Rule = MakeStarRule(Nodes, Node);
Group.Rule = String + ":" + "{" +Nodes+ "}";
ConfigRoot.Rule = MakeStarRule(ConfigRoot, Group);
Root = ConfigRoot;
MarkPunctuation("[", "]", ",", "{", "}",":",";","=");
}
}
}
#<
KxConfig V1.0 仕様
2014/01/04
コメントは、このように「ナンバーサインと開き山括弧」から「閉じ山括弧」までのブロックコメントと、
「ナンバーサインから行末まで」の行コメントが有ります。
フリーフォーマットなので改行は自由にできます。
あと文字コードはUTF-16推奨。
>
#直下には「グループ」のみ定義できます。
#グループは「"グループ名" : { 内容 }」のように定義します。
#グループは入れ子に出来ます
"Hoge" : {
"Huga" : {
}
}
#<
グループの下には「プロパティ」を定義できます。
プロパティは「型:"名前"=値;」のように定義します。
型はNumber,String,Arrayの3種類があり、これ以外は指定できません。
プロパティとグループの名前には日本語も使えますが、C#やVB.NETからアクセスするときに
"."と"#"を使うので、この2文字は使わない方がいいです。
(一応使うことも可能ですが取得が非常に面倒になります)
>
"Foo" : {
#Number型は数値で、Int32及びDoubleに対応しています。
Number:"数値"=20.0;
#String型は文字列で、(.NETの)String型に対応しています。
#エスケープシーケンスも(多分)有効です。
String:"StrValue"="Hello,World!";
#<
Array型は配列です。Number,Stringを入れられる他、
Arrayを入れ子にすることも可能です。
開始・終了には"["と"]"を、区切りには","を使います。
>
Array:"価格表"= [
#1つのArrayの中に型が混在していてもOKです
[ "商品" , "価格" ],
[ "A" , 250 ],
[ "B" , 1050 ]
];
}
#<
.NETからの使い方
(適切に参照された状態で)
読み込み
var cfg=new KxConfigFile();
cfg.LoadFile("ファイル名");
値の取得
cfg["グループ名#プロパティ名"]
入れ子の場合: cfg["グループ.子グループ#プロパティ"]
なお、キャスト演算子が定義されているので、
(double)や(string)を前につければ自然にその型になります。
また、IEnumerable of KxConfigValue が実装されているので、
foreach(var i in cfg["Main#価格表"]) などといったこともできます。
>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment