//
//  MPTerm.h
//  MathKit
//
//  Created by Kim Wittenburg on 11.11.14.
//  Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//


/*!
 @header
 This file contains the <code>MPTerm</code> class.
 
 The <code>MPTerm</code> class is used to evaluate a mathematical expression. A
 term is a purely mathematical representation of a part of an expression. A term
 can be defined with three rules:
 
 1. A number is a term.
 2. A variable is a term.
 3. A connection of terms with zero or more symbols is also a term.
 
 Every mathematical operation (term) is defined in terms of a concrete subclass
 of <code>MPTerm</code> and sub-terms (children). For example the expression
 <code>2+3⋅4</code> would be a @link //apple_ref/occ/cl/MPSumTerm sum term@/link
 with two children: A @link //apple_ref/occ/cl/MPNumber number@/link and a @link
 //apple_ref/occ/cl/MPProductTerm product term@/link. The product term in turn
 has two children which are both numbers.
 
 Subclasses should do the following:
 1. Subclasses must implement the <code>@link doEvaluation:@/link</code> method.
 2. Subclasses should be immutable and thus define <code>readonly</code>
    accessors for any children as well as an <code>init</code> method that
    supplies the necessary values.
 
 If a term has children that define variables to be used in one or more of the
 other children <code>MPTerm</code> offers methods for defining, accessing and
 undefining variable values. Because a term can not define variables that are
 accessible in any of its sibling or parent terms all variables defined in a term
 are automatically undefined when the term is finished evaluating.
 */


/*!
    @define     ReturnIfNil
    @abstract   This macro returns a <code>value</code> if a <code>test</code>
                object is <code>nil</code>.
 
    @param      test
                The object to be tested. If this parameter is <code>nil</code>
                the macro returns <code>value</code>.
 
    @param      value
                The value to be returned if <code>test</code> is
                <code>nil</code>.
 */
#define ReturnIfNil(test, value) if (test == nil) return value


/*!
    @define     ReturnNilIfNil
    @abstract   This macro returns <code>nil</code> if a <code>test</code> object
                is <code>nil</code>.
 
    @param      test
                The object to be tested. If this parameter is <code>nil</code>
                the macro returns <code>nil</code>.
 */
#define ReturnNilIfNil(test) if (test == nil) return nil



@class MPTerm;


/*!
    @class      MPTerm
    @abstract   <code>MPTerm</code> is a half abstract class that is the base
                class for representing an evaluatable mathematical expression.
 
    @discussion <code>MPTerm</code> should not be instanciated directly. Instead
                one of the concrete subclasses should be used.
 */
@interface MPTerm : NSObject

/*!
    @method     evaluate:
    @abstract   Evaluates the receiver.
 
    @discussion The result of the evaluation is up to the concrete subclass of
                <code>MPTerm</code>. Note however that subclasses should
                <b>not</b> override this method. To implement the custom
                evaluation behaviour implement the <code>@link
                doEvaluation:@/link</code> method. To evaluate a term (including
                children of terms) do use this method.
 
    @param      error
                If an error occurs during evaluation it is indirecly returned
                through this parameter. If you are not interested in errors, pass
                <code>NULL</code>.
 
    @return     A <code>NSDecimalNumber</code> containing the result of the
                evaluation. Depending on the term this should be double precision
                or higher.
 */
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error;


/*!
    @method     doEvaluation:
    @abstract   Performs the actual evaluation of the receiver.
 
    @discussion This method must be implemented by subclasses but should never be
                called directly. Call <code>@link evaluate:@/link</code> instead.
 
    @param      error
                If an error occurs during evaluation it is indirecly returned
                through this parameter. If you are not interested in errors, pass
                <code>NULL</code>.
 
    @return     A <code>NSDecimalNumber</code> containing the result of the
                evaluation. Depending on the term this should be double precision
                or higher.
 */
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error;


/*!
    @method     defineVariable:value:error:
    @abstract   Defines a variable in the shared evaluation context.
 
    @discussion A defined variable is available until it is either undefined or
                the end of the <code>@link doEvaluation:@/link</code> method is
                reached at which point all variables defined in that method are
                undefined automatically. If the <code>variableName</code> has
                already been defined in the same method this method overwrites
                the value for the variable. If it has been defined in the
                evaluation method of a parent term this method fails and returns
                <code>NO</code>.
 
    @param      variableName
                The variable to be defined.
 
    @param      value
                The value for the variable.
 
    @param      error
                If <code>variableName</code> has already been defined in a parent
                term this parameter is set to an error object that informs the
                user about a variable redefinition. If you are not interested in
                errors, pass <code>NULL</code>.
    
    @return     <code>YES</code> if the variable could be defined,
                <code>NO</code> if it has previously been declared in a parent
                term of the currently evaluated one.
 */
- (BOOL)defineVariable:(NSString *)variableName
                 value:(NSDecimalNumber *)value
                 error:(NSError *__autoreleasing *)error;


/*!
    @method     valueForVariable:
    @abstract   Returns the value for a variable from the shared evaluation
                context.
 
    @discussion If <code>variableName</code> is not defined this method returns
                <code>nil</code>. Normally you do not need to call this method
                yourself.
 
    @param      variableName
                The variable whose value is requested.
 
    @param      error
                If <code>variableName</code> is not defined this parameter is set
                to an error object that informs the user about an undefined
                variable. If you are not interested in errors, pass
                <code>NULL</code>.
 
    @return     The value associated with <code>variableName</code> or
                <code>nil</code> if it is undefined.
 */
- (NSDecimalNumber *)valueForVariable:(NSString *)variableName
                                error:(NSError *__autoreleasing *)error;


/*!
    @method     undefineVariable:
    @abstract   Removes <code>variableName</code> from the shared evaluation
                context.

    @discussion The variable is only removed if it exists and was defined in the
                same evaluation method. Variables defined by parent terms can not
                be undefined. Normally you do not need to call this method
                yourself since all variables defined during the evaluation of a
                term are automatically undefined afterwards.

    @param      variableName
            The name of the variable to be undefined.
 */
- (void)undefineVariable:(NSString *)variableName;

@end