Show / Hide Table of Contents

    "Smart" Parsing

    Inspiration

    Smart parsing is, at its core, inspired by the idea of traits.

    Traits are, at their core, extension interface. That is, they consist of an interface definition (in Rust, they are defined against some unknown type Self), and some implementation of the trait for a type T. Consider the following Rust example, where Foo is actually defined in some other crate/package entirely:

    struct SomeErrorType { ... }
    
    trait Parseable {
        fn parse(src: &'static str, ...) -> Self;
        fn try_parse(src: &'static str, ...) -> Result<Self, SomeErrorType>;
    }
    
    impl Parseable for Foo {
        fn parse(src: &'static str, ...) -> Foo {
            return Foo::parse(src);
        }
    
        fn try_parse(src: &'static str, ...) -> Result<Foo, SomeErrorType> {
            let fooParse = Foo::try_parse(src);
            return match fooParse {
                Ok(foo) => foo;
                Err(error) => SomeErrorType::from_foo_error(error)
            };   
        }
    }
    

    This is entirely valid for a Rust trait. The crate providing the Parseable trait can provide implementations for types in other crates—for example, you want to provide parsing helpers for things in the std crate (Rust's standard library).

    The same thing might look like this in C#:

    class SomeErrorType : Exception { ... }
    
    trait interface IParseable<T> {
        static T Parse(string src, ...);
        static Result<T, SomeErrorType> TryParse(string src, ...);
    }
    
    trait class IParseable<Foo> {
        static Foo Parse(string src, ...) {
            return Foo.Parse(src, ...);
        }
    
        static Result<Foo, SomeErrorType> TryParse(string src, ...) {
            // Notice this example calls the exception-throwing Parse method
            // in order to get useful error output. :( This is because TryParse
            // with a useful error via the Result type is not a pattern in .NET.
            try {
                return Foo.Parse(src, ...);
            } catch (Exception e) {
                return new SomeErrorType(e);
            }
        }
    }
    
    // Elsewhere...
    
    public static class StringExtensions
    {
        public static T Parse<T>(this string src, params object[] args) {
            // Implementation elided, but can check if `T` implements `IParseable<T>`
            // or if it is provided by a trait, and call `Parse` via the interface.
            …
        }
    }
    

    Note that this is syntax I made up on the fly—it should not be taken as an endorsement of any kind, only wishful thinking.

    With the trait in place, you can call "someString".TryParse<Foo>(…) as if it were a regular method, even if Foo did not define Parse or TryParse. The restrictions follow the same pattern as extension methods, but the method appears on the type parameter T.

    The "smart" parsing implementation in Oxide allows a makeshift version of this. It adds Parse<T> and TryParse<T> methods as extension methods on string (as the above example does), and uses reflection to search for Parse and TryParse methods. First, the given type T is searched (in the case of standard library types that provide those methods), and then, all loaded assemblies are searched for extension methods.

    This is not very performant the first time any type T is used, as you might imagine. Subsequent invocations are fast due to caching of the resulting generated delegate.

    This is not intended to be a seriously used API, and is likely to be removed in Oxide 2.0. In the meantime, it was fun writing it!

    • Improve this Doc
    Back to top Generated by DocFX