Привет всем.
Наваял макрос для генерации типизированного датасета из файла со схемой. Схему можно сгенерировать любым редактором датасета (например, в шарповском проекте) и подключить к немерловому проекту.
Использовать так:
namespace DataSets
{
[assembly:DataSetFromXml(@"DataBase1.xml", @"DataBase1.n")]
[assembly:DataSetFromSchema(@"DataBase2.xsd", @"DataBase2.n")]
}
def ds=DataSets.DataBase1();
Прежде чем привести код макроса, пара вопросов к знатокам компилятора/интеграции:
1. Позырьте генерации AST (метод Helper.IncludeFile), ибо то, что получилось — скорее хакерство. Там генерируется AST напрямую из немерлового кода. Может есть более другие пути?
2. В интеграции нужно бы выставлять перед парсингом очередного проекта его директорию как текущую. Иначе относительные пути при компиляции и работе IntelliSense оказываются разными (всплывающая подсказка над макросом: "cannot open file DataBase.n: Could not find file '(тут путь решения вместо проекта)\DataBase.n'").
3. Пришлось запатчить NemerleCodeGenerator, ибо нашел несколько ошибок (например, при генерации делегатов). Diff выложил
здесь (залейте кто-нибудь, кто права имеет).
4. Может макрос тоже включить в стандартную поставку компилятора? Пригодится.
Код макроса:
[Nemerle.MacroUsage (Nemerle.MacroPhase.BeforeInheritance, Nemerle.MacroTargets.Assembly)]
macro DataSetFromXml (sourceFile : string, nFile : string)
{
Helper.Generate(Nemerle.Macros.ImplicitCTX().Env, sourceFile, nFile,
fun(x){
def dataSet=DataSet();
_=dataSet.ReadXml(x);
dataSet;
});
}
[Nemerle.MacroUsage (Nemerle.MacroPhase.BeforeInheritance, Nemerle.MacroTargets.Assembly)]
macro DataSetFromSchema (sourceFile : string, nFile : string)
{
Helper.Generate(Nemerle.Macros.ImplicitCTX().Env, sourceFile, nFile,
fun(x){
def dataSet=DataSet();
dataSet.ReadXmlSchema(x);
dataSet;
});
}
static class Helper
{
public static Generate(env : GlobalEnv, sourceFile : string, nFile : string, loadFunc : string->DataSet) : void
{
//генерируем n-файл только при компиляции
when(!ManagerClass.Instance.IsIntelliSenseMode && Helper.NeedReCreate(sourceFile, nFile))
{
mutable ds=
try
{
loadFunc(sourceFile);
}catch
{
| _e =>
Message.FatalError("Error reading XML schema file $(_e.Message)");
}
Helper.GenerateCodeFile(ds, nFile);
}
Helper.IncludeFile(env, nFile);
}
static NeedReCreate(sourceFile : string, destinationFile : string) : bool
{
match(File.Exists(sourceFile), File.Exists(destinationFile))
{
| (false, _) =>
Message.FatalError("Source file does not exists");
| (true, true) =>
File.GetLastWriteTime(sourceFile)>File.GetLastWriteTime(destinationFile);
| _ => true;
}
}
static GenerateCodeFile(ds : DataSet, nFile : string) : void
{
def ns=CodeNamespace();
def codeGen : ICodeGenerator = NemerleCodeGenerator();
TypedDataSetGenerator.Generate(ds, ns, codeGen);
try
{
using(s=StreamWriter(nFile))
codeGen.GenerateCodeFromNamespace(ns, s, CodeGeneratorOptions());
}catch
{
| _e =>
Message.FatalError("Error writing code file $(_e.Message)");
}
}
static IncludeFile(env : GlobalEnv, nFile : string) : void
{
def compile(m : ClassMember.TypeDeclaration, tb : TypeBuilder = null)
{
match(m.td)
{
| _ is TopDeclaration.Delegate =>
if(tb==null)
_=env.Define(m);
else
tb.Define(m);
| c is TopDeclaration.Class =>
def ntb=if(tb==null)
env.Define(m);
else
tb.DefineNestedType(m);
ntb.Compile();
_=c.decls.ForAll(member => {
| td is ClassMember.TypeDeclaration =>
compile(td, ntb);
true;
| _ => true;
});
| _ => {}
}
}
def lexer=LexerFile(ManagerClass.Instance, nFile);
try
{
def parsed=MainParser.Parse(lexer);
_=parsed.ForAll(x => {
def cd=ClassMember.TypeDeclaration(
Splicable.Name(Name.NameInCurrentColor(x.Name, env)),
Modifiers(),
x);
compile(cd);
true;
});
}finally
{
Location.RemoveFile(nFile);
}
}
}
Воть.
В человечишке все должно быть прекрасненьким: и одёжка, и душенка, и мордочка, и мыслишки.