Skip to content

Commit c18abfa

Browse files
committed
syntax: rewrite the regex-syntax crate
This commit represents a ground up rewrite of the regex-syntax crate. This commit is also an intermediate state. That is, it adds a new regex-syntax-2 crate without making any serious changes to any other code. Subsequent commits will cover the integration of the rewrite and the removal of the old crate. The rewrite is intended to be the first phase in an effort to overhaul the entire regex crate. To that end, this rewrite takes steps in that direction: * The principle change in the public API is an explicit split between a regular expression's abstract syntax (AST) and a high-level intermediate representation (HIR) that is easier to analyze. The old version of this crate mixes these two concepts, but leaned heavily towards an HIR. The AST in the rewrite has a much closer correspondence with the concrete syntax than the old `Expr` type does. The new HIR embraces its role; all flags are now compiled away (including the `i` flag), which will simplify subsequent passes, including literal detection and the compiler. ASTs are produced by ast::parse and HIR is produced by hir::translate. A top-level parser is provided that combines these so that callers can skip straight from concrete syntax to HIR. * Error messages are vastly improved thanks to the span information that is now embedded in the AST. In addition to better formatting, error messages now also include helpful hints when trying to use features that aren't supported (like backreferences and look-around). In particular, octal support is now an opt-in option. (Octal support will continue to be enabled in regex proper to support backwards compatibility, but will be disabled in 1.0.) * More robust support for Unicode Level 1 as described in UTS#18. In particular, we now fully support Unicode character classes including set notation (difference, intersection, symmetric difference) and correct support for named general categories, scripts, script extensions and age. That is, `\p{scx:Hira}` and `p{age:3.0}` now work. To make this work, we introduce an internal interval set data structure. * With the exception of literal extraction (which will be overhauled in a later phase), all code in the rewrite uses constant stack space, even while performing analysis that requires structural induction over the AST or HIR. This is done by pushing the call stack onto the heap, and is abstracted by the `ast::Visitor` and `hir::Visitor` traits. The point of this method is to eliminate stack overflows in the general case. * Empty sub-expressions are now properly supported. Expressions like `()`, `|`, `a|` and `b|()+` are now valid syntax. The principle downsides of these changes are parse time and binary size. Both seemed to have increased (slower and bigger) by about 1.5x. Parse time is generally peanuts compared to the compiler, so we mostly don't care about that. Binary size is mildly unfortunate, and if it becomes a serious issue, it should be possible to introduce a feature that disables some level of Unicode support and/or work on compressing the Unicode tables. Compile times have increased slightly, but are still a very small fraction of the overall time it takes to compile `regex`. Fixes rust-lang#174, Fixes rust-lang#424
1 parent 7f020b8 commit c18abfa

29 files changed

+23744
-1
lines changed

Diff for: regex-debug/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ workspace = ".."
1414
docopt = "0.8"
1515
regex = { version = "0.2", path = ".." }
1616
regex-syntax = { version = "0.4.0", path = "../regex-syntax" }
17+
regex-syntax2 = { version = "0.5.0", path = "../regex-syntax-2" }
1718
serde = "1"
1819
serde_derive = "1"

