parser

Написать ответ на текущее сообщение

 

 
   команды управления поиском

Ответ

Yuriy V. Vorontsov (Че) 25.04.2004 19:39 / 25.04.2004 19:48

Неплохо доработал класс:
1. Сделал анализатор для разбора структура из макросов, короче regexp шаблон, который выдирает конструкции вида:
# коммен-
# тарий
@method[params][locals]
...code ...
Т.е. отельно комментарий, название метода, сам код!
Полезная штука кому надо: копните код и найдёте!
Если будут какие-то до работки regexp’а -- сообщайте!

2. Добавил анализатор статических полей класса
См. Парсер документация, Пользовательские классы / Определение пользовательского класса и операторов

3. Добавил анализатор определённых переменных в методах.

4. Формируется tab-delimited файл с данными о классе:
type	parent	value	comment	code
file		/library/autodocs//autodocs.p			
MyCLASS	not avaliable	MAIN	Расширение базовых функций Parser3		
method	MAIN	@auto[]		\n$CONTENT[^table::create{path}]\n\n\n	
global	MAIN	$CONTENT			
defined	@auto[]	$CONTENT			
method	MAIN	@create[init]	\n Конструктор\n 1....	
global	MAIN	$PATH			
global	MAIN	$MASK			
global	MAIN	$INSIDE			
global	MAIN	$MAKEFILE			
defined	@create[init]	$PATH			
defined	@create[init]	$MASK			
defined	@create[init]	$INSIDE			
defined	@create[init]	$MAKEFILE			
...
Структура файла
type – тип объекта (
file – имя обработанного файла, 
MyCLASS – данные из @CLASS, 
MyUSE – данные из @USE, 
MyBASE – данные из @BASE, 
method – имя макроса (метода),
global – статическое поля класса,
defined – определённая переменная в методе
)
parent – владелец переменной
value – значение объекта (имя класса, метода, переменной или значение поля)
comment – комментарий к данному
code – код метода, только для методов
Замеченные баги:
1. Все макросы объявленные файле становятся макросами объявленного в файле класса. Т.е. в моём исходнике определёны методы @default, @checked, @main, они класса MAIN, но анализатор посчитает их методами класса autodocs.
2. Не анализирует наследование.

Тесты производились на классе Misha.3 auth.p, mysql.p и прошли успешно! ИМХО :)

Это пока всё.


Если есть энтузиаст готовый сделать viewer а ля дока по парсеру... буду очень признателен, благо даже есть отдельный файл, из которого можно читать данные.
Буду рад сотрудничеству!


PS: Из-за внесённых дополнений скрипт стал подтормаживать, что поделать Parser :)
Но для этого и существует кэш файл (правда пока я его тоже не читаю :))



Исходник:
@default[param1;param2]
$result[^if(def $param1){$param1}{$param2}]


@checked[param;value;isDefault]
$result[^if($param eq $value || (!def $param && def $isDefault)){checked}]


@main[]
<title>Самодокументирование парсерного кода</title>
<form method="GET">
	Путь к папке: <input name="library" value="^default[$form:library;/]"><br>
	Просматривать:
		<input type="radio" name="mask" id="a" value="\.p^$" ^checked[$form:mask;\.p^$;x]>
			<label for=a>Библиотеки (.p)</label> /
		<input type="radio" name="mask" id="b" value="\.html^$" ^checked[$form:mask;\.html^$]>
			<label for=b>Файлы (.html)</label> /
		<input type="radio" name="mask" id="c" value="\.(html|p)^$" ^checked[$form:mask;\.(html|p)^$]>
			<label for=c>Библиотеки и файлы (.p и .html)</label>
	<br>
	Просмотривать вложенные папки:
		<input type="radio" name="in" id="1" value="1" ^checked[$form:in;1;x]>
			<label for=1>Да</label> /
		<input type="radio" name="in" id="0" value="0" ^checked[$form:in;0]>
			<label for=0>Нет</label>
	<br>
	Кэш файл формировать?:
		<input type="radio" name="file" id="I" value="1" ^checked[$form:file;1]>
			<label for=I>Да</label> /
		<input type="radio" name="file" id="II" value="0" ^checked[$form:file;0;x]>
			<label for=II>Нет</label>
	<br>
	<input type="hidden" name="run" value="1">
	<input name="do" type="submit">
</form>

^if(def $form:run){
	<HR width="100%">
	$autodocs[^autodocs::create[
		$.path[$form:library]
		$.mask[^untaint[as-is]{$form:mask}]
		$.in(^form:in.int(0))
		$.file(^form:file.int(0))
	]]
	^autodocs.scan[]
}





########################################
@CLASS
autodocs




@auto[]
$CONTENT[^table::create{path}]




# Конструктор
# 1. init = путь к папку где сканировать
# 2. init.path = путь к папку где сканировать
#    init.mask = маска в regexp
#    init.in = (1/0) открывать вложенные папки
@create[init]
$PATH[/]
$MASK[\.p^$]
$INSIDE(0)
$MAKEFILE(0)

