Makefile · 12114 bytes Raw Blame History
1 # FERP Makefile
2 # Fortran Expression Regular Print - A GNU grep clone
3
4 # Compilers
5 FC = gfortran
6 CC = clang
7
8 # Compiler flags
9 FFLAGS_COMMON = -std=f2008 -Wall -Wextra -pedantic -cpp
10 FFLAGS_DEBUG = $(FFLAGS_COMMON) -g -O0 -fcheck=all -fbacktrace -Wno-unused-dummy-argument
11 FFLAGS_RELEASE = $(FFLAGS_COMMON) -O2 -march=native -fopenmp
12
13 CFLAGS_DEBUG = -g -O0 -Wall
14 CFLAGS_RELEASE = -O2 -march=native
15
16 # Default to debug build (release includes OpenMP)
17 FFLAGS = $(FFLAGS_DEBUG)
18 CFLAGS = $(CFLAGS_DEBUG)
19
20 # PCRE2 library (required for -P option)
21 # Use pkg-config if available, otherwise fall back to defaults
22 PCRE2_CFLAGS := $(shell pkg-config --cflags libpcre2-8 2>/dev/null)
23 PCRE2_LIBS := $(shell pkg-config --libs libpcre2-8 2>/dev/null || echo "-lpcre2-8")
24 LDFLAGS = $(PCRE2_LIBS)
25
26 # Directories
27 SRC_DIR = src
28 REGEX_DIR = src/regex
29 BUILD_DIR = build
30 BIN_DIR = .
31
32 # Target binary
33 TARGET = $(BIN_DIR)/ferp
34
35 # Regex source files (in dependency order)
36 REGEX_SRCS = $(REGEX_DIR)/regex_charclass.f90 \
37 $(REGEX_DIR)/regex_types.f90 \
38 $(REGEX_DIR)/regex_lexer.f90 \
39 $(REGEX_DIR)/regex_parser.f90 \
40 $(REGEX_DIR)/regex_nfa.f90 \
41 $(REGEX_DIR)/regex_engine.f90 \
42 $(REGEX_DIR)/aho_corasick.f90 \
43 $(REGEX_DIR)/regex_optimizer.f90 \
44 $(REGEX_DIR)/regex_api.f90 \
45 $(REGEX_DIR)/pcre_api.f90
46
47 # C source files (SIMD support)
48 C_SRCS = $(SRC_DIR)/simd_scan.c
49
50 # Main source files (in dependency order)
51 MAIN_SRCS = $(SRC_DIR)/ferp_kinds.f90 \
52 $(SRC_DIR)/ferp_options.f90 \
53 $(SRC_DIR)/ferp_mmap.f90 \
54 $(SRC_DIR)/ferp_simd.f90 \
55 $(SRC_DIR)/ferp_io.f90 \
56 $(SRC_DIR)/ferp_output.f90 \
57 $(SRC_DIR)/ferp_dir.f90 \
58 $(SRC_DIR)/ferp_search.f90 \
59 $(SRC_DIR)/ferp_cli.f90 \
60 $(SRC_DIR)/ferp_matcher.f90 \
61 $(SRC_DIR)/main.f90
62
63 # All source files
64 SRCS = $(REGEX_SRCS) $(MAIN_SRCS)
65
66 # Object files
67 REGEX_OBJS = $(patsubst $(REGEX_DIR)/%.f90,$(BUILD_DIR)/%.o,$(REGEX_SRCS))
68 MAIN_OBJS = $(patsubst $(SRC_DIR)/%.f90,$(BUILD_DIR)/%.o,$(MAIN_SRCS))
69 C_OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(C_SRCS))
70 OBJS = $(REGEX_OBJS) $(MAIN_OBJS) $(C_OBJS)
71
72 # Default target
73 all: $(TARGET)
74
75 # Debug build
76 debug: FFLAGS = $(FFLAGS_DEBUG)
77 debug: CFLAGS = $(CFLAGS_DEBUG)
78 debug: clean $(TARGET)
79
80 # Release build
81 release: FFLAGS = $(FFLAGS_RELEASE)
82 release: CFLAGS = $(CFLAGS_RELEASE)
83 release: clean $(TARGET)
84
85 # Create build directory
86 $(BUILD_DIR):
87 mkdir -p $(BUILD_DIR)
88
89 # Link target
90 $(TARGET): $(BUILD_DIR) $(OBJS)
91 $(FC) $(FFLAGS) -o $@ $(OBJS) $(LDFLAGS)
92
93 # Compile regex source files
94 $(BUILD_DIR)/%.o: $(REGEX_DIR)/%.f90 | $(BUILD_DIR)
95 $(FC) $(FFLAGS) -J$(BUILD_DIR) -I$(BUILD_DIR) -c $< -o $@
96
97 # Compile main source files
98 $(BUILD_DIR)/%.o: $(SRC_DIR)/%.f90 | $(BUILD_DIR)
99 $(FC) $(FFLAGS) -J$(BUILD_DIR) -I$(BUILD_DIR) -c $< -o $@
100
101 # Compile C source files
102 $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
103 $(CC) $(CFLAGS) -c $< -o $@
104
105 # Regex module dependencies (note: some depend on ferp_kinds for pattern_len function)
106 $(BUILD_DIR)/regex_charclass.o:
107 $(BUILD_DIR)/regex_types.o: $(BUILD_DIR)/regex_charclass.o
108 $(BUILD_DIR)/regex_lexer.o: $(BUILD_DIR)/regex_types.o $(BUILD_DIR)/ferp_kinds.o
109 $(BUILD_DIR)/regex_parser.o: $(BUILD_DIR)/regex_types.o
110 $(BUILD_DIR)/regex_nfa.o: $(BUILD_DIR)/regex_types.o $(BUILD_DIR)/regex_charclass.o $(BUILD_DIR)/regex_parser.o
111 $(BUILD_DIR)/regex_engine.o: $(BUILD_DIR)/regex_types.o
112 $(BUILD_DIR)/aho_corasick.o: $(BUILD_DIR)/ferp_kinds.o
113 $(BUILD_DIR)/regex_optimizer.o: $(BUILD_DIR)/regex_types.o $(BUILD_DIR)/regex_charclass.o $(BUILD_DIR)/aho_corasick.o $(BUILD_DIR)/ferp_kinds.o
114 $(BUILD_DIR)/regex_api.o: $(BUILD_DIR)/regex_types.o $(BUILD_DIR)/regex_lexer.o $(BUILD_DIR)/regex_parser.o $(BUILD_DIR)/regex_nfa.o $(BUILD_DIR)/regex_engine.o $(BUILD_DIR)/regex_optimizer.o $(BUILD_DIR)/ferp_kinds.o
115 $(BUILD_DIR)/pcre_api.o: $(BUILD_DIR)/ferp_kinds.o
116
117 # Main module dependencies
118 $(BUILD_DIR)/ferp_options.o: $(BUILD_DIR)/ferp_kinds.o
119 $(BUILD_DIR)/ferp_simd.o: $(BUILD_DIR)/simd_scan.o
120 $(BUILD_DIR)/ferp_mmap.o: $(BUILD_DIR)/ferp_kinds.o $(BUILD_DIR)/ferp_simd.o
121 $(BUILD_DIR)/ferp_io.o: $(BUILD_DIR)/ferp_kinds.o $(BUILD_DIR)/ferp_mmap.o
122 $(BUILD_DIR)/ferp_output.o: $(BUILD_DIR)/ferp_kinds.o $(BUILD_DIR)/ferp_options.o
123 $(BUILD_DIR)/ferp_dir.o: $(BUILD_DIR)/ferp_kinds.o
124 $(BUILD_DIR)/ferp_search.o: $(BUILD_DIR)/ferp_kinds.o
125 $(BUILD_DIR)/ferp_cli.o: $(BUILD_DIR)/ferp_kinds.o $(BUILD_DIR)/ferp_options.o
126 $(BUILD_DIR)/ferp_matcher.o: $(BUILD_DIR)/ferp_kinds.o $(BUILD_DIR)/ferp_options.o $(BUILD_DIR)/ferp_io.o $(BUILD_DIR)/ferp_output.o $(BUILD_DIR)/ferp_search.o $(BUILD_DIR)/regex_api.o $(BUILD_DIR)/pcre_api.o
127 $(BUILD_DIR)/main.o: $(BUILD_DIR)/ferp_kinds.o $(BUILD_DIR)/ferp_options.o $(BUILD_DIR)/ferp_cli.o $(BUILD_DIR)/ferp_io.o $(BUILD_DIR)/ferp_dir.o $(BUILD_DIR)/ferp_matcher.o
128
129 # Clean build artifacts
130 clean:
131 rm -rf $(BUILD_DIR)
132 rm -f $(TARGET)
133
134 # Install (optional)
135 install: release
136 cp $(TARGET) /usr/local/bin/
137 ln -sf /usr/local/bin/ferp /usr/local/bin/frep
138
139 # Uninstall
140 uninstall:
141 rm -f /usr/local/bin/ferp
142 rm -f /usr/local/bin/frep
143
144 # Run tests
145 test: $(TARGET)
146 @echo "=== Basic matching tests ==="
147 @echo "hello world" | ./ferp "hello" && echo "PASS: literal match"
148 @echo "hello world" | ./ferp "goodbye" || echo "PASS: no match (exit 1)"
149 @echo "hello world" | ./ferp -i "HELLO" && echo "PASS: case insensitive"
150 @echo "hello world" | ./ferp -v "goodbye" && echo "PASS: invert match"
151 @echo "=== BRE metacharacter tests ==="
152 @echo "hello world" | ./ferp "hel.o" && echo "PASS: dot metachar"
153 @echo "helllo" | ./ferp "hel*o" && echo "PASS: star quantifier"
154 @echo "heo" | ./ferp "hel*o" && echo "PASS: star zero matches"
155 @echo "hello" | ./ferp "^hello" && echo "PASS: start anchor"
156 @echo "hello" | ./ferp "hello$$" && echo "PASS: end anchor"
157 @echo "hello" | ./ferp "^hello$$" && echo "PASS: both anchors"
158 @echo "=== Character class tests ==="
159 @echo "abc" | ./ferp "[abc]" && echo "PASS: char class"
160 @echo "abc" | ./ferp "[a-z]" && echo "PASS: char range"
161 @echo "ABC" | ./ferp "[A-Z]" && echo "PASS: uppercase range"
162 @echo "123" | ./ferp "[0-9]" && echo "PASS: digit range"
163 @echo "!" | ./ferp "[^a-z]" && echo "PASS: negated class"
164 @echo "]test" | ./ferp "[]a-z]" && echo "PASS: ] at class start"
165 @echo "-test" | ./ferp "[-az]" && echo "PASS: - at class start"
166 @echo "test-" | ./ferp "[az-]" && echo "PASS: - at class end"
167 @echo "=== POSIX class tests ==="
168 @echo "abc" | ./ferp "[[:alpha:]]" && echo "PASS: [:alpha:]"
169 @echo "123" | ./ferp "[[:digit:]]" && echo "PASS: [:digit:]"
170 @echo "abc123" | ./ferp "[[:alnum:]]" && echo "PASS: [:alnum:]"
171 @echo " " | ./ferp "[[:space:]]" && echo "PASS: [:space:]"
172 @echo "=== BRE grouping and quantifier tests ==="
173 @echo "abab" | ./ferp '\(ab\)*' && echo "PASS: BRE group with star"
174 @echo "aaa" | ./ferp 'a\{2,3\}' && echo "PASS: BRE bounded {2,3}"
175 @echo "aa" | ./ferp 'a\{2\}' && echo "PASS: BRE exact {2}"
176 @echo "aaaa" | ./ferp 'a\{2,\}' && echo "PASS: BRE minimum {2,}"
177 @echo "=== Word boundary tests ==="
178 @echo "hello world" | ./ferp '\<hello' && echo "PASS: word start \\<"
179 @echo "hello world" | ./ferp 'world\>' && echo "PASS: word end \\>"
180 @echo "hello world" | ./ferp '\<world\>' && echo "PASS: word boundaries"
181 @echo "=== ERE tests ==="
182 @echo "hello" | ./ferp -E "hel+" && echo "PASS: ERE plus"
183 @echo "helo" | ./ferp -E "hel?o" && echo "PASS: ERE question"
184 @echo "heo" | ./ferp -E "hel?o" && echo "PASS: ERE question zero"
185 @echo "cat" | ./ferp -E "cat|dog" && echo "PASS: ERE alternation"
186 @echo "dog" | ./ferp -E "cat|dog" && echo "PASS: ERE alternation 2"
187 @echo "ab" | ./ferp -E "(ab)+" && echo "PASS: ERE group with plus"
188 @echo "abab" | ./ferp -E "(ab){2}" && echo "PASS: ERE bounded {2}"
189 @echo "aaa" | ./ferp -E "a{2,3}" && echo "PASS: ERE bounded {2,3}"
190 @echo "=== Output option tests ==="
191 @echo "hello world" | ./ferp -o "wor" | grep -q "^wor$$" && echo "PASS: -o only matching"
192 @echo "hello world hello" | ./ferp -o "hello" | wc -l | grep -q "2" && echo "PASS: -o multiple matches"
193 @printf "hello\nworld\n" | ./ferp -b "world" | grep -q "^6:" && echo "PASS: -b byte offset"
194 @echo "=== Context line tests ==="
195 @printf "a\nb\nmatch\nd\ne\n" | ./ferp -A 2 "match" | wc -l | grep -q "3" && echo "PASS: -A after context"
196 @printf "a\nb\nmatch\nd\ne\n" | ./ferp -B 2 "match" | wc -l | grep -q "3" && echo "PASS: -B before context"
197 @printf "a\nb\nmatch\nd\ne\n" | ./ferp -C 1 "match" | wc -l | grep -q "3" && echo "PASS: -C both context"
198 @printf "a\nb\nmatch\nd\ne\n" | ./ferp -2 "match" | wc -l | grep -q "5" && echo "PASS: -NUM context shorthand"
199 @echo "=== Edge case tests ==="
200 @echo "" | ./ferp "" && echo "PASS: empty pattern on empty"
201 @echo "hello" | ./ferp "" && echo "PASS: empty pattern matches"
202 @echo "a+b" | ./ferp "a+b" && echo "PASS: BRE + is literal"
203 @echo "a|b" | ./ferp "a|b" && echo "PASS: BRE | is literal"
204 @echo "=== Recursive search tests ==="
205 @./ferp -r "module" src/ | grep -q "ferp_kinds" && echo "PASS: -r recursive search"
206 @./ferp -r "module" src/ | grep -q "regex_types" && echo "PASS: -r searches subdirs"
207 @./ferp -r --include="*.f90" "module" src/ | grep -q "ferp_kinds" && echo "PASS: --include filter"
208 @./ferp -r --exclude="*output*" "module" src/ | grep -qv "ferp_output" && echo "PASS: --exclude filter"
209 @./ferp -r --exclude-dir="regex" "module" src/ | grep -qv "regex_types" && echo "PASS: --exclude-dir filter"
210 @echo "=== Binary file tests ==="
211 @printf 'hello\x00world\n' > /tmp/ferp_binary_test.txt
212 @./ferp "hello" /tmp/ferp_binary_test.txt | grep -q "Binary file" && echo "PASS: binary file detection"
213 @./ferp -a "hello" /tmp/ferp_binary_test.txt | grep -q "hello" && echo "PASS: -a treats binary as text"
214 @./ferp -I "hello" /tmp/ferp_binary_test.txt; test $$? -eq 1 && echo "PASS: -I skips binary"
215 @rm -f /tmp/ferp_binary_test.txt
216 @echo "=== Color mode tests ==="
217 @./ferp --color=always "module" src/ferp_kinds.f90 | grep -q '\[01;31m' && echo "PASS: --color=always outputs ANSI"
218 @./ferp --color=never "module" src/ferp_kinds.f90 | grep -qv '\[01;31m' && echo "PASS: --color=never no ANSI"
219 @./ferp --color=auto "module" src/ferp_kinds.f90 | grep -qv '\[01;31m' && echo "PASS: --color=auto no ANSI when piped"
220 @echo "=== PCRE tests ==="
221 @echo "hello world" | ./ferp -P "hello" | grep -q "hello" && echo "PASS: -P basic match"
222 @echo "foobar" | ./ferp -P 'foo(?=bar)' | grep -q "foobar" && echo "PASS: -P positive lookahead"
223 @echo "foobaz" | ./ferp -P 'foo(?!bar)' | grep -q "foobaz" && echo "PASS: -P negative lookahead"
224 @echo "foobar" | ./ferp -P '(?<=foo)bar' | grep -q "foobar" && echo "PASS: -P positive lookbehind"
225 @echo "bazbar" | ./ferp -P '(?<!foo)bar' | grep -q "bazbar" && echo "PASS: -P negative lookbehind"
226 @echo "abcabc" | ./ferp -P '(?:abc)+' | grep -q "abcabc" && echo "PASS: -P non-capturing group"
227 @echo "test123" | ./ferp -P '\d+' | grep -q "test123" && echo "PASS: -P digit class"
228 @echo "test123more456" | ./ferp -P -o '\d+' | head -1 | grep -q "123" && echo "PASS: -P -o only matching"
229 @echo "HELLO" | ./ferp -P -i 'hello' | grep -q "HELLO" && echo "PASS: -P -i case insensitive"
230 @echo "Hello123" | ./ferp -P -o '\p{L}+' | grep -q "Hello" && echo "PASS: -P Unicode \\p{L} letter class"
231 @echo "Hello123" | ./ferp -P -o '\p{N}+' | grep -q "123" && echo "PASS: -P Unicode \\p{N} number class"
232 @echo "café" | ./ferp -P '\p{L}+' | grep -q "café" && echo "PASS: -P Unicode accented chars"
233 @echo "CAFÉ" | ./ferp -P -i 'café' | grep -q "CAFÉ" && echo "PASS: -P Unicode case folding"
234 @echo "=== All tests complete! ==="
235
236 # Help
237 help:
238 @echo "FERP Makefile targets:"
239 @echo " all - Build ferp (debug mode)"
240 @echo " debug - Build with debug flags"
241 @echo " release - Build with optimization"
242 @echo " test - Run basic tests"
243 @echo " clean - Remove build artifacts"
244 @echo " install - Install to /usr/local/bin"
245 @echo " uninstall- Remove from /usr/local/bin"
246 @echo " help - Show this message"
247
248 .PHONY: all debug release clean install uninstall test help