Здравствуйте, eao197, Вы писали:
Я вот тоже решил попробовать решить задачку импиративно на Шарпе. Причем в отличии от твоего примера я все же создал декларативное описание шаблона. Выносить шаблоны и модель в файл было влом, так что в целях сравнения мысленно отбрасывай их
![](/Forum/Images/smile.gif)
.
using System;
using System.Text;
using System.Xml;
class Program
{
// Модель для преобразования.
const string _model =
@"<objects>
<object name='Obj1'>
<property name='prop1' type='int'/>
<property name='prop2' type='string'/>
</object>
<object name='AnotherObject'>
</object>
<object name='YetAnotherObject'>
<property name='a' type='int'/>
<property name='b' type='string'/>
<property name='c' type='AnotherObject'/>
</object>
</objects>";
const string _templates =
@"<Templates>
<Class>
public class #ClassName#
{
public Obj1(#CtorParams#)
{
#CtorInits#
}
#Properties#
}
</Class>
<Properties>
private #type# _#fieldName#;
public #type# #PropName#
{
get { return _#fieldName#; }
}
</Properties>
<CtorParams>#type# #fieldName#</CtorParams>
<CtorInits>_#fieldName# = #fieldName#;</CtorInits>
</Templates>";
static void Main()
{
Console.WriteLine(MakeCode());
}
static XmlDocument _pemplateDoc = new XmlDocument();
static string MakeCode()
{
_pemplateDoc.LoadXml(_templates);
XmlDocument doc = new XmlDocument();
doc.LoadXml(_model);
string res = "";
// Перебираем описание класов из файла модели...
foreach (XmlNode @class in doc.SelectNodes(@"//object"))
{
// Заменяем паттерны на шаблоны попутно раскрывая их.
string str = GetTemplate("#Class#").Replace("#ClassName#", @class.Attributes["name"].Value);
ReplacePatterns(ref str, "#CtorParams#", @class, ", ");
ReplacePatterns(ref str, "#CtorInits#", @class, ";\r\n ");
ReplacePatterns(ref str, "#Properties#", @class, "\r\n");
res += str;
}
return res;
}
// Загружает шаблон и раскрвает его для каждого описания свойства.
private static void ReplacePatterns(ref string source,
string templateName, XmlNode @class, string separator)
{
StringBuilder str = new StringBuilder();
string template = GetTemplate(templateName);
foreach (XmlNode prop in @class.SelectNodes("property"))
{
str.Append(separator);
str.Append(template.Replace("#type#", prop.Attributes["type"].Value)
.Replace("#fieldName#", prop.Attributes["name"].Value)
.Replace("#PropName#", Capitalize(prop.Attributes["name"].Value)));
}
if (str.Length > 0)
str.Remove(0, separator.Length);
source = source.Replace(templateName, str.ToString());
}
// Загружает шаблон кода по его имени. Если шаблона нет кидает исключение.
static string GetTemplate(string templateName)
{
templateName = "//" + templateName.Replace("#", "");
string template = _pemplateDoc.SelectSingleNode(templateName).InnerText;
if (template == null)
throw new Exception("The template '" + templateName + "' not exists.");
return template;
}
// Переводит первую букву в строке в верхний регистр.
static string Capitalize(string text)
{
char[] array = text.ToCharArray();
array[0] = char.ToUpper(array[0]);
return new String(array);
}
}
Как видишь, хотя мое решение более универсальное оно на на много длинее твоего. Результат аналогичен твоему, так что я его не привожу вовсе. Причем мой код намного более просто расширяем. Чтобы поправить шаблон вообще ничего менять не нужно.
Кстати, код можно автоматизировать если немного его дароботать. Так можно анализировать шаблон на наличие в нем нераскрытых паттернов и пытаться их раскрыть автоматически. Информацию о повторении можно задать как-то в атрибутах шаблона.
В общем, если еще часа два подумать, то может получиться вполне себе работоспособное решение с очень простым декларативным синтаксисом.
Например в качестве имен паттернов можно попробовать использовать XPath. Однако это все ближе и ближе к XSLT.
![](/Forum/Images/biggrin.gif)
... << RSDN@Home 1.2.0 alpha rev. 575>>
Здравствуйте, Andre, Вы писали:
A>Это в принципе можно сократить до:
A>A>CultureInfo.CurrentCulture.TextInfo.ToTitleCase(string text)
A>
Глянул ее реализацию... и понял, что в МС платят за объем кода
![](/Forum/Images/wow.gif)
:
public unsafe string ToTitleCase(string str)
{
if (str == null)
{
throw new ArgumentNullException("str");
}
if (this.m_cultureTableRecord.IsSynthetic)
{
if (this.ANSICodePage == 0x4e6)
{
return CultureInfo.GetCultureInfo("tr-TR").TextInfo.ToTitleCase(str);
}
return CultureInfo.GetCultureInfo("en-US").TextInfo.ToTitleCase(str);
}
int num1 = str.Length;
if (num1 == 0)
{
return str;
}
StringBuilder builder1 = new StringBuilder();
string text1 = null;
for (int num2 = 0; num2 < num1; num2++)
{
int num3;
UnicodeCategory category1 = CharUnicodeInfo.InternalGetUnicodeCategory(str, num2, out num3);
if (char.CheckLetter(category1))
{
if (num3 == 1)
{
builder1.Append(TextInfo.nativeGetTitleCaseChar(this.m_pNativeTextInfo, str[num2]));
}
else
{
char ch1;
char ch2;
this.ChangeCaseSurrogate(str[num2], str[num2 + 1], out ch1, out ch2, true);
builder1.Append(ch1);
builder1.Append(ch2);
}
num2 += num3;
int num4 = num2;
bool flag1 = category1 == UnicodeCategory.LowercaseLetter;
while (num2 < num1)
{
category1 = CharUnicodeInfo.InternalGetUnicodeCategory(str, num2, out num3);
if (this.IsLetterCategory(category1))
{
if (category1 == UnicodeCategory.LowercaseLetter)
{
flag1 = true;
}
num2 += num3;
continue;
}
if (str[num2] == '\'')
{
num2++;
if (flag1)
{
if (text1 == null)
{
text1 = this.ToLower(str);
}
builder1.Append(text1, num4, (int) (num2 - num4));
}
else
{
builder1.Append(str, num4, (int) (num2 - num4));
}
num4 = num2;
flag1 = true;
continue;
}
if (this.IsWordSeparator(category1))
{
break;
}
num2 += num3;
}
int num5 = num2 - num4;
if (num5 > 0)
{
if (flag1)
{
if (text1 == null)
{
text1 = this.ToLower(str);
}
builder1.Append(text1, num4, num5);
}
else
{
builder1.Append(str, num4, num5);
}
}
if (num2 < num1)
{
if (num3 == 1)
{
builder1.Append(str[num2]);
}
else
{
builder1.Append(str[num2++]);
builder1.Append(str[num2]);
}
}
}
else if (num3 == 1)
{
builder1.Append(str[num2]);
}
else
{
builder1.Append(str[num2++]);
builder1.Append(str[num2]);
}
}
return builder1.ToString();
}
Да и это немного не то. Это подем регистра у первых букв каждого слова.
... << RSDN@Home 1.2.0 alpha rev. 577>>
ANS>Здравствуйте, AndrewVK, Вы писали:
AVK>>Вопросы:
AVK>>1) Валидация исходной схемы?
AVK>>2) Собственно генерирующий код?
AVK>>3) Возможность генерации нескольких разных исходников по одной модели?
Всё дело проверялось на LispWorks 4.4 Personal Ed. for Win.
Перед кодом краткое пояснение:
(format t "..." ...) — выводит строку в поток (t — на стандартный вывод). Формат "~&" — новая строка; "~t" — табуляция; "~A" подставляет значение соответсвующего параметра.
(defun gen-constructor (name proplist)
(format t "~&~tpublic ~A " name)
;вывод через з.п.т параметров конструктора
;может его можно записать короче, но я не знаю как ;)
(format t "(~A)" (reduce #'(lambda (a b) (concatenate 'string a ", " b))
(loop for e in proplist
if e collect (concatenate 'string (first e) " " (second e)))
)
)
(format t "~&~t{")
;вывод инициализации переменных
(dolist (i proplist) (format t "~&~t~t_~A = ~A;" (second i) (second i)))
(format t "~&~t}")
)
(defmacro object (name &body body)
`(progn
(format t "~&class ~A {" ,name)
;(eval e) выполняет все вложенные определения свойств
;и собирает список пар (тип имя-свойства) по которым и генерируется конструктор
;если тут будет не только property, а, например, еще и constant
;то тот метод должен вернуть пустой список.
;при генерации конструктора эти пустые значения будут отброшены
(gen-constructor ,name (loop for e in '(,@body) collect (eval e)))
(format t "~&}~%")
)
)
(defmacro property (&key name type)
`(progn
(format t "~&~tprivate ~A _~A;" ,type ,name)
(format t "~&~tpublic ~A ~A {" ,type (string-capitalize ,name) )
(format t "~&~t~tget { return _~A; }" ,name)
(format t "~&~t}")
;выводит в поток определения свойств, и возвращает пару (тип имя-свойства)
;для генерации конструктора
(list ,type ,name)
)
)
Итого:
gen-constructor — 8 строчек, из них 3 это просто вывод текста; [/*]
object — 4 строчки, из них 2 — просто вывод;[/*]
property — 6 строчек, из них 4 — просто вывод.[/*]
Самая сложная часть — вывод параметров конструктора через з.п.т. Меняя эти три функции получаем возможность генерировать разные исходники.
В качестве исходного дерева возьмём:
(object "Test"
(property :name "prop1" :type "int")
(property :name "prop2" :type "string")
)
Результат:
class Test {
private int _prop1;
public int Prop1 {
get { return _prop1; }
}
private string _prop2;
public string Prop2 {
get { return _prop2; }
}
public Test (int prop1, string prop2)
{
_prop1 = prop1;
_prop2 = prop2;
}
}
Так же, если, постоянно заглядывая в доку, я и смог написать этот фрагмент, то на валидацию
меня уже не хватает. Идея, как реализовать валидацию, у меня такая: завести динамическую переменную, в которой будет хранится список допустимых "тегов". Методы object, property проверяют валидность своих параметров, и, заглядывая в переменную определяют, могут ли они находится на этом уровне вложенности. Это должно получится еще короче, чем методы генерирующие код.
Интересный вопрос: нафига тут валидация вообще? Всё равно, истина в последней
инстанции — это компилятор. С валидацией у тебя есть куча мест, где можно ошибится:
код генерирующий исходный описатель это раз, схема данных это два, генератор это три.
Какой прок от валидации в данном случае?
А теперь пару критических слов о Common Lisp после написания на нём этой программки.
Во-первых, если вас спросят, есть ли что-то общее между Perl и CL, смело отвечайте — есть. "Птичий" язык в CL не уступает перловому: В вышеприведённом коде употребляются модификаторы из прямого апострофа, диеза-и-апострофа, обратного апострофа, запятой, запятой-и-собаки, двоеточия, амперсанда. Есть еще правила их использования внутри "квоченного" текста.
Именование и количество методов — это песня. Если CONS, CAR, CDR — еще запоминается без проблем, то, например, MAP, MAPC, MAPCAR, MAPCAN, MAPL, MAPLIST, MAPCON — и каждая со своими вариантами поведения — это уже перебор. Вот набор повеселее: CAR, CDR, CAAR, CADR, CDAR, CDDR, CAAAR, CAADR, CADAR, CADDR, CDAAR, CDADR, CDDAR, CDDDR, CAAAAR, CAAADR, CAADAR, CAADDR, CADAAR, CADADR, CADDAR, CADDDR, CDAAAR, CDAADR, CDADAR, CDADDR, CDDAAR, CDDADR, CDDDAR, CDDDDR. Разбираться с этим в программе? Я пас.
Неконсистентность. Например:
EVERY — первый параметр это предикат, второй — список.
APPLY — первый параметр — функция, второй — неограниченной число аргументов. (Внутри функции — и то и то — список, а вот с вызывающей стороны выглядит по другому. После smalltalk-овского единообразия это удивляет.)
MAP — первый параметр — тип результата, второй — функция, третий — неограниченное число списков.
DOLIST — первый параметр — пара(!) из имени переменной и обрабатываемого списка, потом тело метода.
DO — первый параметр — список типа (имя-переменной инициализатор-переменной имя-пер...).
LOOP — это вообще не LISP (это демонстрация использования не-лиспа в лиспе)! Пример:
(loop for i in '(1
324 2345 323 2 4 235 252)
when (oddp i)
do (print i)
and collect i into odd-numbers
and do (terpri)
else
collect i into even-numbers
finally
(return (values odd-numbers even-numbers)))
Там к LOOP идёт нехилое руководство.
и еще под конец — PROGV — первый параметр — список имён локальных переменных (может вычислятся в ран-тайме), второй — список начальных значений для переменных. Сравните это с "DO"
Короче, пилите, Шура, они зол... Э... В смысле, это не возможно понять это нужно запомнить!
По непонятной мне причине функции map, concatenate, merge требуют задать тип результата. Например: (concatenate 'string a ", " b)
Дальше: логика, по которой (lambda (x) (* x x)) — это не замыкание а _имя_ замыкания мне кажется несколько инородной
![](/Forum/Images/smile.gif)
Само замыкание достаётся по '#(lambda (x) (* x x))
Ну, и на последок: лисповая подсветка в фаре глючит
В защиту нужно сказать, что в CL есть вроде все(!) возможности из других языков. Лексические, динамические переменный, продолжаемые исключения, возвращение множества результатов с удобным доступом к ним, ключевые параметры, необязательные параметры, неограниченное число параметров, макры, поддержка ООП, мультиметоды, всякие :before, :after и пр. хуки, поддержка функционального программирования, патерн-матчинг, продолжения, замыкания, поддержка GOTO (через tagbody и go), бактрекинг. Что я забыл?
И в конце концов, как по мне, то грехемовская "On Lisp" — неплохая книга.
... << RSDN@Home 1.1.4 stable rev. 510>>