^if($init is string){
	$PATH[$init]
}{
	^if($init is hash){
		^if(def $init.path){
			$PATH[$init.path]
		}

		^if(def $init.mask){
			$MASK[$init.mask]
		}

		^if(def $init.in){
			$INSIDE(^init.in.int(1))
		}

		^if(def $init.file){
			$MAKEFILE(^init.file.int(1))
		}
	}{
		^throw[autodocs.create;$init;Входной параметр задан не верно, это может быть string или hash.]
	}
}




# Начать сканирование
@scan[]
^scandir[$PATH]
^if($CONTENT){
	^CONTENT.menu{
		^open[$CONTENT.path]
	}
}
#$result[]




# Рекурсивный просмотр папок
@scandir[folder][folder;list]
$list[^file:list[$folder]] 
^list.menu{
	^if(^list.name.match[$MASK][]){
		^add[$folder/$list.name]
	}

	^if($INSIDE && (-d "$folder/$list.name")){
		^scandir[$folder/$list.name]
	}
}
#$result[]



# Добавить файл в стэк обработки
@add[file]
^CONTENT.append{$file}
#$result[]



# Обрезать ужас моего не знания regexp'а
@cut[string;also]
^if(def $string){
	$string[^string.match[\n$also][g]{}]
	$result[^string.match[\n+^$][g]{}]
}{
	$result[]
}



