part of 'router.dart'; class RouteGrammar { static const String notSlashRgx = r'([^/]+)'; //static final RegExp rgx = RegExp(r'\((.+)\)'); static final Parser notSlash = match(RegExp(notSlashRgx)).value((r) => r.span?.text ?? ''); static final Parser regExp = match(RegExp(r'\(([^\n)]+)\)([^/]+)?')) .value((r) => r.scanner.lastMatch!); static final Parser parameterName = match(RegExp('$notSlashRgx?' r':([A-Za-z0-9_]+)' r'([^(/\n])?')) .value((r) => r.scanner.lastMatch!); static final Parser parameterSegment = chain([ parameterName, match('?').value((r) => true).opt(), regExp.opt(), ]).map((r) { var match = r.value![0] as Match; var r2 = r.value![2]; Match? rgxMatch; if (r2 != 'NULL') { rgxMatch = r2 as Match?; } var pre = match[1] ?? ''; var post = match[3] ?? ''; RegExp? rgx; if (rgxMatch != null) { rgx = RegExp('(${rgxMatch[1]})'); post = (rgxMatch[2] ?? '') + post; } if (pre.isNotEmpty || post.isNotEmpty) { if (rgx != null) { var pattern = pre + rgx.pattern + post; rgx = RegExp(pattern); } else { rgx = RegExp('$pre$notSlashRgx$post'); } } // TODO: relook at this later var m2 = match[2] ?? ''; var s = ParameterSegment(m2, rgx); return r.value![1] == true ? OptionalSegment(s) : s; }); static final Parser parsedParameterSegment = chain([ match(RegExp(r'(int|num|double)'), errorMessage: 'Expected "int","double", or "num".') .map((r) => r.span!.text), parameterSegment, ]).map((r) { return ParsedParameterSegment( r.value![0] as String, r.value![1] as ParameterSegment); }); static final Parser wildcardSegment = match(RegExp('$notSlashRgx?' r'\*' '$notSlashRgx?')) .value((r) { var m = r.scanner.lastMatch!; var pre = m[1] ?? ''; var post = m[2] ?? ''; return WildcardSegment(pre, post); }); static final Parser constantSegment = notSlash.map((r) => ConstantSegment(r.value)); static final Parser slashSegment = match(SlashSegment.rgx).map((_) => SlashSegment()); static final Parser routeSegment = any([ //slashSegment, parsedParameterSegment, parameterSegment, wildcardSegment, constantSegment ]); // static final Parser routeDefinition = routeSegment // .star() // .map((r) => RouteDefinition(r.value ?? [])) // .surroundedBy(match(RegExp(r'/*')).opt()); static final Parser slashes = match(RegExp(r'/*')); static final Parser routeDefinition = routeSegment .separatedBy(slashes) .map((r) => RouteDefinition(r.value ?? [])) .surroundedBy(slashes.opt()); } class RouteDefinition { final List segments; RouteDefinition(this.segments); Parser? compile() { Parser? out; for (var i = 0; i < segments.length; i++) { var s = segments[i]; var isLast = i == segments.length - 1; if (out == null) { out = s.compile(isLast); } else { out = s.compileNext( out.then(match('/')).index(0).cast(), isLast); } } return out; } } abstract class RouteSegment { Parser compile(bool isLast); Parser compileNext(Parser p, bool isLast); } class SlashSegment implements RouteSegment { static final RegExp rgx = RegExp(r'/+'); const SlashSegment(); @override Parser compile(bool isLast) { return match(rgx).map((_) => RouteResult({})); } @override Parser compileNext(Parser p, bool isLast) { return p.then(compile(isLast)).index(0).cast(); } @override String toString() => 'Slash'; } class ConstantSegment extends RouteSegment { final String? text; ConstantSegment(this.text); @override String toString() { return 'Constant: $text'; } @override Parser compile(bool isLast) { return match(text!).map((r) => RouteResult({})); } @override Parser compileNext(Parser p, bool isLast) { return p.then(compile(isLast)).index(0).cast(); } } class WildcardSegment extends RouteSegment { final String pre, post; WildcardSegment(this.pre, this.post); @override String toString() { return 'Wildcard segment'; } String _symbol(bool isLast) { if (isLast) return r'.*'; return r'[^/]*'; } RegExp _compile(bool isLast) { return RegExp('$pre(${_symbol(isLast)})$post'); // if (isLast) return match(RegExp(r'.*')); // return match(RegExp(r'[^/]*')); } @override Parser compile(bool isLast) { return match(_compile(isLast)).map((r) { var result = r.scanner.lastMatch; if (result != null) { //return RouteResult({}, tail: r.scanner.lastMatch![1]) return RouteResult({}, tail: result[1]); } else { return RouteResult({}); } }); } @override Parser compileNext(Parser p, bool isLast) { return p.then(compile(isLast)).map((r) { var items = r.value!.cast(); var a = items[0], b = items[1]; return a ..addAll(b.params) .._setTail(b.tail); }); } } class OptionalSegment extends ParameterSegment { final ParameterSegment parameter; OptionalSegment(this.parameter) : super(parameter.name, parameter.regExp); @override String toString() { return 'Optional: $parameter'; } @override Parser compile(bool isLast) { return super.compile(isLast).opt(); } @override Parser compileNext(Parser p, bool isLast) { return p.then(_compile().opt()).map((r) { // Return an empty RouteResult if null if (r.value == null) { return RouteResult({}); } var v = r.value!; if (v[1] == null) { return v[0] as RouteResult; } return (v[0] as RouteResult) ..addAll({name: Uri.decodeComponent(v as String)}); }); } } class ParameterSegment extends RouteSegment { final String name; final RegExp? regExp; ParameterSegment(this.name, this.regExp); @override String toString() { if (regExp != null) { return 'Param: $name (${regExp?.pattern})'; } return 'Param: $name'; } Parser _compile() { if (regExp != null) { return match(regExp!).value((r) { var result = r.scanner.lastMatch; if (result != null) { // TODO: Invalid method //return r.scanner.lastMatch![1]; return result.toString(); } else { return ''; } }); } else { return RouteGrammar.notSlash; } } @override Parser compile(bool isLast) { return _compile() .map((r) => RouteResult({name: Uri.decodeComponent(r.value!)})); } @override Parser compileNext(Parser p, bool isLast) { return p.then(_compile()).map((r) { return (r.value![0] as RouteResult) ..addAll({name: Uri.decodeComponent(r.value![1] as String)}); }); } } class ParsedParameterSegment extends RouteSegment { final String type; final ParameterSegment parameter; ParsedParameterSegment(this.type, this.parameter); num getValue(String s) { switch (type) { case 'int': return int.parse(s); case 'double': return double.parse(s); default: return num.parse(s); } } @override Parser compile(bool isLast) { return parameter._compile().map((r) => RouteResult( {parameter.name: getValue(Uri.decodeComponent(r.span!.text))})); } @override Parser compileNext(Parser p, bool isLast) { return p.then(parameter._compile()).map((r) { return (r.value![0] as RouteResult) ..addAll({ parameter.name: getValue(Uri.decodeComponent(r.value![1] as String)) }); }); } }