606 lines
No EOL
35 KiB
TeX
606 lines
No EOL
35 KiB
TeX
\chapter{Collecting User Feedback for Syntactic Proposals}
|
|
|
|
The goal for this project is to utilize users familiarity with their own code to gain early and worthwhile user feedback on new
|
|
syntactic proposals for EcmaScript.
|
|
|
|
\section{The core idea}
|
|
|
|
When a use of EcmaScript wants to suggest a change to the language, the idea of the change has to be described in a Proposal. A proposal is a general way of describing a change and its requirements, this is done by a language specification, motivation for the idea, and general discussion around the proposed change. A proposal ideally also needs backing from the community of users that use EcmaScript, this means the proposal has to be presented to users some way. This is currently done by many channels, such as polyfills, code examples, and as beta features of the main JavaScript engines, however, this paper wishes to showcase proposals to users by using a different avenue.
|
|
|
|
Users of EcmaScript have a familiarity with code they themselves have written. This means they have knowledge of how their own code works and why they might have written it a certain way. This project aims to utilize this pre-existing knowledge to showcase new proposals for EcmaScript. Showcasing proposals this way will allow users to focus on what the proposal actually entails, instead of focusing on the examples written by the proposal author.
|
|
|
|
Further in this chapter, we will be discussing the current version and future version of EcmaScript. What we are referring to in this case is with set of problems a proposal is trying to solve, if that proposal is allowed into EcmaScript as part of the language, there will be a future way of solving said problems. The current way is the current status quo when the proposal is not part of EcmaScript, and the future version is when the proposal is part of EcmaScript and we are utilizing the new features of said proposal.
|
|
|
|
The program will allow the users to preview proposals way before they are part of the language. This way the committee can get useful feedback from users of the language earlier in the proposal process. Using the users familiarity will ideally allow for a more efficient process developing EcmaScript.
|
|
|
|
\subsection{Applying a proposal}
|
|
|
|
The way this project will use the pre-existing knowledge a user has of their own code is to use that code as base for showcasing a proposals features. Using the users own code as base requires the following steps in order to automatically implement the examples that showcase the proposal inside the context of the users own code.
|
|
|
|
The ide is to identify where the features and additions of a proposal could have been used. This means identifying parts of the users program that use pre-existing EcmaScript features that the proposal is interacting with and trying to solve. This will then identify all the different places in the users program the proposal can be applied. This step is called \textit{matching} in the following chapters
|
|
|
|
Once we have matched all the parts of the program the proposal could be applied to, the users code has to be transformed to use the proposal, this means changing the code to use a possible future version of JavaScript. This step also includes keeping the context and functionality of the users program the same, so variables and other context related concepts have to be transferred over to the transformed code.
|
|
|
|
The output of the previous step is then a set of code pairs, where one a part of the users original code, and the second is the transformed code. The transformed code is then ideally a perfect replacement for the original user code if the proposal is part of EcmaScript. These pairs are used as examples to present to the user, presented together so the user can see their original code together with the transformed code. This allows for a direct comparison and an easier time for the user to understand the proposal.
|
|
|
|
The steps outlined in this section require some way of defining matching and transforming of code. This has to be done very precisely and accurately in order to avoid examples that are wrong. Imprecise definition of the proposal might lead to transformed code not being a direct replacement for the code it was based upon. For this we suggest two different methods, a definition written in a custom DSL \DSL and a definition written in a self-hosted way only using EcmaScript as a language as definition language. Read more about this in SECTION HERE.
|
|
|
|
\section{Applicable proposals}
|
|
\label{sec:proposals}
|
|
|
|
A proposal for EcmaScript is a suggested change for the language, in the case of EcmaScript this comes in the form of an addition to the language, as EcmaScript does not allow for breaking changes. There are many different kinds of proposals, this project focuses exclusively on Syntactic Proposals.
|
|
|
|
\subsection{Syntactic Proposals}
|
|
|
|
A syntactic proposal, is a proposal that contains only changes to the syntax of a language. This means, the proposal contains either no, or very limited change to functionality, and no changes to semantics. This limits the scope of proposals this project is applicable to, but it also focuses solely on some of the most challenging proposals where the users of the language might have the strongest opinions.
|
|
|
|
\subsection{Simple example of a syntactic proposal}
|
|
|
|
Consider a imaginary proposal \exProp. This proposal describes adding an optional keyword for declaring numerical variables if the expression of the declaration is a numerical literal.
|
|
|
|
This proposal will look something like this:
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Example of imaginary proposal \exProp}, label={ex:proposal}]
|
|
// Original code
|
|
let x = 100;
|
|
let b = "Some String";
|
|
let c = 200;
|
|
|
|
// Code after application of proposal
|
|
int x = 100;
|
|
let b = "Some String";
|
|
let c = 200;
|
|
\end{lstlisting}
|
|
|
|
See that in \ref{ex:proposal} the change is optional, and is not applied to the declaration of \textit{c}, but it is applied to the declaration of \textit{x}. Since the change is optional to use, and essentially is just \textit{syntax sugar}, this proposal does not make any changes to functionality or semantics, and can therefore be categorized as a syntactic proposal.
|
|
|
|
\subsection{\cite{Proposal:DiscardBindings}{Discard Bindings}}
|
|
|
|
The proposal \discardBindings is classified as a Syntactic Proposal, as it contains no change to the semantics of EcmaScript. This proposal is created to allow for discarding objects when using the feature of unpacking objects/arrays on the left side of an assignment. The whole idea of this proposal is to avoid declaring unused temporary variables.
|
|
|
|
Unpacking when doing an assignment refers to assigning internal fields of an object/array directly in the assignment rather than using a temporary variable. See \ref{ex:unpackingObject} for an example of unpacking an object and \ref{ex:unpackingArr}.
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Example of unpacking Object}, label={ex:unpackingObject}]
|
|
// previous
|
|
let temp = { a:1, b:2, c:3, d:4 };
|
|
let a = temp.a;
|
|
let b = temp.b;
|
|
|
|
// unpacking
|
|
let {a,b ...rest} = { a:1, b:2, c:3, d:4 };
|
|
rest; // { c:3, d:4 }
|
|
\end{lstlisting}
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Example of unpacking Array}, label={ex:unpackingArr}]
|
|
// previous
|
|
let tempArr = [ 0, 2, 3, 4 ];
|
|
let a = tempArr[0]; // 0
|
|
let b = tempArr[1] // 2
|
|
|
|
//unpacking
|
|
let [a, b, _1, _2] = [ 0, 2, 3, 4 ]; // a = 0, b = 2, _1 = 3, _2 = 4
|
|
\end{lstlisting}
|
|
|
|
|
|
|
|
In EcmaScripts current form, it is required to assign every part of an unpacked object/array to some identifier. The current status quo is to use \_ as a sign it is meant to be discarded. This proposal suggests a specific keyword \textit{void} to be used as a signifier whatever is at that location should be discarded.
|
|
|
|
This feature is present in other languages, such as Rust wildcards, Python wildcards and C\# using statement and discards. In most of these other languages, the concept of discard is a single \_. In EcmaScript the \_ token is a valid identifier, therefore this proposal suggests the use of the keyword \textit{void}. This keyword is already is reserved as part of function definitions where a function is meant to have no return value.
|
|
|
|
This proposal allows for the \textit{void} keyword to be used in a variety of contexts. Some simpler than others but all following the same pattern of allowing discarding of bindings to an identifier. It is allowed anywhere the \textit{BindingPattern}, \textit{LexicalBinding} or \textit{DestructuringAssignmentTarget} features are used in EcmaScript. This means it can be applied to unpacking of objects/arrays, in callback parameters and class methods.
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Example discard binding with variable discard}]
|
|
using void = new UniqueLock(mutex);
|
|
// Not allowed on top level of var/let/const declarations
|
|
const void = bar(); // Illegal
|
|
\end{lstlisting}
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Example Object binding and assignment pattern}]
|
|
let {b:void, ...rest} = {a:1, b:2, c:3, d:4}
|
|
rest; // {a:1, c:3, d:4};
|
|
\end{lstlisting}
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={
|
|
Example Array binding and assignment pattern. It is not clear to the reader that in line 8 we are consuming 2 or 3 elements of the iterator. In the example on line 13 we see that is it more explicit how many elements of the iterator is consumed
|
|
}]
|
|
function* gen() {
|
|
for (let i = 0; i < Number.MAX_SAFE_INTEGER; i++) {
|
|
console.log(i);
|
|
yield i;
|
|
}
|
|
}
|
|
|
|
const iter = gen();
|
|
const [a, , ] = iter;
|
|
// prints:
|
|
// 0
|
|
// 1
|
|
|
|
const [a, void] = iter; // author intends to consume two elements
|
|
// vs.
|
|
const [a, void, void] = iter; // author intends to consume three elements
|
|
\end{lstlisting}
|
|
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Example discard binding with function parameters. This avoids needlessly naming parameters of a callback function that will remain unused.}]
|
|
// project an array values into an array of indices
|
|
const indices = array.map((void, i) => i);
|
|
|
|
// passing a callback to `Map.prototype.forEach` that only cares about
|
|
// keys
|
|
map.forEach((void, key) => { });
|
|
|
|
// watching a specific known file for events
|
|
fs.watchFile(fileName, (void, kind) => { });
|
|
|
|
// ignoring unused parameters in an overridden method
|
|
class Logger {
|
|
log(timestamp, message) {
|
|
console.log(`${timestamp}: ${message}`);
|
|
}
|
|
}
|
|
|
|
class CustomLogger extends Logger {
|
|
log(void, message) {
|
|
// this logger doesn't use the timestamp...
|
|
}
|
|
}
|
|
|
|
// Can also be utilized for more trivial examples where _ becomes
|
|
// cumbersome due to multiple discarded parameters.
|
|
doWork((_, a, _1, _2, b) => {});
|
|
// vs.
|
|
doWork((void, a, void, void, b) => {
|
|
});
|
|
\end{lstlisting}
|
|
|
|
|
|
The grammar of this proposal is precisely specified in the specification found in the \href{https://github.com/tc39/proposal-discard-binding?tab=readme-ov-file#object-binding-and-assignment-patterns}{proposal definition} on github.
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Grammar of Discard Binding}]
|
|
var [void] = x; // via: BindingPattern :: `void`
|
|
var {x:void}; // via: BindingPattern :: `void`
|
|
|
|
let [void] = x; // via: BindingPattern :: `void`
|
|
let {x:void}; // via: BindingPattern :: `void`
|
|
|
|
const [void] = x; // via: BindingPattern :: `void`
|
|
const {x:void} = x; // via: BindingPattern :: `void`
|
|
|
|
function f(void) {} // via: BindingPattern :: `void`
|
|
function f([void]) {} // via: BindingPattern :: `void`
|
|
function f({x:void}) {} // via: BindingPattern :: `void`
|
|
|
|
((void) => {}); // via: BindingPattern :: `void`
|
|
(([void]) => {}); // via: BindingPattern :: `void`
|
|
(({x:void}) => {}); // via: BindingPattern :: `void`
|
|
|
|
using void = x; // via: LexicalBinding : `void` Initializer
|
|
await using void = x; // via: LexicalBinding : `void` Initializer
|
|
|
|
[void] = x; // via: DestructuringAssignmentTarget : `void`
|
|
({x:void} = x); // via: DestructuringAssignmentTarget : `void`
|
|
\end{lstlisting}
|
|
|
|
|
|
\subsection{Pipeline Proposal}
|
|
|
|
The pipeline proposal is a Syntactic proposal with no change to functionality of EcmaScript, it focuses solely on solving problems related to nesting of function calls and other expressions that allow for a topic reference. A topic reference is a reference to some value based on the current context/topic.
|
|
|
|
The pipeline proposal aims to solve two problems with performing consecutive operations on a value. In EcmaScript there are two main styles of achieving this functionality currently. Nesting calls and chaining calls, these two come with a differing set of challenges when used.
|
|
|
|
Nesting calls is mainly an issue related to function calls with one or more arguments. When doing many calls in sequence the result will be a \textit{deeply nested} call expression.
|
|
|
|
Nested calls has some specific challenges when relating to readability when used. The order of calls go from right to left, which is opposite of the natural reading direction a lot of the users of EcmaScript are used to day to day, this means it is difficult switch reading direction when working out which call happens in which order. When using functions with multiple arguments in the middle of the nested call, it is not intuitive to see what call its arguments belong to, this is also a problem with readability of nested calls, which is the main challenge this proposal is trying to solve. Nested calls are not all bad however, one of the main good points of nested calls is they can be simplified by using temporary variables, while this does introduce its own set of issues, it provides some way of mitigating the readability problem. Another positive side of nested calls is they do not require a specific design to be used, a library developer does not have to design their library around this specific call style.
|
|
|
|
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Deeply nested call with single arguments
|
|
function1(function2(function3(function4(value))));
|
|
|
|
// Deeply nested call with multi argument functions
|
|
function1(function2(function3(value2, function4)), value1);
|
|
\end{lstlisting}
|
|
|
|
|
|
|
|
Chaining solves some of the issues relating to nesting, as it allows for a more natural reading direction left to right when identifying the sequence of call, arguments are naturally grouped together with their respoective function call, and it provides a way of untangling deep nesting. However, solving consecutive operations using chaining has its own set of challenges when used. In order to use chaining, the api of the code you are trying to call has to be designed to allow for chaining. This is not always the case, making using chaining when it is not been designed specifically for very difficult. There are also concepts in JavaScript not supported when using chaining, such as arithmetic operations, literals, await, yield and so on. This is actually the biggest downside of chaining, as it only allows for function calls when used, and if one wants to allow for use of other concepts temporary variables have to be used.
|
|
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Chaining calls
|
|
function1().function2().function3();
|
|
|
|
// Chaining calls with multiple arguments
|
|
function1(value1).function2().function3(value2).function4();
|
|
\end{lstlisting}
|
|
|
|
The pipeline proposal aims to combine the benefits of these two styles without all the challenges each method faces.
|
|
|
|
The main benefit of pipeline is to allow for a similar style to chaining when chaining has not been specifically designed to be applicable. The idea uses syntactic sugar to change the order of writing the calls without influencing the API of the functions. Doing this allows each call to come in the direction of left to right, while still maintaining the modularity of deeply nested function calls.
|
|
|
|
The way the pipeline proposal aims to solve this is to introduce a pipe operator, which takes the result of an expression on the left, and \textit{pipes} it into an expression on the right. The location of where the result is piped to is where the \textit{topic token} is located. All the specifics of the exact token used as a \textit{topic token} and exactly what operator will be used as the pipe operator might be subject to change.
|
|
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Status quo
|
|
var loc = Object.keys(grunt.config( "uglify.all" ))[0];
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// With pipes
|
|
var loc = grunt.config('uglify.all') |> Object.keys(%)[0];
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Status quo
|
|
const json = await npmFetch.json(
|
|
npa(pkgs[0]).escapedName, opts);
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// With pipes
|
|
const json = pkgs[0] |> npa(%).escapedName |> await npmFetch.json(%, opts);
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Status quo
|
|
return filter(obj, negate(cb(predicate)), context);
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// With pipes
|
|
return cb(predicate) |> _.negate(%) |> _.filter(obj, %, context);
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
|
|
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Status quo
|
|
return xf['@@transducer/result'](obj[methodName](bind(xf['@@transducer/step'], xf), acc));
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// With pipes
|
|
return xf
|
|
|> bind(%['@@transducer/step'], %)
|
|
|> obj[methodName](%, acc)
|
|
|> xf['@@transducer/result'](%);
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
|
|
\subsection{Do proposal}
|
|
|
|
The \cite[Do Proposal]{Proposal:DoProposal} is a proposal meant to bring \textit{expression oriented} programming to EcmaScript. Expression oriented programming is a concept taken from functional programming which allows for combining expressions in a very free manor allowing for a highly malleable programming experience.
|
|
|
|
The motivation of the do expression proposal is to create a feature that allows for local scoping of a code block that is treated as an expression. This allows for complex code requiring multiple statements to be confined inside its own scope and the resulting value is returned from the block as an expression. Similar to how a unnamed functions or arrow functions are currently used. The current status quo of how to achieve this behavior is to use unnamed functions and invoke them immediately, or use an arrow function, these two are equivalent to a do expression.
|
|
|
|
The codeblock of a do expression has one major difference from these equivalent functions, as it allows for implicit return of the final expression of the block, and is the resulting value of the entire do expression.
|
|
|
|
The local scoping of this feature allows for a cleaner environment in the parent scope of the do expression. What is meant by this is for temporary variables and other assignments used once can be enclosed inside a limited scope within the do block. Allowing for a cleaner environment inside the parent scope where the do block is defined.
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Current status quo
|
|
let x = () => {
|
|
let tmp = f();
|
|
return tmp + tmp + 1;
|
|
};
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// With do expression
|
|
let x = do {
|
|
let tmp = f();
|
|
tmp + tmp + 1;
|
|
};
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Current status quo
|
|
let x = function(){
|
|
let tmp = f();
|
|
let a = g() + tmp;
|
|
return a - 1;
|
|
}();
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// With do expression
|
|
let x = do {
|
|
let tmp = f();
|
|
let a = g() + tmp;
|
|
a - 1;
|
|
};
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
|
|
|
|
This proposal has some limitations on its usage. Due to the implicit return of the final expression you cannot end a do expression with an \texttt{if} without and \texttt{else}, or a \texttt{loop}.
|
|
|
|
|
|
|
|
\subsection{Await to Promise}
|
|
|
|
This section covers an imaginary proposal that was used to evaluate the program developed in this thesis. This imaginary proposal is less of a proposal and more of just a pure JavaScript transformation example. What this proposal wants to achieve is transforming a function using \texttt{await}, into a function that uses and returns a promise.
|
|
|
|
In order to do this an equivalent way of writing code containing \texttt{await} in the syntax of \texttt{.then()} promise, an equivalent way of writing the same functionality has to be identified. In this case, the equivalent way of expressing this using a promise is consuming the rest of the scope after \texttt{await} was written, and place it inside a \texttt{then(() => {})} function. The variable the await was assigned to has to be used as the argument to the \texttt{.then()} function.
|
|
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Code containing await
|
|
async function a(){
|
|
let b = 9000;
|
|
let something = await asyncFunction();
|
|
let c = something + 100;
|
|
return c + 1;
|
|
}
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Re-written using promises
|
|
async function a(){
|
|
let b = 9000;
|
|
return asyncFunction()
|
|
.then((something) => {
|
|
let c = something + 100;
|
|
return c;
|
|
})
|
|
}
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
|
|
\section{Searching user code for applicable snippets}
|
|
|
|
In order to identify snippets of code in the users codebase where a proposal is applicable we need some way to define patterns of code where we can apply the proposal. To do this, a DSL titled \DSL is used.
|
|
|
|
\subsection{\DSL}
|
|
\label{sec:DSL_DEF}
|
|
|
|
Showcasing a proposal using a users code requires some way of identifying applicable code sections to that proposal. To do this, we have designed a DSL called \DSL , JavaScript Template Query Language. This DSL will contain the entire definition used to identify and transform user code in order to showcase a proposal.
|
|
|
|
\subsection*{Identifying applicable code}
|
|
|
|
In order to identify sections of code a proposal is applicable to, we use templates of JavaScript. These templates are used to identify and match applicable sections of a users code. A matching section for a template is one that produces an exactly equal AST structure, where each node of the AST sections has the same information contained within it. This means templates are matched exactly against the users code, this does not really provide some way of actually querying the code and performing context based transformations, so for that we use \textit{Wildcards} within the template.
|
|
|
|
\textit{Wildcards} are written into the template inside a block denoted by $<$$<$ $>$$>$. Each wildcard has to start with an identifier, which is a way of referring to that wildcard in the definition of the transformation template later. This allows for transferring the context of parts matched to a wildcard into the transformed output, like identifiers, parts of statements or even entire statements all together can be transferred from the original user code into the transformation template.The second part of the wildcard contains a wildcard type expression. A wildcard type expression is a way of defining exactly what types of AST nodes a wildcard will produce a match against, these type expressions use boolean logic together with the AST node-types from \cite{Babel}{BabelJS} to create a very strict way of defining wildcards.
|
|
|
|
\subsubsection*{Wildcard type expressions}
|
|
|
|
Wildcard type expressions allow for writing complex boolean logic on the kinds of nodes a wildcard can be matched against, this means writing the type for a wildcard can be as simple as just \texttt{Expression || Statement}, or as complex as \texttt{((Statement \&\& ! ReturnStatement) \&\& !VariableDeclaration)*}. The operators mean the following, \texttt{\&\&} is logical AND, this means both parts of the expression have to evaluate to true, \texttt{||} means logical OR, so either side of expression can be true for the entire expression to be true, \texttt{!} is the only unary expression, and is logical NOT, so \texttt{!Statement} is any node that is NOT a Statement. There is also this definition \texttt{+}, this is only valid at the top level of the expression, and the pluss operator means this wildcard can be used any number of times in order. This is useful for matching against a series of Statements, while not wanting to match an entire BlockStatement. The final part of a wildcard expression is the AST types used, an example of this would be the wildcard type expression \texttt{ReturnStatement}, which will only match against an AST node of type ReturnStatement. Using the power of the wildcards, we can create templates that can be used for querying a users code for specific code sections that a proposal is applicable to.
|
|
|
|
|
|
\begin{lstlisting}
|
|
let variableName = << expr1: ((CallExpression || Identifier) && !ReturnStatement)+ >>;
|
|
\end{lstlisting}
|
|
|
|
A wildcard section is defined on the right hand side of an assignment statement. This wildcard will match against any AST node classified as a CallExpression or an Identifier.
|
|
|
|
|
|
\subsection{Transforming}
|
|
|
|
When matching sections of the users code has been found, we need some way of defining how to transform those sections to showcase a proposal. This is done by a similar template to applicable to, namely \textit{transform to}, this template describes the general structure of the newly transformed code.
|
|
|
|
A transformation template is used to define how the matches will be transformed after applicable code has been found. The transformation is a general template of the code once the match is replaced in the original AST. However, without transferring over the context from the match, it is just a template search and replace. So in order to transfer the context from the match, wildcards are defined in this template as well. These wildcards use the same block notation found in the applicable to template, however they do not need to contain the types, as those are not needed in the transformation. The only section required in the wildcard is the identifier used in applicable to, this is done in order to know which wildcard match we are taking the context from, and where to place it in the transformation template.
|
|
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Example of transform to template
|
|
const variableName = <<expr1>>;
|
|
\end{lstlisting}
|
|
|
|
|
|
|
|
\subsection{Structure of \DSL}
|
|
\label{sec:DSLStructure}
|
|
|
|
|
|
\DSL is designed to mimic the examples already provided by a proposal champion in the proposals README. These examples can be seen in each of the proposals described in \ref{sec:proposals}. The idea is to allow a similar kind of notation to the examples in order to define the transformations.
|
|
|
|
\subsubsection*{Define proposal}
|
|
|
|
The first part of \DSL is defining the proposal, this is done by creating a named block containing all definitions of templates used for matching alongside their respective transformation. This section is used to contain everything relating to a specific proposal and is meant for easy proposal identification by tooling.
|
|
|
|
\begin{lstlisting}[caption={Example of section containing the pipeline proposal}]
|
|
proposal Pipeline_Proposal{
|
|
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection*{Defining a pair of template and transformation}
|
|
|
|
Each proposal will have 1 or more definitions of a template for code to identify in the users codebase, and its corresponding transformation definition. These are grouped together in order to have a simple way of identifying the corresponding cases of matching and transformations. This section of the proposal is defined by the keyword \textit{case} and a block to contain its related fields. A proposal will contain 1 or more of this section. This allows for matching many different code snippets and showcasing more of the proposal than a single concept the proposal has to offer.
|
|
|
|
\begin{lstlisting}[caption={Example of pair section}]
|
|
case case_name {
|
|
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection*{Template used for matching}
|
|
|
|
In order to define the template used to match, we have another section defined by the keyword \textit{applicable to}. This section will contain the template defined using JavaScript with specific DSL keywords defined inside the template.
|
|
|
|
\begin{lstlisting}[caption={Example of applicable to section}]
|
|
applicable to {
|
|
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection*{Defining the transformation}
|
|
|
|
In order to define the transformation that is applied to a specific matched code snippet, the keyword \textit{transform to} is used. This section is similar to the template section, however it uses the specific DSL identifiers defined in applicable to, in order to transfer the context of the matched user code, this allows us to keep parts of the users code important to the original context it was written in.
|
|
|
|
\begin{lstlisting}[caption={Example of transform to section}]
|
|
transform to{
|
|
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection*{All sections together}
|
|
|
|
Taking all these parts of \DSL structure, defining a proposal in \DSL will look as follows.
|
|
|
|
\begin{lstlisting}[caption={\DSL definition of a proposal}]
|
|
proposal PROPOSAL_NAME {
|
|
case PAIR_NAME {
|
|
applicable to {
|
|
|
|
}
|
|
transform to {
|
|
|
|
}
|
|
}
|
|
pair PAIR_NAME {
|
|
applicable to .....
|
|
}
|
|
|
|
pair ....
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\section{\DSLSH}
|
|
|
|
In this thesis, we also created an alternative way of defining proposals and their respective transformations, this is done using JavaScript as it's own meta language for the definitions. The reason for creating a way of defining proposals using JavaScript is, it allows us to limit the amount of dependencies of the tool, since we no longer rely on \DSL, and it allows for more exploration in the future work of this project.
|
|
|
|
\DSLSH is less of an actual language, and more of a program API at the moment, it allows for defining proposals purely in JavaScript objects, which is meant to allow a more modular way of using this idea. In \DSLSH you define a \textit{prelude}, which is just a list of variable declarations that contain the type expression as a string for that given wildcard. This means we do not need to perform wildcard extraction when wanting to parse the templates used for matching and transformation.
|
|
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}
|
|
// Definition in JSTQL
|
|
proposal a{
|
|
case {
|
|
applicable to {
|
|
<<a:Expression>>
|
|
}
|
|
transform to {
|
|
() => <<a>>
|
|
}
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
\noindent\begin{minipage}{.45\textwidth}
|
|
\begin{lstlisting}[language={JavaScript}]
|
|
// Equivalent definition in JSTQL-SH
|
|
{
|
|
prelude: 'let a = "Expression"'`,
|
|
applicableTo: "a;",
|
|
transformTo: "() => a;"
|
|
}
|
|
\end{lstlisting}
|
|
\end{minipage}\hfil
|
|
|
|
|
|
\section{Using the \DSL with an actual syntactic proposal}
|
|
|
|
This section contains the definitions of the proposals used to evaluate the tool created in this thesis. These definitions do not have to cover every single case where the proposal might be applicable, as they just have to be general enough to create some amount of examples that will give a representative number of matches when the transformations are applied to some relatively long user code.
|
|
|
|
\subsection{Pipeline Proposal}
|
|
|
|
The Pipeline Proposal is the easiest to define of the proposals presented in \ref*{sec:proposals}. This is due to the proposal being applicable to a very wide array of expressions, and the main problem this proposal is trying to solve is deep nesting of function calls.
|
|
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Example of Pipeline Proposal definition in \DSL}, label={def:pipeline}]
|
|
proposal Pipeline{
|
|
|
|
case SingleArgument {
|
|
applicable to {
|
|
"<<someFunctionIdent:Identifier || MemberExpression>>(<<someFunctionParam: Expression>>);"
|
|
}
|
|
|
|
transform to {
|
|
"<<someFunctionParam>> |> <<someFunctionIdent>>(%);"
|
|
}
|
|
}
|
|
|
|
case DualArgument{
|
|
applicable to {
|
|
"<<someFunctionIdent: Identifier || MemberExpression>>(<<someFunctionParam: Expression>>, <<moreFunctionParam: Expression>>)"
|
|
}
|
|
transform to {
|
|
"<<someFunctionParam>> |> <<someFunctionIdent>>(%, <<moreFunctionParam>>)"
|
|
}
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
|
|
This first pair definition \texttt{SingleArgument} of the Pipeline proposal will apply to any \textit{CallExpression} with a single argument. And it will be applied to each of the deeply nested callExpressions in a nested call. The second pair definition \texttt{DualArgument} will apply to any \textit{CallExpression} with 2 arguments. One can in theory define any number of cases to cover a higher amount of arguments in the function call, however we only need to cover enough cases to produce at least some matches, so a smaller definition is better in this case
|
|
|
|
\subsection{Do Proposal}
|
|
|
|
The \cite[Do Proposal]{Proposal:DoProposal} can also be defined with this tool. This definition will never catch all the applicable sections of the users code, and is very limited in where it might discover this proposal is applicable. This is due to the Do Proposal introducing an entirely new way to write JavaScript (Expression-oriented programming). If the user running this tool has not used the current status-quo way of doing expression-oriented programming in JavaScript, \DSL will probably not find any applicable snippets in the users code. However, in a reasonably large codebase, some examples will probably be discovered.
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Definition of Do Proposal in \DSL}, label={def:doExpression}]
|
|
proposal DoExpression{
|
|
case arrowFunction{
|
|
applicable to {
|
|
"let <<ident:Identifier>> = () => {
|
|
<<statements: (Statement && !ReturnStatement)*>>
|
|
return <<returnVal : Expression>>;
|
|
}
|
|
"
|
|
}
|
|
transform to {
|
|
"let <<ident>> = do {
|
|
<<statements>>
|
|
<<returnVal>>
|
|
}"
|
|
}
|
|
}
|
|
|
|
case immediatelyInvokedUnnamedFunction {
|
|
applicable to {
|
|
"let <<ident:Identifier>> = function(){
|
|
<<statements: (Statement && !ReturnStatement)*>>
|
|
return <<returnVal : Expression>>;
|
|
}();"
|
|
}
|
|
|
|
transform to {
|
|
"let <<ident>> = do {
|
|
<<statements>>
|
|
<<returnVal>>
|
|
}"
|
|
}
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
|
|
|
|
\subsection{Await to Promises evaluation proposal}
|
|
|
|
This section will cover the evaluation proposal we created in order to evaluate this tool described in \ref{sec:proposals}.
|
|
|
|
This proposal was created in order to evaluate the tool, as it is quite difficult to define applicable code in this current template form. This definition is limited, and can only apply if the function only contains a single await expression. This actually highlights some of the issues with the current design of \DSL that will be described in Future Work.
|
|
|
|
\begin{lstlisting}[language={JavaScript}, caption={Definition of Await to Promise evaluation proposal in \DSL}, label={def:awaitToPromise}]
|
|
proposal awaitToPomise{
|
|
case single{
|
|
applicable to {
|
|
"let <<ident:Identifier>> = await <<awaitedExpr: Expression>>;
|
|
<<statements: (Statement && !ReturnStatement)*>>
|
|
return <<returnExpr: Expression>>
|
|
"
|
|
}
|
|
|
|
transform to{
|
|
"return <<awaitedExpr>>.then((<<ident>>) => {
|
|
<<statements>>
|
|
return <<returnExpr>>
|
|
});"
|
|
}
|
|
}
|
|
}
|
|
\end{lstlisting} |