Diff for: regex-debug/src/main.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
extern crate docopt;
22
extern crate regex;
33
extern crate regex_syntax as syntax;
4+
extern crate regex_syntax2;
45
extern crate serde;
56
#[macro_use]
67
extern crate serde_derive;
@@ -17,6 +18,8 @@ use syntax::{ExprBuilder, Expr, Literals};
1718
const USAGE: &'static str = "
1819
Usage:
1920
regex-debug [options] ast <pattern>
21+
regex-debug [options] ast2 <pattern>
22+
regex-debug [options] hir2 <pattern>
2023
regex-debug [options] prefixes <patterns> ...
2124
regex-debug [options] suffixes <patterns> ...
2225
regex-debug [options] anchors <pattern>
@@ -51,6 +54,8 @@ Options:
5154
#[derive(Deserialize)]
5255
struct Args {
5356
cmd_ast: bool,
57+
cmd_ast2: bool,
58+
cmd_hir2: bool,
5459
cmd_prefixes: bool,
5560
cmd_suffixes: bool,
5661
cmd_anchors: bool,
@@ -93,6 +98,10 @@ fn main() {
9398
fn run(args: &Args) -> Result<()> {
9499
if args.cmd_ast {
95100
cmd_ast(args)
101+
} else if args.cmd_ast2 {
102+
cmd_ast2(args)
103+
} else if args.cmd_hir2 {
104+
cmd_hir2(args)
96105
} else if args.cmd_prefixes {
97106
cmd_literals(args)
98107
} else if args.cmd_suffixes {
@@ -109,7 +118,28 @@ fn run(args: &Args) -> Result<()> {
109118
}
110119

111120
fn cmd_ast(args: &Args) -> Result<()> {
112-
println!("{:#?}", try!(args.parse_one()));
121+
let ast = try!(args.parse_one());
122+
println!("{:#?}", ast);
123+
Ok(())
124+
}
125+
126+
fn cmd_ast2(args: &Args) -> Result<()> {
127+
use regex_syntax2::ast::parse::Parser;
128+
129+
let mut parser = Parser::new();
130+
let ast = try!(parser.parse(&args.arg_pattern));
131+
println!("{:#?}", ast);
132+
Ok(())
133+
}
134+
135+
fn cmd_hir2(args: &Args) -> Result<()> {
136+
use regex_syntax2::ParserBuilder;
137+
138+
let mut parser = ParserBuilder::new()
139+
.allow_invalid_utf8(false)
140+
.build();
141+
let hir = try!(parser.parse(&args.arg_pattern));
142+
println!("{:#?}", hir);
113143
Ok(())
114144
}
115145

Diff for: regex-syntax-2/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "regex-syntax2"
3+
version = "0.5.0" #:version
4+
authors = ["The Rust Project Developers"]
5+
license = "MIT/Apache-2.0"
6+
repository = "https://github.com/rust-lang/regex"
7+
documentation = "https://docs.rs/regex-syntax"
8+
homepage = "https://github.com/rust-lang/regex"
9+
description = "A regular expression parser."
10+
workspace = ".."
11+
12+
[dependencies]
13+
ucd-util = "0.1.0"

Diff for: regex-syntax-2/benches/bench.rs

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(test)]
12+
13+
extern crate regex_syntax2;
14+
extern crate test;
15+
16+
use regex_syntax2::Parser;
17+
use test::Bencher;
18+
19+
#[bench]
20+
fn parse_simple1(b: &mut Bencher) {
21+
b.iter(|| {
22+
let re = r"^bc(d|e)*$";
23+
Parser::new().parse(re).unwrap()
24+
});
25+
}
26+
27+
#[bench]
28+
fn parse_simple2(b: &mut Bencher) {
29+
b.iter(|| {
30+
let re = r"'[a-zA-Z_][a-zA-Z0-9_]*(')\b";
31+
Parser::new().parse(re).unwrap()
32+
});
33+
}
34+
35+
#[bench]
36+
fn parse_small1(b: &mut Bencher) {
37+
b.iter(|| {
38+
let re = r"\p{L}|\p{N}|\s|.|\d";
39+
Parser::new().parse(re).unwrap()
40+
});
41+
}
42+
43+
#[bench]
44+
fn parse_medium1(b: &mut Bencher) {
45+
b.iter(|| {
46+
let re = r"\pL\p{Greek}\p{Hiragana}\p{Alphabetic}\p{Hebrew}\p{Arabic}";
47+
Parser::new().parse(re).unwrap()
48+
});
49+
}
50+
51+
#[bench]
52+
fn parse_medium2(b: &mut Bencher) {
53+
b.iter(|| {
54+
let re = r"\s\S\w\W\d\D";
55+
Parser::new().parse(re).unwrap()
56+
});
57+
}
58+
59+
#[bench]
60+
fn parse_medium3(b: &mut Bencher) {
61+
b.iter(|| {
62+
let re = r"\p{age:3.2}\p{hira}\p{scx:hira}\p{alphabetic}\p{sc:Greek}\pL";
63+
Parser::new().parse(re).unwrap()
64+
});
65+
}
66+
67+
#[bench]
68+
fn parse_huge(b: &mut Bencher) {
69+
b.iter(|| {
70+
let re = r"\p{L}{100}";
71+
Parser::new().parse(re).unwrap()
72+
});
73+
}

0 commit comments

Comments
 (0)