Skip to content

arithmetic comparison and boolean operators

Stéphane Lozier edited this page Jan 18, 2021 · 1 revision

17 Arithmetic, Comparison, and Boolean Operators

Sympl supports addition, subtraction, multiplication, division, less than, greater than, equals, and not equal as the obvious corresponding Expression Tree nodes. It support and, or, and not logical operators (see section for their semantics). Here are example Sympl expressions:

(+ 3 5)

(+ (* 10 x) (/ y 2))

(< x max-fixnum)

(or arg default-value)

(= (not (and a b))

(or (not a) (not b)))

17.1 Analysis and Code Generation for Binary Operations

All these operators are all keyword forms that AnalyzeBinaryExpr in etgen.cs analyzes. It picks off and and or to handle specially since they are conditionally evaluating and need to support Sympl's truth value semantics (see section on IF expressions). Otherwise, AnalyzeBinaryExpr just emits a BinaryOperation DynamicExpression using the operator stored in the SymplBinaryExpr AST node.

Here's the code for AnalyzeBinaryExpr, which is discussed further below:

public static Expression AnalyzeBinaryExpr(SymplBinaryExpr expr,

AnalysisScope scope) {

if (expr.Operation == ExpressionType.And) {

return AnalyzeIfExpr(

new SymplIfExpr(

expr.Left, expr.Right, null),

scope);

} else if (expr.Operation == ExpressionType.Or) {

// (Let* (tmp1 x)

// (If tmp1 tmp1

// (Let* (tmp2 y) (If tmp2 tmp2))))

IdOrKeywordToken tmp2 = new IdOrKeywordToken(

"__tmpLetVariable2");

var tmpExpr2 = new SymplIdExpr(tmp2);

var binding2 = new LetBinding(tmp2, expr.Right); ;

var ifExpr2 = new SymplIfExpr(

tmpExpr2, tmpExpr2, null);

var letExpr2 = new SymplLetStarExpr(

new[] { binding2 },

new[] { ifExpr2 });

// Build outer let*

IdOrKeywordToken tmp1 = new IdOrKeywordToken(

"__tmpLetVariable1");

var tmpExpr1 = new SymplIdExpr(tmp1);

LetBinding binding1 = new LetBinding(tmp1, expr.Left); ;

SymplExpr ifExpr1 = new SymplIfExpr(

tmpExpr1, tmpExpr1, letExpr2);

return AnalyzeLetStarExpr(

new SymplLetStarExpr(

new[] { binding1 },

new[] { ifExpr1 }

),

scope

);

}

return Expression.Dynamic(

scope.GetRuntime().GetBinaryOperationBinder(expr.Operation),

typeof(object),

AnalyzeExpr(expr.Left, scope),

AnalyzeExpr(expr.Right, scope));

Because and and or have equivalent semantic to IF (with some temporary bindings for or), the code above creates ASTs for IF and re-uses the AnalyzeIfExpr and AnalyzeLetStarExpr. Sympl could also have "open coded" the equivalent Expression Tree code generation here.

If the operation is other than and and or, AnalyzeBinaryExpr emits a DynamicExpression with a SymplBinaryOperationBinder. See section for a discussion of why this method calls GetBinaryOperationBinder rather than just calling the constructor. The reason Sympl uses a DynamicExpression when it only supports the static built-in semantics of Expression Trees is for interoperability with dynamic languages from other languages or libraries that might flow though a Sympl program.

17.2 Analysis and Code Generation for Unary Operations

The only unary operation Sympl supports is logical negation. Here's the code for AnalyzeUnaryExpr in etgen.cs:

public static Expression AnalyzeUnaryExpr(SymplUnaryExpr expr,

AnalysisScope scope) {

if (expr.Operation == ExpressionType.Not) {

return Expression.Not(WrapBooleanTest(

AnalyzeExpr(expr.Operand,

scope)));

}

return Expression.Dynamic(

scope.GetRuntime()

.GetUnaryOperationBinder(expr.Operation),

typeof(object),

AnalyzeExpr(expr.Operand, scope));

Execution never reaches the DynamicExpression result. This is there as plumbing and an example should Sympl support other unary operations, such as binary or arithmetic negation.

Sympl's logical not translates directly to an Expression Tree Not node as long as the operand expression has a Boolean type. Because of Sympl's truth semantics (see section on IF expressions), AnalyzeUnaryExpr calls WrapBooleanTest which results in an Expression with Type bool.

17.3 SymplBinaryOperationBinder

Binding binary operations is pretty easy in Sympl. Here's the code for the binder's FallbackBinaryOperation in runtime.cs:

public override DynamicMetaObject FallbackBinaryOperation(

DynamicMetaObject target, DynamicMetaObject arg,

DynamicMetaObject errorSuggestion) {

var restrictions = target.Restrictions.Merge(arg.Restrictions)

.Merge(BindingRestrictions.GetTypeRestriction(

target.Expression, target.LimitType))

.Merge(BindingRestrictions.GetTypeRestriction(

arg.Expression, arg.LimitType));

return new DynamicMetaObject(

RuntimeHelpers.EnsureObjectResult(

Expression.MakeBinary(

this.Operation,

Expression.Convert(target.Expression, target.LimitType),

Expression.Convert(arg.Expression, arg.LimitType))),

restrictions);

This function gathers all the restrictions for the arguments and merges them with the target's restrictions. Then it also merges in restrictions to ensure the arguments are the same LimitType as they have during this pass through the CallSite; the rule produced is only good for those types.

Then FallbackBinaryOperation returns a DynamicMetaObject with a BinaryExpression. The arguments are wrapped in ConvertExpressions to ensure they have the strict typing required for the BinaryExpression node returned. Recall that the argument's expression type may be more general than the actual LimitType they have at run time. The result BinaryExpression also passes through EnsureObjectResult in case it needs to be wrapped to ensure it is strictly typed as assignable to object. For more information, see section 3.2.4.

17.4 SymplUnaryOperationBinder

As stated above, Sympl never really uses its UnaryOperationBinder. It exists a plumbing for future unary features and as an example. It is exactly like FallbackBinaryOperation except that it only has one argument to process.

Here's the code form runtime.cs.

public override DynamicMetaObject FallbackUnaryOperation(

DynamicMetaObject target,

DynamicMetaObject errorSuggestion) {

return new DynamicMetaObject(

RuntimeHelpers.EnsureObjectResult(

Expression.MakeUnary(

this.Operation,

Expression.Convert(target.Expression, target.LimitType),

target.LimitType)),

target.Restrictions.Merge(

BindingRestrictions.GetTypeRestriction(

target.Expression, target.LimitType)));

SymPL Implementation on the Dynamic Language Runtime

Frontmatter
1 Introduction
  1.1 Sources
  1.2 Walkthrough Organization
2 Quick Language Overview
3 Walkthrough of Hello World
  3.1 Quick Code Overview
  3.2 Hosting, Globals, and .NET Namespaces Access
    3.2.1 DLR Dynamic Binding and Interoperability -- a Very Quick Description
    3.2.2 DynamicObjectHelpers
    3.2.3 TypeModels and TypeModelMetaObjects
    3.2.4 TypeModelMetaObject's BindInvokeMember -- Finding a Binding
    3.2.5 TypeModelMetaObject.BindInvokeMember -- Restrictions and Conversions
  3.3 Import Code Generation and File Module Scopes
  3.4 Function Call and Dotted Expression Code Generation
    3.4.1 Analyzing Function and Member Invocations
    3.4.2 Analyzing Dotted Expressions
    3.4.3 What Hello World Needs
  3.5 Identifier and File Globals Code Generation
  3.6 Sympl.ExecuteFile and Finally Running Code
4 Assignment to Globals and Locals
5 Function Definition and Dynamic Invocations
  5.1 Defining Functions
  5.2 SymplInvokeBinder and Binding Function Calls
6 CreateThrow Runtime Binding Helper
7 A Few Easy, Direct Translations to Expression Trees
  7.1 Let* Binding
  7.2 Lambda Expressions and Closures
  7.3 Conditional (IF) Expressions
  7.4 Eq Expressions
  7.5 Loop Expressions
8 Literal Expressions
  8.1 Integers and Strings
  8.2 Keyword Constants
  8.3 Quoted Lists and Symbols
    8.3.1 AnalyzeQuoteExpr -- Code Generation
    8.3.2 Cons and List Keyword Forms and Runtime Support
9 Importing Sympl Libraries and Accessing and Invoking Their Globals
10 Type instantiation
  10.1 New Keyword Form Code Generation
  10.2 Binding CreateInstance Operations in TypeModelMetaObject
  10.3 Binding CreateInstance Operations in FallbackCreateInstance
  10.4 Instantiating Arrays and GetRuntimeTypeMoFromModel
11 SymplGetMemberBinder and Binding .NET Instance Members
12 ErrorSuggestion Arguments to Binder FallbackX Methods
13 SymplSetMemberBinder and Binding .NET Instance Members
14 SymplInvokeMemberBinder and Binding .NET Member Invocations
  14.1 FallbackInvokeMember
  14.2 FallbackInvoke
15 Indexing Expressions: GetIndex and SetIndex
  15.1 SymplGetIndexBinder's FallbackGetIndex
  15.2 GetIndexingExpression
  15.3 SymplSetIndexBinder's FallbackSetIndex
16 Generic Type Instantiation
17 Arithmetic, Comparison, and Boolean Operators
  17.1 Analysis and Code Generation for Binary Operations
  17.2 Analysis and Code Generation for Unary Operations
  17.3 SymplBinaryOperationBinder
  17.4 SymplUnaryOperationBinder
18 Canonical Binders or L2 Cache Sharing
19 Binding COM Objects
20 Using Defer When MetaObjects Have No Value
21 SymPL Language Description
  21.1 High-level
  21.2 Lexical Aspects
  21.3 Built-in Types
  21.4 Control Flow
    21.4.1 Function Call
    21.4.2 Conditionals
    21.4.3 Loops
    21.4.4 Try/Catch/Finally and Throw
  21.5 Built-in Operations
  21.6 Globals, Scopes, and Import
    21.6.1 File Scopes and Import
    21.6.2 Lexical Scoping
    21.6.3 Closures
  21.7 Why No Classes
  21.8 Keywords
  21.9 Example Code (mostly from test.sympl)
22 Runtime and Hosting
  22.1 Class Summary
23 Appendixes
  23.1 Supporting the DLR Hosting APIs
    23.1.1 Main and Example Host Consumer
    23.1.2 Runtime.cs Changes
    23.1.3 Sympl.cs Changes
    23.1.4 Why Not Show Using ScriptRuntime.Globals Namespace Reflection
    23.1.5 The New DlrHosting.cs File
  23.2 Using the Codeplex.com DefaultBinder for rich .NET interop
  23.3 Using Codeplex.com Namespace/Type Trackers instead of ExpandoObjects
  23.4 Using Codeplex.com GeneratorFunctionExpression


Other documents:

Dynamic Language Runtime
DLR Hostirng Spec
Expression Trees v2 Spec
Getting Started with the DLR as a Library Author
Sites, Binders, and Dynamic Object Interop Spec

Clone this wiki locally