aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-11-07 22:29:02 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-12-16 19:18:51 -0800
commit035008cd9daf96a06f4e046cb81862275b013b71 (patch)
treeee9a096141930f1c4e791d6263148fe1dc781a16 /src
parente7f1ff14d6e17137841a4f692ac616e6125e234b (diff)
downloadbun-035008cd9daf96a06f4e046cb81862275b013b71.tar.gz
bun-035008cd9daf96a06f4e046cb81862275b013b71.tar.zst
bun-035008cd9daf96a06f4e046cb81862275b013b71.zip
[bun install] node-semver implementation (doesn't run yet)
Diffstat (limited to 'src')
-rw-r--r--src/install/semver.zig427
1 files changed, 398 insertions, 29 deletions
diff --git a/src/install/semver.zig b/src/install/semver.zig
index 14eb3097a..ba3659247 100644
--- a/src/install/semver.zig
+++ b/src/install/semver.zig
@@ -9,6 +9,23 @@ pub const Version = struct {
extra_tags: []const Tag = &[_]Tag{},
raw: strings.StringOrTinyString = strings.StringOrTinyString{},
+ pub fn format(self: Version, comptime layout: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void {
+ try std.fmt.format(writer, "{d}.{d}.{d}", .{ self.major, self.minor, self.patch });
+
+ const pre = self.tag.pre.slice();
+ const build = self.tag.build.slice();
+
+ if (pre.len > 0) {
+ try writer.writeAll("-");
+ try writer.writeAll(pre);
+ }
+
+ if (build.len > 0) {
+ try writer.writeAll("+");
+ try writer.writeAll(build);
+ }
+ }
+
inline fn atPart(i: u8) u32 {
return switch (i) {
0 => self.major,
@@ -18,31 +35,135 @@ pub const Version = struct {
};
}
+ pub fn order(lhs: Version, rhs: Version) std.math.Order {
+ if (lhs.major < rhs.major) return .lt;
+ if (lhs.major > rhs.major) return .gt;
+ if (lhs.minor < rhs.minor) return .lt;
+ if (lhs.minor > rhs.minor) return .gt;
+ if (lhs.patch < rhs.patch) return .lt;
+ if (lhs.patch > rhs.patch) return .gt;
+
+ return .eql;
+ }
+
pub const Tag = struct {
pre: strings.StringOrTinyString = strings.StringOrTinyString{},
build: strings.StringOrTinyString = strings.StringOrTinyString{},
+ pub inline fn hasPre(this: Tag) bool {
+ return this.pre.slice().len > 0;
+ }
+
+ pub inline fn hasBuild(this: Tag) bool {
+ return this.build.slice().len > 0;
+ }
+
pub const TagResult = struct {
tag: Tag = Tag{},
extra_tags: []const Tag = &[_]Tag{},
len: u32 = 0,
};
- pub fn parse(allocator: *std.mem.Allocator, input: string) TagResult {}
- };
+ var multi_tag_warn = false;
+ // TODO: support multiple tags
+ pub fn parse(allocator: *std.mem.Allocator, input: string) TagResult {
+ var build_count: u32 = 0;
+ var pre_count: u32 = 0;
+
+ for (input) |c| {
+ switch (c) {
+ ' ' => break,
+ '+' => {
+ build_count += 1;
+ },
+ '-' => {
+ pre_count += 1;
+ },
+ }
+ }
- pub fn isGreaterThan(self: Version, other: Version) bool {
- if (self.major > other.major) {
- return true;
- }
+ if (build_count == 0 and pre_count == 0) {
+ return TagResult{
+ .len = 0,
+ };
+ }
- if (self.minor > other.minor) {
- return true;
- }
+ if (@maximum(build_count, pre_count) > 1 and !multi_tag_warn) {
+ Output.prettyErrorln("<r><orange>warn<r>: Multiple pre/build tags is not supported yet.", .{});
+ multi_tag_warn = true;
+ }
+
+ const State = enum { none, pre, build };
+ var result = TagResult{};
+ // Common case: no allocation is necessary.
+ var state = State.none;
+ var start: usize = 0;
+
+ var tag_i: usize = 0;
+ var had_content = false;
+
+ for (input) |c, i| {
+ switch (c) {
+ ' ' => {
+ switch (state) {
+ .none => {},
+ .pre => {
+ result.tag.pre = strings.StringOrTinyString.init(input[start..i]);
+ if (comptime Environment.isDebug) {
+ std.debug.assert(!strings.containsChar(result.tag.pre.slice(), '-'));
+ }
+ state = State.none;
+ },
+ .build => {
+ result.tag.build = strings.StringOrTinyString.init(input[start..i]);
+ if (comptime Environment.isDebug) {
+ std.debug.assert(!strings.containsChar(result.tag.build.slice(), '-'));
+ }
+ state = State.none;
+ },
+ }
+ result.len = @truncate(u32, i);
+ break;
+ },
+ '+' => {
+ // qualifier ::= ( '-' pre )? ( '+' build )?
+ if (state == .pre) {
+ result.tag.pre = strings.StringOrTinyString.init(input[start..i]);
+ if (comptime Environment.isDebug) {
+ std.debug.assert(!strings.containsChar(result.tag.pre.slice(), '-'));
+ }
+ }
+
+ state = .build;
+ start = i + 1;
+ },
+ '-' => {
+ state = .pre;
+ start = i + 1;
+ },
+ }
+ }
+
+ switch (state) {
+ .none => {},
+ .pre => {
+ result.tag.pre = strings.StringOrTinyString.init(input[start..]);
+ if (comptime Environment.isDebug) {
+ std.debug.assert(!strings.containsChar(result.tag.pre.slice(), '-'));
+ }
+ result.len = @truncate(u32, input.len);
+ },
+ .build => {
+ result.tag.build = strings.StringOrTinyString.init(input[start..]);
+ if (comptime Environment.isDebug) {
+ std.debug.assert(!strings.containsChar(result.tag.build.slice(), '-'));
+ }
+ result.len = @truncate(u32, input.len);
+ },
+ }
- if (self.patch > other.patch) {
- return true;
+ return result;
}
- }
+ };
pub const ParseResult = struct {
wildcard: Query.Token.Wildcard = Query.Token.Wildcard.none,
@@ -197,7 +318,7 @@ pub const Version = struct {
}
result.stopped_at = @intCast(u32, @maximum(stopped_at, 0));
-
+ result.version.raw = strings.StringOrTinyString.init(input[0..result.stopped_at]);
return result;
}
@@ -206,6 +327,8 @@ pub const Version = struct {
var bytes: [10]u8 = undefined;
var byte_i: u8 = 0;
+ std.debug.assert(input[0] != '.');
+
for (input) |char, i| {
switch (char) {
'X', 'x', '*' => return 0,
@@ -215,6 +338,7 @@ pub const Version = struct {
bytes[byte_i] = char;
byte_i += 1;
},
+ ' ', '.' => break,
// ignore invalid characters
else => {},
}
@@ -223,26 +347,144 @@ pub const Version = struct {
// If there are no numbers, it's 0.
if (byte_i == 0) return 0;
+ if (comptime Environment.isDebug) {
+ return std.fmt.parseInt(u32, bytes[0..byte_i], 10) catch |err| {
+ Output.prettyErrorln("ERROR {s} parsing version: \"{s}\", bytes: {s}", .{
+ @errorName(err),
+ input,
+ bytes[0..byte_i],
+ });
+ return 0;
+ };
+ }
+
return std.fmt.parseInt(u32, bytes[0..byte_i], 10) catch 0;
}
};
pub const Range = struct {
- pub const Op = enum {
- eql,
- lt,
- lte,
- gt,
- gte,
+ pub const Op = enum(u8) {
+ unset = 0,
+ eql = 1,
+ lt = 3,
+ lte = 4,
+ gt = 5,
+ gte = 6,
};
+ left: Comparator = Comparator{},
+ right: Comparator = Comparator{},
+
+ pub fn initWildcard(version: Version, wildcard: Query.Token.Wildcard) Range {
+ switch (wildcard) {
+ .none => {
+ return Range{
+ .left = Comparator{
+ .op = Op.eql,
+ .version = version,
+ },
+ };
+ },
+
+ .major => {
+ return Range{
+ .left = Comparator{
+ .op = Op.gte,
+ .version = Version{ .raw = version.raw },
+ },
+ };
+ },
+ .minor => {
+ var lhs = Version{ .raw = version.raw };
+ lhs.major = version.major + 1;
+
+ var rhs = Version{ .raw = version.raw };
+ rhs.major = version.major;
+
+ return Range{
+ .left = Comparator{
+ .op = Op.lt,
+ .version = lhs,
+ },
+ .right = Comparator{
+ .op = Op.gte,
+ .version = rhs,
+ },
+ };
+ },
+ .patch => {
+ var lhs = Version{};
+ lhs.major = version.major;
+ lhs.minor = version.minor + 1;
+
+ var rhs = Version{};
+ rhs.major = version.major;
+ rhs.minor = version.minor;
+
+ rhs.raw = version.raw;
+ lhs.raw = version.raw;
+
+ return Range{
+ .left = Comparator{
+ .op = Op.lt,
+ .version = lhs,
+ },
+ .right = Comparator{
+ .op = Op.gte,
+ .version = rhs,
+ },
+ };
+ },
+ }
+ }
+
+ pub inline fn hasLeft(this: Range) bool {
+ return this.left.op != Op.unset;
+ }
+
+ pub inline fn hasRight(this: Range) bool {
+ return this.right.op != Op.unset;
+ }
+
pub const Comparator = struct {
- op: Op = Op.eql,
+ op: Op = Op.unset,
version: Version = Version{},
+
+ pub fn satisfies(this: Comparator, version: Version) bool {
+ const order = this.version.order(version);
+
+ return switch (order) {
+ .eql => switch (this.op) {
+ .lte, .gte, .eql => true,
+ else => false,
+ },
+ .gt => switch (this.op) {
+ .gt => true,
+ else => false,
+ },
+ .lt => switch (this.op) {
+ .lt => true,
+ else => false,
+ },
+ };
+ }
};
- left: Comparator = Comparator{},
- right: ?Comparator = null,
+ pub fn satisfies(this: Range, version: Version) bool {
+ if (!this.hasLeft()) {
+ return false;
+ }
+
+ if (!this.left.satisfies(version)) {
+ return false;
+ }
+
+ if (this.hasRight() and !this.right.satisfies(version)) {
+ return false;
+ }
+
+ return true;
+ }
};
pub const Query = struct {
@@ -261,20 +503,120 @@ pub const Query = struct {
head: Query,
tail: ?*Query = null,
allocator: *std.mem.Allocator,
- pub fn setVersion(self: *List, version: Version) void {}
+ pub fn orVersion(self: *List, version: Version) !void {
+ if (!self.head.range.hasLeft() and self.tail == null) {
+ std.debug.assert(!self.head.range.hasRight());
+ self.head.range.left.version = version;
+ self.head.range.left.op = .eql;
+ return;
+ }
+
+ var tail = try self.allocator.create(Query);
+ tail.* = Query{};
+ tail.range.left.version = version;
+ tail.range.left.op = .eql;
+
+ var last_tail = self.tail orelse &self.head;
+ std.debug.assert(last_tail.next_op == .none);
+
+ last_tail.next_op = .OR;
+ last_tail.next = tail;
+ self.tail = tail;
+ }
+
+ fn addRange(self: *List, range: Range, is_and: bool) !void {
+ if (!self.head.range.hasLeft() and !self.head.range.hasRight()) {
+ self.head.range = range;
+ return;
+ }
- pub fn andRange(self: *List, range: Range) !void {}
+ var tail = try self.allocator.create(Query);
+ tail.range = range;
- pub fn orRange(self: *List, range: Range) !void {}
+ var last_tail = self.tail orelse &self.head;
+ std.debug.assert(last_tail.next_op == .none);
+ last_tail.next = tail;
+ last_tail.next_op = if (is_and) .AND else .OR;
+ }
+ pub fn andRange(self: *List, range: Range) !void {
+ try self.addRange(range, true);
+ }
+ pub fn orRange(self: *List, range: Range) !void {
+ try self.addRange(range, false);
+ }
};
+ pub fn satisfies(this: *Query, version: Version) bool {
+ const left = this.range.satisfies(version);
+ return switch (this.next_op) {
+ .none => left,
+ .AND => left and this.next.satisfies(version),
+ .OR => left or this.next.satisfies(version),
+ };
+ }
+
pub const Token = struct {
tag: Tag = Tag.none,
wildcard: Wildcard = Wildcard.none,
+ pub fn toRange(this: Token, version: Version) Range {
+ switch (this.tag) {
+ // Allows changes that do not modify the left-most non-zero element in the [major, minor, patch] tuple
+ .caret => {
+ var range = Range{ .left = .{ .op = .gte, .version = Version{ .raw = version.raw } } };
+
+ if (version.patch > 0 or version.minor > 0) {
+ range.right = .{
+ .op = .lt,
+ .version = Version{ .raw = version.raw, .major = version.major, .minor = version.minor + 1, .patch = 0 },
+ };
+ return range;
+ }
+
+ range.right = .{
+ .op = .lt,
+ .version = Version{ .raw = version.raw, .major = version.major + 1, .minor = 0, .patch = 0 },
+ };
+
+ return range;
+ },
+ .tilda => {
+ if (version.minor == 0 or this.wildcard == .minor or this.wildcard == .major) {
+ return Range.initWildcard(version, .minor);
+ }
+
+ return Range.initWildcard(version, .patch);
+ },
+ .none => unreachable,
+ else => {},
+ }
+
+ if (this.wildcard != Wildcard.none) {
+ return Range.initWildcard(version, this.wildcard);
+ }
+
+ switch (this.tag) {
+ .version => {
+ return Range{ .left = .{ .op = .eql, .version = version } };
+ },
+ .gt => {
+ return Range{ .left = .{ .op = .gt, .version = version } };
+ },
+ .gte => {
+ return Range{ .left = .{ .op = .gte, .version = version } };
+ },
+ .lt => {
+ return Range{ .left = .{ .op = .lt, .version = version } };
+ },
+ .lte => {
+ return Range{ .left = .{ .op = .lte, .version = version } };
+ },
+ else => unreachable,
+ }
+ }
+
pub const Tag = enum {
none,
- logical_or,
gt,
gte,
lt,
@@ -292,9 +634,9 @@ pub const Query = struct {
};
};
- pub fn parse(allocator: *std.mem.Allocator, input: string) !Query {
+ pub fn parse(allocator: *std.mem.Allocator, input: string) !List {
var i: usize = 0;
- var query = Query{};
+ var list = List{ .allocator = allocator };
var token = Token{};
var prev_token = Token{};
@@ -303,6 +645,8 @@ pub const Query = struct {
var skip_round = false;
var is_or = false;
+ var last_non_whitespace: usize = 0;
+
while (i < input.len) {
skip_round = false;
@@ -382,10 +726,35 @@ pub const Query = struct {
}
if (!skip_round) {
+ const parse_result = Version.parse(input[i..]);
+
if (count == 0 and token.tag == .version) {
prev_token.tag = token.tag;
- const parse_result = Version.parse(input[i..]);
+
+ token.wildcard = parse_result.wildcard;
+
+ switch (parse_result.wildcard) {
+ .none => {
+ try list.orVersion(parse_result.version);
+ },
+ else => {
+ try list.andRange(token.toRange(parse_result.version));
+ },
+ }
+ token.tag = Token.Tag.none;
+ token.wildcard = .none;
+ } else if (count == 0) {
+ prev_token.tag = token.tag;
+ token.wildcard = parse_result.wildcard;
+ try list.andRange(token.toRange(parse_result.version));
+ } else if (is_or) {
+ try list.orRange(token.toRange(parse_result.version));
+ } else {
+ try list.andRange(token.toRange(parse_result.version));
}
+
+ i += parse_result.stopped_at + 1;
+ is_or = false;
}
}