понедельник, 24 декабря 2007 г.

Совершенный код

Вчера наконец-то доставили заказаную мной две недели назад книгу Стива Макконнелла Совершенный код. Практическое руководство по разработке программного обеспечения. Это более 850 страниц в твёрдой обложке. Судя по отзывам, эту книгу определённо стоит прочитать(и соответственно стоило заказывать =) ). Как только закончу читать Александреску Современное проектирование на С++, примусь за Макконнелла.

вторник, 11 декабря 2007 г.

Интеграция Tcl в программу на C/C++

Книжку по Tcl, про которою я говорил ранее, я дочитал. В ней есть глава, посвяшённая теме моего поста. Использование Tcl оказывается весьма и весьма простым.
Есть три задачи, которые необходимо решать при работе с любым скриптовым языком:


  1. Вызов из основной программы скриптовых функций.

  2. Вызов из под скрипта функций основной программы.

  3. Передача данных между скриптом и основной программой.

В принципе третий пункт вполне можно реализовать через первые два.
В следующем примере создаётся инстанс интерпретатора, создаётся Tcl функция equal, которая реализуется на C и вызывается в интерпретаторе.

#include "tcl.h"

//Хэндл интерпретатора
Tcl_Interp* interp = NULL;

//функция, которая будет вызвана интерпретатором
int EqualCmd(ClientData clientData,
Tcl_Interp* interp, int argc, char** argv)
{
//устанавливаем возвращаемое значение
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(strcmp(argv[1], argv[2]) ? 0 : 1 ) );
return TCL_OK;
}

int main(int argc,char** argv)
{
//создаётся инстанс интерпретатора
interp = Tcl_CreateInterp();
int bResult;
//создаётся Tcl функция equal, которая обрабатывается в ф-ции EqualCmd
Tcl_CreateCommand(interp, "equal", (Tcl_CmdProc*)EqualCmd,
(ClientData*)NULL, (Tcl_CmdDeleteProc*)NULL);
//вызываем функцию equal для значений 10 и 10
if(TCL_OK == Tcl_Eval(interp, "equal 10 10"))
{
Tcl_Obj* pResult = Tcl_GetObjResult(interp);
Tcl_GetBooleanFromObj(interp, pResult, &bResult);
printf("result = %i\n", bResult);
}
//вызываем функцию equal для значений 20 и 10
if(TCL_OK == Tcl_Eval(interp, "equal 20 10"))
{
Tcl_Obj* pResult = Tcl_GetObjResult(interp);
Tcl_GetBooleanFromObj(interp, pResult, &bResult);
printf("result = %i\n", bResult);
}

//удалякм инстанс интерпретатора
Tcl_DeleteInterp(interp);
return 0;
}


Для обмена данными между интерпретатором и программой используются функции Tcl_SetVar и Tcl_GetVar, которые позволяют устанавливать и получать значение переменных. Кроме этих вполне ожидаемых средств в Tcl есть и другая интересная фишка - возможность линковать между собой переменную в C коде и переменную в Tcl скрипте. Когда меняется одна, меняется и другая.

воскресенье, 2 декабря 2007 г.

Парсинг XML

Продолжаю экспериментировать с Ruby и Tcl. И я решил написать на каждом из этих языков парсер xhtml-документов. Задача - преобразовать все xhtml-документы в текущей директории в текстовые документы. Преобразование самое простое - в сущности оно заключается просто в удалении тэгов из документа. На обоих языках задача была выполнена.

Програма на Tcl выглядит следующим образом


package require xml

set skip_data false
set result ""

#колбэк для обработки текста xml-элемента
proc cdata {data args} {
global skip_data
global result
if {$skip_data == false } {
append result $data

}
set skip_data false
}

#колбэк для обработки начала xml-элемента.
#если встречаются тэги <style> или <title>, то мы их пропускаем
proc elem {data attlist args} {
global skip_data
if {$data == "style"} {
set skip_data true
} elseif {$data == "title"} {
set skip_data true
}
}

set l [eval glob *.html]
set b [split $l]
# b - список html-файлов в текущей директории

foreach fn $b {
#считываем содержимое файла
set f [eval open $fn]
set data [eval read $f]
close $f
#создаём парсер и указываем колбэки
set parser [::xml::parser -characterdatacommand cdata -elementstartcommand elem]
$parser parse $data
set result [string trim $result]
set ofn $fn
append ofn {.txt}
#записываем результат в файл
set res_file [open $ofn w]
puts $res_file $result
close $res_file
}

На Ruby получился следующий код

require 'rexml/parsers/PullParser'

$result = ""
#получаем список html-фалов
h_files = Dir.glob("*.html")
#обрабатываем в цикле все эти файлы
for fn in h_files
#считываем данные
f = File.open(fn, "r")
if(f.eof)
p "empty"
return
end
res = f.read
#создаём парсер
lp = REXML::Parsers::PullParser.new(res)
skip_element = 0
while(lp.has_next?)
data = lp.pull
if(skip_element > 0)
skip_element -= 1
next
end
#если попадается ненужный элемент, то пропускаем его
if(data.start_element? && (data[0] == "style" data[0] == "title"))
skip_element = 2
end
if(data.text?)
#а если нужный, то сохраняем его
$result = $result + data[0].to_s
end
end
#записываем результат в файл
res = File.open(fn.to_s + ".txt", "w")
res.write($result.strip)
end


Несмотря на то, что текст програмы на Ruby получился лаконичнее, чем на Tcl, на Ruby вариант у меня ушло значительно больше времени, большую часть из которого я потратил на то, чтобы найти и использовать подходящий xml-парсер. Кроме того результат, который выдаёт Ruby, некорректен - в тексте сохранились значки "&nbsp;". Хотя конечно это недостаток парсера, а не языка... Тем не менее Tcl в данном случае показал себя гораздо лучше. Правда при этом програма на Ruby работает примерно в два раза быстрее.

Сортировка на Ruby

А теперь тоже самое, но на Ruby.

a = [2, 1, 26, 14]
for i in (0..a.length - 1)
      for j in (i..a.length - 1)
            if a[i] > a[j]
                  tmp = [i]
                  a[i] = a[j]
                  a[j] = tmp
            end
      end
end
p a

Естественно в Ruby присутствуют и встроенные средства сортировки массивов
data = data.sort


На написание этой программы на Ruby мне потребовалось меньше времени и услилий, чем на Tcl за счёт более удобного и привычного доступа к элементам массивов в Ruby.

Пузырьковая сортировка на Tcl

Только что написал на Tcl сортироку пузырьком. Выглядит это следующим образом

set a {23 4 3 7 6 10}
puts $a
for { set i 0 } { $i < [llength $a] } { incr i } {
      for { set j $i } { $j < [llength $a] } { incr j } {
            if { [lindex $a $i] > [lindex $a $j]} {
            set tmp [lindex $a $i]
            lset a $i [lindex $a $j]
            lset a $j $tmp
           }
     }
}
puts $a

Естественно того же эффекта можно добиться одной строчкой с
использованием средств языка.
set a [lsort -integer $a]

Просто хотелось написать на этом языке что-нибудь простое. Хотя конечно Tcl предназначен для решения совершенно других задач.