From 00a531045b6eedfe39096ecec632f982f60d348e Mon Sep 17 00:00:00 2001 From: Lyssieth Date: Sun, 24 Nov 2024 01:26:03 +0200 Subject: [PATCH] Massive fuckery --- .woodpecker/build.yml | 9 + Justfile | 2 + build.zig | 28 ++ build.zig.zon | 10 + flake.nix | 1 + src/args/arg.zig | 87 ++++++ src/args/args.zig | 458 +++++++++++++++++++++++++++++ src/args/parsers/boolean.zig | 49 +++ src/args/parsers/enumLiteral.zig | 43 +++ src/args/parsers/num.zig | 11 + src/args/parsers/parsers.zig | 32 ++ src/args/validators/number.zig | 0 src/args/validators/string.zig | 32 ++ src/args/validators/validators.zig | 37 +++ src/root.zig | 13 + 15 files changed, 812 insertions(+) create mode 100644 .woodpecker/build.yml create mode 100644 Justfile create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/args/arg.zig create mode 100644 src/args/args.zig create mode 100644 src/args/parsers/boolean.zig create mode 100644 src/args/parsers/enumLiteral.zig create mode 100644 src/args/parsers/num.zig create mode 100644 src/args/parsers/parsers.zig create mode 100644 src/args/validators/number.zig create mode 100644 src/args/validators/string.zig create mode 100644 src/args/validators/validators.zig create mode 100644 src/root.zig diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml new file mode 100644 index 0000000..41896af --- /dev/null +++ b/.woodpecker/build.yml @@ -0,0 +1,9 @@ +when: + - event: push + branch: main + +steps: + - name: Test + image: "git.cutie.zone/lyssieth/baseimages:nix-docker" + commands: + - nix develop -c zig build test diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..f5e02f1 --- /dev/null +++ b/Justfile @@ -0,0 +1,2 @@ +watch: + watchexec -e zig -c -- zig build test \ No newline at end of file diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..65ef7cc --- /dev/null +++ b/build.zig @@ -0,0 +1,28 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const libRoot = b.path("src/root.zig"); + + const lib = b.addStaticLibrary(.{ + .name = "lys", + .root_source_file = libRoot, + .target = target, + .optimize = optimize, + }); + + b.installArtifact(lib); + + const libTests = b.addTest(.{ + .root_source_file = libRoot, + .target = target, + .optimize = optimize, + }); + + const libTestsRun = b.addRunArtifact(libTests); + + const test_step = b.step("test", "Run library tests"); + test_step.dependOn(&libTestsRun.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..7d3a736 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = "lys", + .version = "0.0.0", + .dependencies = .{}, + .paths = .{ + "build.zig", + "build.zig.zon", + "src/root.zig", + }, +} diff --git a/flake.nix b/flake.nix index 038fb36..d8e5120 100644 --- a/flake.nix +++ b/flake.nix @@ -17,6 +17,7 @@ devShells.default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ just + watchexec zig zls ]; diff --git a/src/args/arg.zig b/src/args/arg.zig new file mode 100644 index 0000000..7181247 --- /dev/null +++ b/src/args/arg.zig @@ -0,0 +1,87 @@ +const std = @import("std"); + +pub const Arg = union(enum(u2)) { + Flag: struct { + name: []const u8, + value: ?[]const u8, + consumed: bool = false, + }, + Positional: struct { + value: []const u8, + idx: ?usize = undefined, + consumed: bool = false, + }, + + pub inline fn setConsumed(s: *Arg) void { + switch (s.*) { + .Flag => |*f| f.consumed = true, + .Positional => |*p| p.consumed = true, + } + } + + pub inline fn isConsumed(s: Arg) bool { + return switch (s) { + .Flag => |f| f.consumed, + .Positional => |p| p.consumed, + }; + } + + pub fn parse_arg(arg: []const u8) !Arg { + var flag: ?Arg = null; + if (std.mem.startsWith(u8, arg, "--")) { + flag = try Arg.parse_flag(arg[2..]); + } else if (std.mem.startsWith(u8, arg, "-")) { + flag = try Arg.parse_flag(arg[1..]); + } + + if (flag) |f| { + return f; + } + + return Arg{ + .Positional = .{ + .value = arg, + }, + }; + } + + fn parse_flag(arg: []const u8) !Arg { + if (arg.len == 0) { + return error.EmptyFlag; + } + + const idx = std.mem.indexOf(u8, arg, "="); + var flag: Arg = undefined; + if (idx) |i| { + const remainder = arg[i +| 1..]; + + if (remainder.len == 0) { + return error.NoValueButExpectedValue; + } + + flag = .{ + .Flag = .{ + .name = arg[0..i], + .value = arg[i +| 1..], + }, + }; + } else { + flag = .{ .Flag = .{ + .name = arg, + .value = null, + } }; + } + + switch (flag) { + .Flag => |f| { + if (f.name.len == 0) { + std.debug.print("failed to parse flag: {s}\n", .{arg}); + return error.InvalidFlag; + } + }, + .Positional => unreachable, + } + + return flag; + } +}; diff --git a/src/args/args.zig b/src/args/args.zig new file mode 100644 index 0000000..039972b --- /dev/null +++ b/src/args/args.zig @@ -0,0 +1,458 @@ +const std = @import("std"); + +const Allocator = std.mem.Allocator; + +pub const parsers = @import("./parsers/parsers.zig"); +pub const validators = @import("./validators/validators.zig"); + +const Arg = @import("./arg.zig").Arg; + +/// Metadata for a field that is parseable by the argument parser. +pub const Extra = union(enum(u2)) { + Positional, + Remainder, + Flag: struct { + name: []const u8, + short: ?[]const u8 = null, + toggle: bool = false, + takesValue: bool = false, + }, +}; + +/// A marker for any field `T` that you want to be parseable by the argument parser. +pub fn Marker(comptime T: type) type { + return struct { + value: T, + extra: Extra, + /// Optional function that parses the value of the field. + /// If this is null, no parsing will take place. + /// + /// This function can be any function that takes a `[]const u8` and returns `anyerror!T`. + /// + /// However, it is recommended to use `lys.args.parsers` for common types. + parse: ?parsers.ParseSignature(T) = null, + /// Optional function that validates the value of the field. + /// If this is null, no validation will take place. + /// + /// This function can be any function that takes a `[]const u8` and returns `anyerror!void`. + /// + /// However, it is recommended to use `lys.args.validators` for common types. + validate: ?validators.ValidateSignature = null, + }; +} + +/// +pub fn parseArgs(comptime T: type, allocator: Allocator) !T { + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); + + return parseArgsImpl(T, allocator, args); +} + +/// actual implementation of parse_args, necessary for testing purposes. +/// The main implementation is in parse_args, which is a wrapper around this. +fn parseArgsImpl(comptime T: type, allocator: Allocator, args: [][]const u8) !T { + var flags = std.ArrayList(Arg).init(allocator); + defer flags.deinit(); + + if (args.len < 2) { + return error.NoArguments; + } + + for (args, 0..) |arg, idx| { + if (idx == 0) continue; // skip first arg: process name + + const argument = try Arg.parse_arg(arg); + + try flags.append(argument); + } + + var i: usize = 0; + for (0..flags.items.len) |idx| { + const flag = &flags.items[idx]; + switch (flag.*) { + .Positional => |*p| { + p.idx = i; + i += 1; + }, + .Flag => continue, + } + } + + const result = initFromParsed(T, allocator, flags.items); + + return result; +} + +/// helper that finds a flag by name (or short name), if it's not consumed +fn find(flags: []Arg, expected_long: []const u8, expected_short: ?[]const u8) ?*Arg { + for (flags) |*flag| { + if (flag.isConsumed()) continue; // skip consumed flags + + switch (flag.*) { + .Flag => |f| { + if (std.mem.eql(u8, f.name, expected_long)) { + return flag; + } + + if (expected_short == null) continue; + + if (std.mem.eql(u8, f.name, expected_short.?)) { + return flag; + } + }, + .Positional => continue, + } + } + + return null; +} + +/// helper that takes the next non-consumed positional argument +fn nextPositional(flags: []Arg) ?*Arg { + for (flags) |*flag| { + if (flag.isConsumed()) continue; // skip consumed flags + + switch (flag.*) { + .Flag => continue, + .Positional => |_| { + return flag; + }, + } + } + + return null; +} + +/// Does the actual work of initializing T from flags. +fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T { + comptime { + std.debug.assert(@hasField(T, "allocator")); + std.debug.assert(@hasDecl(T, "deinit")); + } + + var result = T{ + .allocator = allocator, + }; + errdefer result.deinit(); + + const this = @TypeOf(result); + const info = @typeInfo(this); + + if ((info.Struct.fields.len -| 1) == 0) { + return result; + } + + inline for (info.Struct.fields) |field| { + if (comptime std.mem.eql(u8, field.name, "allocator")) { + continue; // skip allocator + } + + const fie = &@field(result, field.name); + const extra = fie.*.extra; + + switch (extra) { + .Flag => |f| { + if (!(f.takesValue or f.toggle)) { + std.debug.print("error: invalid flag `{s}`: is not a toggle and doesn't take a value\n", .{f.name}); + return error.InvalidFlag; + } + + const maybe_flag = find(flags, f.name, f.short); + + if (maybe_flag) |flag| { + flag.setConsumed(); + if (f.toggle) { + if (@TypeOf(fie.*.value) == bool) { + fie.*.value = true; + comptime continue; + } else { + std.debug.print("error: invalid switch {s}: expected bool, found {s}\n", .{ + f.name, + @typeName(@TypeOf(fie.*.value)), + }); + return error.InvalidSwitch; + } + } + + if (f.takesValue) { + if (flag.*.Flag.value) |value| { + if (@TypeOf(fie.*.value) == []const u8) { + fie.*.value = try result.allocator.dupe(u8, value); + comptime continue; + } else { + if (fie.*.parse) |parse_fn| { + fie.*.value = try parse_fn(value); + comptime continue; + } else { + std.debug.print("error: flag `{s}` expected a value, but no parser was provided\n", .{f.name}); + return error.NoValueForFlag; + } + + unreachable; + } + } else { + std.debug.print("error: flag `{s}` expected a value, but none was provided\n", .{f.name}); + return error.NoValueForFlag; + } + } + + std.debug.print("error: invalid flag `{s}`: is not a toggle and doesn't take a value\n", .{f.name}); + return error.InvalidFlag; + } else { + std.debug.print("error: invalid flag `{s}`: could not find flag\n", .{f.name}); + return error.InvalidFlag; + } + }, + .Remainder => { + var not_consumed = std.ArrayList([]const u8).init(result.allocator); + errdefer not_consumed.deinit(); + + for (flags) |*flag| { + if (flag.isConsumed()) continue; + + switch (flag.*) { + .Flag => |f| { + std.debug.print("TODO: figure out what to do with remainder flag {s} (value: {?s})\n", .{ + f.name, + f.value, + }); + }, + .Positional => |p| { + flag.setConsumed(); + try not_consumed.append(try result.allocator.dupe(u8, p.value)); + }, + } + } + + if (@TypeOf(fie.*.value) == std.ArrayList([]const u8)) { + fie.*.value = not_consumed; + } else { + std.debug.print("error: invalid remainder {s}: expected `std.ArrayList([] const u8)`, got `{s}`\n", .{ + field.name, + @typeName(@TypeOf(fie.*.value)), + }); + return error.InvalidRemainder; + } + }, + .Positional => { + const next = nextPositional(flags); + + if (next) |flag| { + flag.setConsumed(); + if (@TypeOf(fie.*.value) == []const u8) { + fie.*.value = try result.allocator.dupe(u8, flag.*.Positional.value); + } else if (fie.*.parse) |parse_fn| { + fie.*.value = parse_fn(flag.*.Positional.value) catch |err| { + std.debug.print("error: could not parse positional argument for {s}: {s}\n", .{ + field.name, + @errorName(err), + }); + std.debug.print("- tried to parse: {s}\n", .{flag.*.Positional.value}); + std.debug.print("- expected type: {s}\n", .{@typeName(@TypeOf(fie.*.value))}); + return error.InvalidPositional; + }; + } else { + std.debug.print("error: could not parse positional argument for {s}\n", .{field.name}); + return error.InvalidPositional; + } + } else { + std.debug.print("error: could not find positional argument for {s}\n", .{field.name}); + return error.NoArgumentFound; + } + }, + } + } + + return result; +} + +const t = std.testing; + +test "parse args" { + const args = try t.allocator.alloc([]const u8, 4); + defer t.allocator.free(args); + + const Demo = struct { + allocator: Allocator, + + flag: Marker([]const u8) = .{ + .value = undefined, + .extra = Extra{ + .Flag = .{ + .name = "flag", + .takesValue = true, + }, + }, + }, + toggle: Marker(bool) = .{ + .value = undefined, + .extra = Extra{ + .Flag = .{ + .name = "toggle", + .toggle = true, + .takesValue = false, + }, + }, + }, + positional: Marker([]const u8) = .{ + .value = undefined, + .extra = Extra{ + .Positional = {}, + }, + }, + + fn deinit(self: *@This()) void { + self.allocator.free(self.flag.value); + self.allocator.free(self.positional.value); + + self.* = undefined; + } + }; + + args[0] = ""; + args[1] = "--flag=value"; + args[2] = "--toggle"; + args[3] = "positional"; + + var result = try parseArgsImpl(Demo, t.allocator, args); + defer result.deinit(); + + try t.expectEqualStrings("value", result.flag.value); + try t.expect(result.toggle.value); + try t.expectEqualStrings("positional", result.positional.value); +} + +test "parse fn (positional)" { + const args = try t.allocator.alloc([]const u8, 4); + defer t.allocator.free(args); + + args[1] = "1234"; + args[2] = "true"; + args[3] = "Value"; + + const DemoEnum = enum(u8) { + NotValue, + Value, + }; + + const Demo = struct { + allocator: Allocator, + + flag: Marker(u16) = .{ + .value = undefined, + .extra = Extra{ + .Positional = {}, + }, + .parse = parsers.num(u16), + }, + + boolean: Marker(bool) = .{ + .value = undefined, + .extra = Extra{ + .Positional = {}, + }, + .parse = parsers.boolean, + }, + + enumeration: Marker(DemoEnum) = .{ + .value = undefined, + .extra = Extra{ + .Positional = {}, + }, + .parse = parsers.enumLiteral(DemoEnum), + }, + + fn deinit(self: *@This()) void { + self.* = undefined; + } + }; + + var result = try parseArgsImpl(Demo, t.allocator, args); + defer result.deinit(); + + try t.expectEqual(1234, result.flag.value); + try t.expect(result.boolean.value); + try t.expectEqual(DemoEnum.Value, result.enumeration.value); +} + +test "parse fn (flag)" { + const args = try t.allocator.alloc([]const u8, 4); + defer t.allocator.free(args); + + args[1] = "--number=1234"; + args[2] = "--boolean=yes"; + args[3] = "--enumeration=Value"; + + const DemoEnum = enum(u8) { + NotValue, + Value, + }; + + const Demo = struct { + allocator: Allocator, + + number: Marker(u16) = .{ + .value = undefined, + .extra = Extra{ + .Flag = .{ + .name = "number", + .takesValue = true, + }, + }, + .parse = parsers.num(u16), + }, + + boolean: Marker(bool) = .{ + .value = undefined, + .extra = Extra{ + .Flag = .{ + .name = "boolean", + .takesValue = true, + }, + }, + .parse = parsers.boolean, + }, + + enumeration: Marker(DemoEnum) = .{ + .value = undefined, + .extra = Extra{ + .Flag = .{ + .name = "enumeration", + .takesValue = true, + }, + }, + .parse = parsers.enumLiteral(DemoEnum), + }, + + fn deinit(self: *@This()) void { + self.* = undefined; + } + }; + + var result = try parseArgsImpl(Demo, t.allocator, args); + defer result.deinit(); + + try t.expectEqual(1234, result.number.value); + try t.expect(result.boolean.value); + try t.expectEqual(DemoEnum.Value, result.enumeration.value); +} + +test "parse failure because no args" { + const args = try t.allocator.alloc([]const u8, 1); + defer t.allocator.free(args); + + const Demo = struct { + allocator: Allocator, + + fn deinit(self: *@This()) void { + self.* = undefined; + } + }; + + args[0] = "zither"; + + const result = parseArgsImpl(Demo, t.allocator, args); + + try t.expectError(error.NoArguments, result); +} diff --git a/src/args/parsers/boolean.zig b/src/args/parsers/boolean.zig new file mode 100644 index 0000000..b948bb6 --- /dev/null +++ b/src/args/parsers/boolean.zig @@ -0,0 +1,49 @@ +const std = @import("std"); + +const TrueValues = [_][]const u8{ "true", "yes", "y", "on", "1" }; +const FalseValues = [_][]const u8{ "false", "no", "n", "off", "0" }; + +pub fn boolean(value: []const u8) anyerror!bool { + if (value.len > 10) { + return error.InvalidBool_ValueTooLong; + } + + var buf: [10]u8 = undefined; + const lower = std.ascii.lowerString(&buf, value); + + inline for (TrueValues) |true_value| { + if (std.mem.eql(u8, lower, true_value)) { + return true; + } + } + + inline for (FalseValues) |false_value| { + if (std.mem.eql(u8, lower, false_value)) { + return false; + } + } + + return error.InvalidBool; +} + +test "bool parser" { + const t = std.testing; + + const ExpectTrue = [_][]const u8{ "true", "TrUe", "TRUE", "1", "yes", "Y", "on" }; + const ExpectFalse = [_][]const u8{ "false", "FaLsE", "FALSE", "0", "no", "N", "off" }; + const ExpectError = [_][]const u8{ "wrong", "maybe", "1.0", "0.0", "truefalse" }; + + inline for (ExpectTrue) |expect| { + try t.expect(try boolean(expect) == true); + } + + inline for (ExpectFalse) |expect| { + try t.expect(try boolean(expect) == false); + } + + inline for (ExpectError) |expect| { + try t.expectError(error.InvalidBool, boolean(expect)); + } + + try t.expectError(error.InvalidBool_ValueTooLong, boolean("1234567890123456789012345678901234567890")); +} diff --git a/src/args/parsers/enumLiteral.zig b/src/args/parsers/enumLiteral.zig new file mode 100644 index 0000000..98c7bda --- /dev/null +++ b/src/args/parsers/enumLiteral.zig @@ -0,0 +1,43 @@ +const std = @import("std"); + +pub fn enumLiteral(comptime T: type) *const fn (value: []const u8) anyerror!T { + const container = struct { + fn func(value: []const u8) anyerror!T { + return parseEnumFromStr(T, value); + } + }; + + return &container.func; +} + +fn parseEnumFromStr(comptime T: type, str: []const u8) !T { + const info = @typeInfo(T); + + comptime { + if (std.meta.activeTag(info) != .Enum) { + @compileError("Expected enum type, got " ++ @typeName(T)); + } + } + + inline for (info.Enum.fields) |field| { + if (std.mem.eql(u8, field.name, str)) { + return @enumFromInt(field.value); + } + } + + return error.InvalidEnum; +} + +test "enum literal" { + const t = std.testing; + + const Demo = enum(u8) { + A, + B, + }; + + try t.expectEqual(Demo.A, try parseEnumFromStr(Demo, "A")); + try t.expectEqual(Demo.B, try parseEnumFromStr(Demo, "B")); + + try t.expectError(error.InvalidEnum, parseEnumFromStr(Demo, "DefinitelyNot")); +} diff --git a/src/args/parsers/num.zig b/src/args/parsers/num.zig new file mode 100644 index 0000000..35c4971 --- /dev/null +++ b/src/args/parsers/num.zig @@ -0,0 +1,11 @@ +const std = @import("std"); + +pub fn num(comptime T: type) *const fn (value: []const u8) anyerror!T { + const container = struct { + fn func(value: []const u8) anyerror!T { + return std.fmt.parseInt(T, value, 10); + } + }; + + return &container.func; +} diff --git a/src/args/parsers/parsers.zig b/src/args/parsers/parsers.zig new file mode 100644 index 0000000..c4fc550 --- /dev/null +++ b/src/args/parsers/parsers.zig @@ -0,0 +1,32 @@ +const std = @import("std"); + +/// A signature for a 'parse' function. It takes a string and returns an error or a value. +/// +/// This function should *not* be called directly, but rather through the `parse` field of a `Marker`. +pub fn ParseSignature(comptime T: type) type { + return *const fn (value: []const u8) anyerror!T; +} + +pub const num = @import("./num.zig").num; +pub const boolean = @import("./boolean.zig").boolean; +pub const enumLiteral = @import("./enumLiteral.zig").enumLiteral; + +test "valid parser signatures" { + const t = std.testing; + + try t.expectEqual(ParseSignature(u8), @TypeOf(num(u8))); + try t.expectEqual(ParseSignature(bool), @TypeOf(&boolean)); + + const Enum = enum { Value, Other }; + try t.expectEqual(ParseSignature(Enum), @TypeOf(enumLiteral(Enum))); +} + +comptime { + const builtin = @import("builtin"); + + if (builtin.is_test) { + std.mem.doNotOptimizeAway(num); + std.mem.doNotOptimizeAway(boolean); + std.mem.doNotOptimizeAway(enumLiteral); + } +} diff --git a/src/args/validators/number.zig b/src/args/validators/number.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/args/validators/string.zig b/src/args/validators/string.zig new file mode 100644 index 0000000..c48bbfb --- /dev/null +++ b/src/args/validators/string.zig @@ -0,0 +1,32 @@ +const std = @import("std"); +const t = std.testing; + +pub fn notEmpty(value: []const u8) anyerror!bool { + if (value.len == 0) { + return false; + } + + return true; +} + +test "not empty" { + try t.expect(try notEmpty("") == false); + try t.expect(try notEmpty("a")); +} + +pub fn notWhiteSpace(value: []const u8) anyerror!bool { + if (std.mem.trim(u8, value, " \t\n\r").len == 0) { + return false; + } + + return true; +} + +test "not white space" { + try t.expect(try notWhiteSpace("") == false); + try t.expect(try notWhiteSpace("\n\r\t ") == false); + try t.expect(try notWhiteSpace("a")); + try t.expect(try notWhiteSpace(" a")); + try t.expect(try notWhiteSpace("\ta")); + try t.expect(try notWhiteSpace("\n\r\t a")); +} diff --git a/src/args/validators/validators.zig b/src/args/validators/validators.zig new file mode 100644 index 0000000..a34cf11 --- /dev/null +++ b/src/args/validators/validators.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +/// A function that validates a string value. +/// It should return an error if the value is unparseable. +/// Or `true` if the value is valid. +/// Or `false` if the value is invalid. +pub const ValidateSignature = *const fn (value: []const u8) anyerror!bool; + +pub const string = @import("./string.zig"); +pub const number = @import("./number.zig"); + +comptime { + const builtin = @import("builtin"); + + if (builtin.is_test) { + std.mem.doNotOptimizeAway(string); + std.mem.doNotOptimizeAway(number); + } +} + +const this = @This(); +test "valid validator signatures" { + const t = std.testing; + + const decls = @typeInfo(this).Struct.decls; + + inline for (decls) |decl| { + const field = @field(this, decl.name); + if (comptime std.mem.startsWith(u8, @typeName(field), "*")) { + comptime continue; // skip `ValidateSignature` + } + + inline for (@typeInfo(field).Struct.decls) |function| { + try t.expectEqual(ValidateSignature, @TypeOf(&@field(field, function.name))); + } + } +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..0f9ab73 --- /dev/null +++ b/src/root.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +pub const args = @import("./args/args.zig"); + +comptime { + // A hack to prevent the compiler from optimizing tests and "exports" away. + // but only in `Debug` mode. Hopefully. + const builtin = @import("builtin"); + + if (builtin.is_test) { + std.mem.doNotOptimizeAway(args); + } +}