Skip to content

literal expressions

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

8 Literal Expressions

This section describes some Sympl features added along with eq before IF and loop. These features are quoted symbols, quoted lists, and literal keyword constants. They enable more testing of IF and loop. This section also describes integer and string literals which of course were added at the beginning along with the lexer. You could skip this section unless you're curious about the built-in list objects and their runtime helpers.

8.1 Integers and Strings

These come out of the parser as SymplLiteralExprs with the value tucked in them. This snippet from AnalyzeExpr in etgen is the code generation:

if (expr is SymplLiteralExpr) {

return Expression.Constant(((SymplLiteralExpr)expr).Value)

8.2 Keyword Constants

Literal keyword constants are nil, false, and true. Sympl includes nil for its built-in lists, and it includes true and false for easier .NET interoperability. Sympl could do more work in its runtime binders to map nil to false for Boolean parameters, and map anything else to true for Boolean parameters. Adding this to Sympl didn't seem to add any new lessons given the TypeModel mappings binders do.

These literal keywords come out of the parser as SymplIdExprs. Section discussed part of AnalyzeIdExpr from etgen.cs, but it omitted the literal keywords branch shown here:

public static Expression AnalyzeIdExpr(SymplIdExpr expr,

AnalysisScope scope) {

if (expr.IdToken.IsKeywordToken) {

if (expr.IdToken == KeywordToken.Nil)

return Expression.Constant(null, typeof(object));

else if (expr.IdToken == KeywordToken.True)

return Expression.Constant(true);

else if (expr.IdToken == KeywordToken.False)

return Expression.Constant(false);

else

throw new InvalidOperationException(

"Internal: unrecognized keyword literal constant.");

} else {

var param = FindIdDef(expr.IdToken.Name, scope);

if (param != null) {

return param;

} else {

return Expression.Dynamic(

new SymplGetMemberBinder(expr.IdToken.Name),

typeof(object),

scope.GetModuleExpr());

Handling this is straightforward; just turn them into ConstantExpressions with obvious .NET representations.

8.3 Quoted Lists and Symbols

Sympl does stand for Symbolic Programming Language, so it needs to have symbols and built-in lists as a nod to its ancestry in Lisp-like languages. Sympl also includes these because it provided a nice domain for writing a little Sympl library, lists.sympl, and demonstrating loading libraries and cross-module access. Quoted literals are symbols, numbers, strings, lists, or lists of these things. A Symbol is an object with a name that's interned into a Sympl runtime's symbol table. All Symbols with the same name are the same object, or eq. Sympl's Symbols are similar to Lisp's, but don't have all the same slots.

8.3.1 AnalyzeQuoteExpr -- Code Generation

The high-level idea is that quoted constants are literal constants, so Sympl builds the constant and burns it into the resulting Expression Tree as a ConstantExpression. Here's the code for AnalyzeQuoteExpr and its helper from etgen.cs:

public static Expression AnalyzeQuoteExpr(SymplQuoteExpr expr,

AnalysisScope scope) {

return Expression.Constant(MakeQuoteConstant(

expr.Expr, scope.GetRuntime()));

}

private static object MakeQuoteConstant(object expr,

Sympl symplRuntime) {

if (expr is SymplListExpr) {

SymplListExpr listexpr = (SymplListExpr)expr;

int len = listexpr.Elements.Length;

var exprs = new object[len];

for (int i = 0; i < len; i++) {

exprs[i] = MakeQuoteConstant(listexpr.Elements[i],

symplRuntime);

}

return Cons._List(exprs);

} else if (expr is IdOrKeywordToken) {

return symplRuntime.MakeSymbol(

((IdOrKeywordToken)expr).Name);

} else if (expr is LiteralToken) {

return ((LiteralToken)expr).Value;

} else {

throw new InvalidOperationException(

"Internal: quoted list has -- " + expr.ToString());

As stated above, AnalyzeQuoteExpr just creates a ConstantExpression. MakeQuoteConstant does the work, and it takes a Sympl runtime instance both for runtime helper functions and to intern symbols as they are created.

Skipping the first case for a moment, if the expression is an identifier or keyword, Sympl interns its name to create a Symbol as the resulting constant. If the expression is a literal constant (string, integer, nil, false, true), the resulting constant is just the value.

If the expression is a SymplListExpr, then MakeQuoteConstant recurses to make constants out of the elements. Then it calls the runtime helper function Cons._List to create a Sympl built-in list as the constant to emit. See the next section for more on lists and this helper function.

8.3.2 Cons and List Keyword Forms and Runtime Support

Sympl's lists have the structure of Lisp lists, formed from chaining Cons cells together. A Cons cell has two pointers, conventionally with the first pointing to data and the second pointing to the rest of the list (another Cons cell). You can also create a Cons cell that just points to two objects even if the second object is not a Cons cell (or list tail).

Sympl provides a cons keyword form and a list keyword form for creating lists:

(cons 'a (cons 'b nil)) --> (a b)

(cons 'a (cons 'b 'c)) --> (a b . c)

(list 'a 'b 3 "c") --> (a b 3 "c")

(cons 'a (list 2 '(b c) 3)) --> (a 2 (b c) 3)

Here is the code for analyzing cons and list from etgen.cs:

public static Expression AnalyzeConsExpr (SymplConsExpr expr,

AnalysisScope scope) {

var mi = typeof(RuntimeHelpers).GetMethod("MakeCons");

return Expression.Call(mi, Expression.Convert(

AnalyzeExpr(expr.Left, scope),

typeof(object)),

Expression.Convert(

AnalyzeExpr(expr.Right, scope),

typeof(object)));

}

public static Expression AnalyzeListCallExpr

(SymplListCallExpr expr, AnalysisScope scope) {

var mi = typeof(Cons).GetMethod("_List");

int len = expr.Elements.Length;

var args = new Expression[len];

for (int i = 0; i < len; i++) {

args[i] = Expression.Convert(AnalyzeExpr(expr.Elements[i],

scope),

typeof(object));

}

return Expression.Call(mi, Expression

.NewArrayInit(typeof(object),

args));

Cons is just a Call node for invoking RuntimeHelpers.MakeCons. Sympl analyzes the left and right expressions, and wraps them in Convert nodes to satisfy the Call factory and make sure conversions are explicit for the Expression Tree compiler and .NET CLR. Emitting the code to call MakeCons in the IronPython implementation is not this easy. You can't call the Call factory with MethodInfo for IronPython methods. You need to emit an Invoke DynamicExpression with a no-op InvokeBinder. See the code for comments and explanation.

The List keyword form analyzes all the arguments and ultimately just emits a Call node to Cons._List. Because the helper function takes a params array, Sympl emits a NewArrayInit node that results in an array of objects. Each of the element arguments needs to be wrapped in a Convert node to make all the strict typing consistent for Expression Trees. The same comment as above holds about this code generation being easier in C# than IronPython.

You can look at RuntimeHelpers.MakeCons and Cons._List in runtime.cs to see the code. It's not worth excerpting for this document, but there are a couple of comments to make. The reason Sympl has MakeCons at this point in Sympl's implementation evolution is that Sympl does not have type instantiation. Also, without some basic SymplGetMemberBinder or SymplSetMemberBinder that just looked up the name and assumed it was a property, for example, Sympl couldn't have some of the tests it had at this time with Cons members. The IronPython implementation at this point still didn't need the binder to do more than convey the name because the IronPython implementation of Cons had a DynamicMetaObject for free from IronPython, which handled the binding. Since IronPython ignores the ignoreCase flag, the tests had to be written with uppercase First and Rest names.

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