Я очищаю проект C, который имеет один заголовочный файл, содержащий все используемые структуры.
Я хочу извлечь все структуры и поместить их в отдельные файлы с помощью PowerShell Regex.
Я написал следующий скрипт:
$file = Get-Content -Path "./types.h"
Write-Output $file
$structs = Select-String -Pattern "struct\s*([a-zA-Z0-9_]+)\s*{(?:\s*.*\s*)*?}\s*;" -InputObject $file
Write-Output $structs.Matches.Count
foreach($struct in $structs.Matches) {
New-Item "./$($struct.groups[1]).h" -Force -Value $struct.Value
}
У меня есть следующий тестовый файл types.h
:
struct foo {
int a;
};
struct bar{
short i[200];
};
Однако когда я запускаю скрипт, он, кажется, сопоставляет весь файл и записывает его в один файл foo.h
:
struct foo { int a; }; struct bar{ short i[200]; };
Я ожидал бы два файла foo.h
и bar.h
с соответствующими структурами в них.
Мое регулярное выражение должно работать (regex101):
В PowerShell для этого есть парсер (PowerShell) AST, я предполагаю, что C
имеет что-то подобное...
Я предлагаю вам иметь еще пару образцов. Например, struct
внутри struct
, а также union
внутри struct
. Мне любопытно, что произойдет, если у вас есть struct
внутри union
.
Мне не хватало переключателя -AllMatches
.
Я завершил сценарий следующим образом (теперь с поддержкой перечисления):
param (
[string]$SourceFile,
[string]$OutputPath
)
$structs = Get-Content -Path $SourceFile -Raw | Select-String -List -Pattern "(?:struct|enum)\s*([a-zA-Z0-9_]+)\s*{(?:\s*.*?\s*)*?}\s*;" -AllMatches
foreach($struct in $structs.Matches) {
Write-Output $struct.Value
New-Item "$($OutputPath)/$($struct.groups[1].Value.ToLower().Replace('_','')).h" -Force -Value @"
#ifndef $($struct.groups[1].Value.ToUpper())_INCLUDED
#define $($struct.groups[1].Value.ToUpper())_INCLUDED
$($struct.Value)
#endif
"@
}
Это работает для простого примера, который вы представили, но он сломается, как только вы учтете такие вещи, как {
/}
в комментариях, вложенные блоки объединения и т. д.
ты прав. но это подходит для моего варианта использования, и, честно говоря, я никогда не видел скобок {} в комментариях (если только структура/класс не была закомментирована)
Вам не просто не хватало переключателя -AllMatches
, вам также нужно было сопоставить все строки, что требовало от вас передачи всего содержимого файла в виде одной строки в Select-String
через Get-Content -Raw ...
. Обратите внимание, что -List
можно опустить, поскольку он имеет смысл только при передаче нескольких входные строки.
Определение структуры C не определяется регулярной грамматикой, поэтому его нельзя проанализировать с помощью регулярного выражения.
Вы используете .*
в своем регулярном выражении, что позволяет выполнять синтаксический анализ так, как вы его определяете (вы разрешаете, чтобы }
сопровождался {
снова внутри него). Здесь у вас есть две альтернативы, обе из которых могут потерпеть неудачу. Вы можете использовать [^}]*
вместо .*
, что приведет к сбою (поскольку он запрещает анализ вложенных struct
, заканчиваясь на первом найденном }
, вырезая остальные, если это произойдет, у вас есть определение вложенной структуры), но будет правильно анализировать невложенные структуры, Или вы можете создать регулярное выражение (гораздо более сложное), которое допускает максимальное количество определений вложенных структур, но этот контекст не позволяет мне привести пример такого подхода.
контекстно-свободные грамматики невозможно проанализировать с помощью обычного определения языка. Вам следует использовать контекстно-свободный парсер и контекстно-свободную грамматику. Регулярные выражения определяют языки, которые не допускают вложенности структур. Только до ограниченного предела (некоторые разработчики, вероятно, сочтут этот подход допустимым, несмотря на то, что регулярные выражения становятся больше по мере увеличения количества уровней максимальной вложенности структур).
Для более общего решения, использующего парсер CFG, используйте Antlr4 , грамматику C , Trash и pwsh или bash. Недостатком является то, что для установки требуется немного.
$ git clone https://github.com/antlr/grammars-v4.git
Cloning into 'grammars-v4'...
remote: Enumerating objects: 50583, done.
remote: Counting objects: 100% (1872/1872), done.
remote: Compressing objects: 100% (1265/1265), done.
remote: Total 50583 (delta 669), reused 1599 (delta 495), pack-reused 48711
Receiving objects: 100% (50583/50583), 47.49 MiB | 23.16 MiB/s, done.
Resolving deltas: 100% (27090/27090), done.
Updating files: 100% (9413/9413), done.
$ cd grammars-v4/c
$ trgen -t CSharp
CSharp C.g4 success 0.0555443
Rendering template file from CSharp/Other.csproj to ./Generated-CSharp/Other.csproj
Rendering template file from CSharp/st.build.ps1 to ./Generated-CSharp/st.build.ps1
Rendering template file from CSharp/st.build.sh to ./Generated-CSharp/st.build.sh
Rendering template file from CSharp/st.clean.ps1 to ./Generated-CSharp/st.clean.ps1
Rendering template file from CSharp/st.clean.sh to ./Generated-CSharp/st.clean.sh
Rendering template file from CSharp/st.Encodings.cs to ./Generated-CSharp/st.Encodings.cs
Rendering template file from CSharp/st.ErrorListener.cs to ./Generated-CSharp/st.ErrorListener.cs
Rendering template file from CSharp/st.makefile to ./Generated-CSharp/st.makefile
Rendering template file from CSharp/st.perf.sh to ./Generated-CSharp/st.perf.sh
Rendering template file from CSharp/st.ProfilingCommonTokenStream.cs to ./Generated-CSharp/st.ProfilingCommonTokenStream.cs
Rendering template file from CSharp/st.run.ps1 to ./Generated-CSharp/st.run.ps1
Rendering template file from CSharp/st.run.sh to ./Generated-CSharp/st.run.sh
Rendering template file from CSharp/st.test-cover.sh to ./Generated-CSharp/st.test-cover.sh
Rendering template file from CSharp/st.Test.cs to ./Generated-CSharp/st.Test.cs
Rendering template file from CSharp/st.test.ps1 to ./Generated-CSharp/st.test.ps1
Rendering template file from CSharp/st.test.sh to ./Generated-CSharp/st.test.sh
Rendering template file from CSharp/Test.csproj.st to ./Generated-CSharp/Test.csproj.st
Copying source file from C:/msys64/home/Kenne/temp/grammars-v4/c/desc.xml to ./Generated-CSharp/desc.xml
Copying source file from C:/msys64/home/Kenne/temp/grammars-v4/c/C.g4 to ./Generated-CSharp/C.g4
$ cd Generated-CSharp/
$ make
bash build.sh
Determining projects to restore...
Restored C:\msys64\home\Kenne\temp\grammars-v4\c\Generated-CSharp\Test.csproj (in 526 ms).
Determining projects to restore...
All projects are up-to-date for restore.
Test -> C:\msys64\home\Kenne\temp\grammars-v4\c\Generated-CSharp\bin\Debug\net8.0\Test.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:04.56
$ cat extract.sh
#!/bin/bash
g=`trparse types.h | trxgrep ' //structOrUnionSpecifier/Identifier/text()' | tr '\n' ' ' | tr -d '\r'`
for f in $g
do
trparse types.h | trxgrep " //structOrUnionSpecifier[Identifier = '$f']" | trtext > $f.h
done
$ cat types.h
// This is foo.
struct foo {
int a;
};
// This is bar.
struct bar{
short i[200];
};
$ bash extract.sh
CSharp 0 types.h success 0.0539358
CSharp 0 types.h success 0.0550352
CSharp 0 types.h success 0.0597384
$ cat foo.h
// This is foo.
struct foo {
int a;
}
$ cat bar.h
// This is bar.
struct bar{
short i[200];
}
$
Обратите внимание, что сценарий, эквивалентный Powershell:
$g = trparse types.h | trxgrep ' //structOrUnionSpecifier/Identifier/text()' | Out-String
$g = $g -replace "\n", " "
$g = $g -replace "\r", ""
$g.Trim().Split(" ") | ForEach-Object {
$f = $_
trparse types.h | trxgrep " //structOrUnionSpecifier[Identifier = '$f']" | trtext | Out-File "$f.h"
}
Разве так не должно быть
foreach($struct in $structs)
? Однако я тестировал со строковой переменной, а не с файлом, т. е.$structs = [regex]::Matches($types, "struct\s*([a-zA-Z0-9_]+)\s*{(?:.*\s*)*?}\s*;")