# Обрезать ужас моего не знания regexp'а и символы комментариев
@change_comment[string]
^if(def $string){
	$result[^string.match[#+][g]{}]
}{
	$result[]
}



@unescape_br[string]
^if(def $string){
	$string[^string.match[^^\s*][g]{}]
	$string[^string.match[\s*^$][g]{}]
	$string[^string.match[\n\n+][g]{<p>}]
	$string[^string.match[\n][g]{<br>}]
	$result[$string]
}{
	$result[]
}




# открыть файл
@open[file][f;HELP;table;GHELP]
^if(-f $file){	
	$f[^file::load[text;$file]]
	$f[$f.text]

	$HELP[^table::create{method	comment	code}]
	$GHELP[^hash::create[]]

	$table[^f.match[
		# Сам текст комментария
			(
				(?:
					(?:^^|\n)\#+
					^[^^\n^]*
				)*
			)?

		# Разделитель между комментарием и методом
			(?:
				\s*
			)?

		# Макрос / Метод / CLASS / BASE / USE
			(
				\n@^[^^\n^]*
			)

		# Код / Содержание
			(
				(?:
					\n^[^^\n^]*
					(?!
						(?:
							(?:
								\n\#+^[^^\n^]*
							)*
						)?

						(?:
							\s+
						)?

						(?:
							\n@^[^^\n^]*
						)
					)
				)*
			)

	][gxs]]
	$f[]

	^table.menu{
		^if(^table.2.match[@(CLASS|BASE|USE)][]){
			$GHELP.[My^cut[$table.2;@]][
				$.value[^cut[$table.3]]
				$.comment[^change_comment[$table.1]]
			]
		}{
			^HELP.append{^cut[$table.2]	^change_comment[$table.1]	$table.3}
		}
	}
	$table[]

	^make[$file;$GHELP;$HELP]

	$HELP[]
	$GHELP[]

#	$result[]
}{
	^throw[autodocs.open;$file;${file}: Такой файл не найден]
}





# Получить локальные переменные
# except имена какие исключить
@getlocals[string;except]
^if(def $string && 
	(def $except && $except is table || !def $except)
){
	$string[^string.match[
		# Сам текст комментария
			(
				(?:
					(?:^^|\n)\#+
					^[^^\n^]*
				)*
			)?
							
		# Объект
			(?:
				# Пока буду брать только вменяемые имена (у egr'а вариант такой ^^^$@^;()#)
					# \n\s*(\^$^[^^\n^]+)
					\n\s*(\^$^[\w\d\-_^]+)^[\^[\^{\(^]
			)
	][gxs]]
	$string[^string.select(!^string.2.match[^^\^$result^$][])]

	$result[^table::create{name	comment}]
	^string.menu{
		^if(!^result.locate[name;$string.2] && 
			(def $except && !^except.locate[name;$string.2] || !def $except)
		){
			^result.append{$string.2	$string.1}
		}
	}	
}{
	$result[^table::create{name	comment}]
}




# Получить параметры макроса, в том числе и локальные переменные, copy paste с egr'а
@getparams[string][table]
^if(def $string){
	$table[^string.match[
		^^\s*
		# Имя метода
			@([^^^$@^;()#]+?)
		# Передаваемые параметры
			\^[
				(.*?)
			\^]
		# Локальные переменные
			(?:
				\^[
					(.*?)
				\^]
			)?
		\s*^$
	][gxs]]

# Параметры и переменные
	$table[^;${table.2}^;${table.3}^;]

	$table[^table.split[^;]]
	$table[^table.select(def $table.piece)]

	$result[^table::create{name}]
	^table.menu{
		^result.append{^$$table.piece}
	}	
}{
	$result[^table::create{name}]
}




##########################
# taint to sql
@sql-taint[string][ws_pack_replace]
$ws_pack_replace[^table::create[nameless]{\	\\
^taint[^#0A]	\n
^taint[^#09]	\t}]

^if(def $string){
	$result[^string.replace[$ws_pack_replace]]
;
	$result[]
}





##########################
# taint to sql
@sql-untaint[string][ws_unpack_replace]
$ws_unpack_replace[^table::create[nameless]{\n	^taint[^#0A]
\t	^taint[^#09]
\\	\}]

^if(def $string){
	$result[^string.replace[$ws_unpack_replace]]
;
	$result[]
}





############################################################
# метод для записи таблицы в файл, с предварительной заменой символов перевод строки и табуляция на \n и \t
# copy paste с Misha.3
@table_save[table;file_name][col;tmp]
$col[^table.columns[]]
$tmp[^col.menu{$col.column}[^#09]^#0A^table.menu{^col.menu{^sql-taint[$table.[$col.column]]^#09}}[^#0A]]
^tmp.save[$file_name]
$result[]








# Сделать структуру
@make[file;GHELP;HELP][container;VARS;PARAMS;GLOBAL;MyCLASS]
$container[^table::create{type	parent	value	comment	code}]
$GLOBAL[^table::create{name	comment	method}]

# $file:
	^container.append{file		$file		}

# GHELP
# конфигурационные данные
	^if(def $GHELP.MyCLASS){
		^container.append{MyCLASS	not avaliable	$GHELP.MyCLASS.value	$GHELP.MyCLASS.value	}
		$MyCLASS[$GHELP.MyCLASS.value]
	}{
		^container.append{MyCLASS	not avaliable	MAIN	Расширение базовых функций Parser3	}
		$MyCLASS[MAIN]
	}

	^if(def $GHELP.MyBASE){
		^container.append{MyBASE	$MyCLASS	$GHELP.MyBASE.value	$GHELP.MyBASE.comment	}
	}

	^if(def $GHELP.MyUSE){
		^container.append{MyUSE	$MyCLASS	$GHELP.MyUSE.value	$GHELP.MyUSE.comment	}
	}


# HELP
	^if($HELP){
		^HELP.menu{
			^container.append{method	$MyCLASS	$HELP.method	$HELP.comment	$HELP.code}
			^if(def $HELP.code){
# Переменные метода
				$VARS[^getlocals[$HELP.code;^getparams[$HELP.method]]]

				^if($VARS){
					^VARS.menu{
						^if(!^GLOBAL.locate[name;$VARS.name]){
							^GLOBAL.append{$VARS.name	$VARS.comment	$HELP.method}
							^container.append{global	$MyCLASS	$VARS.name	$VARS.comment	}
						}
					}
				}


				$VARS[^getlocals[$HELP.code]]

				^if($VARS){
					^VARS.menu{
						^container.append{defined	$HELP.method	$VARS.name	$VARS.comment	}
					}
				}
			}
		}
	}

# Сделать файл?
	^if($MAKEFILE){
		^table_save[$container;^file.match[[/\.]][g]{_}.cfg]
	}

$result[^show[$file;$GHELP;$HELP;$GLOBAL]]






@show[file;GHELP;HELP;GLOBAL][VARS]
$result[
	^if(def $GHELP.MyCLASS){
		<H1>Класс: $GHELP.MyCLASS.value</H1>

		^if(def $GHELP.MyCLASS.comment){
			<h4><ul>^unescape_br[$GHELP.MyCLASS.comment]</ul></h4>
		}
	}{
		<H1>Класс: MAIN</H1>
		<h3>Расширение базовых функций Parser3</h3>
	}

	<H2>Файл $file</H2>
	
	^if($GLOBAL){
		<H2>Статические поля класса:</H2>
		<ol>
			^GLOBAL.menu{
				<li>
					<strong>$GLOBAL.name</strong> (определяется в <a href="#$GLOBAL.method">$GLOBAL.method</a>): 
					^if(def $GLOBAL.comment){
						<br> <i>^change_comment[$GLOBAL.comment]</i>
					}
				</li>
			}[<p>]
		</ol>
	}

	^if($HELP){
		<table border=1>
		^HELP.menu{
			<tr>
				<td align="left" valign="top" width="10%">
					<a name="$HELP.method"/>
					<pre>$HELP.method</pre>
					<ul>^unescape_br[$HELP.comment]</ul>

#					<p>^unescape_br[$HELP.code]

					$VARS[^getlocals[$HELP.code]]

					^if($VARS){
						<p>Объявленные в методе переменные:
						<ol>
							^VARS.menu{
								<li>
									<strong>$VARS.name</strong>: 
									^if(def $VARS.comment){
										<br> <i>^change_comment[$VARS.comment]</i>
									}								
								</li>
							}[<p>]
						</ol>
					}
				</td>
			</tr>
		}
		</table>
	}

	<HR width=100%>
]