Safe Haskell | Safe-Infered |
---|
In Chapter 2 of "JavaScript: The Good Parts", Douglas Crockford presents a concrete grammar for "the good parts" of JavaScript.
This module provides an abstract grammar for those good parts. Henceforth, we abbreviate this language to JS:TGP
Crockford presents the grammar as a series of railroad diagrams. The correspondence between the concrete grammar and the abstract grammar in this module is NOT one-to-one. However, the following property does hold: the pretty printing of an abstract syntax tree will be parseable by the concrete grammar. i.e. For each valid program produced by the concrete grammar there is a corresponding abstract syntax tree that when pretty printed will produce that program (modulo whitespace).
The abstract grammar
- removes unnecessary characters such as parentheses (normal, curly and square)
- represents JavaScript's string, name and number literals directly in Haskell as
String
,String
andDouble
respectively.
Conventions for concrete syntax
- Non-terminals appear in angle brackets e.g. <Name>
- ? means zero or one. e.g. <Expr>?
- * means zero or more e.g. <Stmt>*
- + means one or more e.g. <Stmt>+
- ( ) are meta-brackets used to enclose a concrete-syntax expression so that ?,* or + can be applied. e.g. (= <Expr>)* This means zero or more repetitions of: = <Expr>
This library was designed so that it would be impossible, save for name, string literals
to construct an incorrect JS:TGP program. To this end some of the data structures may look like
they contain redundancy. For instance, consider the ESDelete
constructor which is defined
ESDelete Expr Invocation
Why not just define it as ESDelete Expr
since type Expr
has a constructor defined as ExprInvocation Expr Invocation
?
The reason is that this would allow incorrect programs. A Expr
is
not necessarily a Invocation
.
A note on precedence of JavaScript operators
Interestingly, the precedence of JavaScript operators is not defined in the ECMAScript standard. The precedence used in this library comes from the Mozilla Developer's Network pages. (https:developer.mozilla.orgenJavaScriptReferenceOperators/Operator_Precedence)
I have not used the precise precedence numbers from that page since in this module a lower precedence means the operator binds more tightly (as opposed to the page where a higher precedence does the same). Also, we have need for less precedence values so they have been normalised to what we are using in JS:TGP
You will also note that we don't even consider the associativity/precedence of "=", "+=", "-=" etc. In JS:TGP the notion of expression statements is quite different to that of expressions. It simply isn't legal to write an expression statement like
(a += 2) -= 3
or
a = (b = c) = (c = d)
although it is perfectly legal to write
a = b = c = d += 2
which if we add brackets to disambiguate is really
a = (b = (c = (d += 2)))
Interesting aspects of "the good parts":
A JS:TGP program is a collection of statements. You'll note that there is no statement to declare a function in JS:TGP. However you can assign a function literal to a variable.
e.g.
var fun = function(x) { return x + 1;}
What about recursive functions then? There is the option to give the function a name which is local to the literal.
e.g.
var factorial = function f(n) { if ( n > 0 ) { return n * f(n - 1); } else { return 1; } }
f
is local. It will not be in scope outside of the function body.
Abbreviations:
Stmt = Statement, Expr = Expression, Fn = Function, Decl = Declaration
- data JSString
- data Name
- unString :: JSString -> String
- unName :: Name -> String
- jsString :: String -> Either String JSString
- name :: String -> Either String Name
- newtype Number = Number Double
- data VarStmt = VarStmt (NonEmptyList VarDecl)
- data VarDecl = VarDecl Name (Maybe Expr)
- data Stmt
- data DisruptiveStmt
- data IfStmt = IfStmt Expr [Stmt] (Maybe (Either [Stmt] IfStmt))
- data SwitchStmt
- data CaseAndDisruptive = CaseAndDisruptive CaseClause DisruptiveStmt
- data CaseClause = CaseClause Expr [Stmt]
- data ForStmt
- data DoStmt = DoStmt [Stmt] Expr
- data WhileStmt = WhileStmt Expr [Stmt]
- data TryStmt = TryStmt [Stmt] Name [Stmt]
- data ThrowStmt = ThrowStmt Expr
- data ReturnStmt = ReturnStmt (Maybe Expr)
- data BreakStmt = BreakStmt (Maybe Name)
- data ExprStmt
- data LValue = LValue Name [([Invocation], Refinement)]
- data RValue
- = RVAssign Expr
- | RVAddAssign Expr
- | RVSubAssign Expr
- | RVInvoke (NonEmptyList Invocation)
- data Expr
- data PrefixOperator
- data InfixOperator
- data Invocation = Invocation [Expr]
- data Refinement
- data Lit
- data ObjectLit = ObjectLit [ObjectField]
- data ObjectField = ObjectField (Either Name String) Expr
- data ArrayLit = ArrayLit [Expr]
- data FnLit = FnLit (Maybe Name) [Name] FnBody
- data FnBody = FnBody [VarStmt] [Stmt]
- data Program = Program [VarStmt] [Stmt]
Documentation
jsString :: String -> Either String JSStringSource
The only way you can create a Javascript string. This function needs to correctly encode all special characters. See p9 of "JavaScript: The Good Parts"
Data types
Concrete syntax:
var <VarDecl> [, <VarDecl>]* ;
e.g. var x = 1, y;
Concrete syntax:
<Name> (= <Expr>)?
e.g.
x
x = 2 + y
The many different kinds of statements
StmtExpr ExprStmt | <ExprStmt>; |
StmtDisruptive DisruptiveStmt | <DisruptiveStmt> |
StmtTry TryStmt | <TryStmt> |
StmtIf IfStmt | <IfStmt> |
StmtSwitch (Maybe Name) SwitchStmt | (<Name> : ) <SwitchStmt> |
StmtWhile (Maybe Name) WhileStmt | (<Name> : ) <WhileStmt> |
StmtFor (Maybe Name) ForStmt | (<Name> : ) <ForStmt> |
StmtDo (Maybe Name) DoStmt | (<Name> : ) <DoStmt> |
data DisruptiveStmt Source
Disruptive statements
DSBreak BreakStmt | <BreakStmt> |
DSReturn ReturnStmt | syntax: <ReturnStmt> |
DSThrow ThrowStmt | syntax: <ThrowStmt> |
Pretty DisruptiveStmt | |
PrettyPrec DisruptiveStmt |
Concrete syntax:
if ( <Expr> ) { <Stmt>* }
-- for Nothing
or
if ( <Expr> ) { <Stmt>* } else { <Stmt>* }
-- for 'Just . Left'
or
if ( <Expr> ) { <Stmt>* } else <IfStmt>
-- for 'Just . Right'
e.g.
if (x > 3) { y = 2; }
if (x < 2) { y = 1; } else { y = 3; z = 2; }
if (x > 0) { y = 20; } else if ( x > 10) { y = 30; } else { y = 10; }
data SwitchStmt Source
Concrete syntax:
switch ( <Expr> ) { <CaseClause> }
or
switch ( <Expr> ) { <CaseAndDisruptive>+ default : <Stmt>* }
e.g.
switch ( x ) { case 1: y = 2; }
switch ( x ) { case 1: y = 2; break; case 2: y = 3; break; default: y = 4; }
SwitchStmtSingleCase Expr CaseClause | |
SwitchStmt Expr (NonEmptyList CaseAndDisruptive) [Stmt] | default clause statements |
Pretty SwitchStmt | |
PrettyPrec SwitchStmt |
data CaseAndDisruptive Source
A case clause followed by a disruptive statement
Concrete syntax:
<CaseClause> <DisruptiveStmt>
e.g.
-
case 2: y = 2; break;
Pretty CaseAndDisruptive | |
PrettyPrec CaseAndDisruptive |
data CaseClause Source
Concrete syntax:
case <Expr> : <Stmt>*
e.g.
case 2: // zero statements following the case expression is valid.
case 2: y = 1;
Pretty CaseClause | |
PrettyPrec CaseClause |
Two style of for-statements -- C-style and In-style.
Concrete syntax:
for (<ExprStmt>? ; <Expr>? ; <ExprStmt>? ) { <Stmt>* }
for ( <Name> in <Expr> ) { <Stmt>* }
e.g.
for ( ; ; ) { }
for ( ; x < 10 ;) { x += 1; }
for (i = 0; i < 10; i += 1) { x += i; }
for ( i in indices ) { a[i] = 66; }
Concrete syntax:
do { <Stmt>* } while ( <Expr> );
Concrete syntax:
while ( <Expr>) { <Stmt>* }
Concrete syntax:
try { <Stmt>* } catch ( <Name> ) { <Stmt>* }
Concrete syntax:
throw <Expr>;
data ReturnStmt Source
Concrete syntax:
return <Expr>?;
e.g.
return;
return 2 + x;
Pretty ReturnStmt | |
PrettyPrec ReturnStmt |
Concrete syntax:
break <Name>?;
e.g.
break;
break some_label;
Concrete syntax:
<Value>+ <RValue>
or
delete <Expr> <Refinement>
Concrete syntax:
<Name> (<Invocation>* <Refinement>)*
e.g.
x
x.field_1
fun().field_1
fun(1)(2)
fun(1)(2).field_1
x.fun_field_1(x+2).fun_field_2(y+3).field_3
LValue Name [([Invocation], Refinement)] |
Concrete syntax:
= <Expr>
or
+= <Expr>
or
-= <Expr>
or
<Invocation>+
e.g.
= 2
+= 3
-= (4 + y)
()
(1)
(x,y,z)
ExprLit Lit | <Lit> |
ExprName Name | <Name> |
ExprPrefix PrefixOperator Expr | <PrefixOperator> <Expr> |
ExprInfix InfixOperator Expr Expr | <Expr> <InfixOperator> <Expr> |
ExprTernary Expr Expr Expr | <Expr> ? <Expr> : <Expr> |
ExprInvocation Expr Invocation | <Expr><Invocation> |
ExprRefinement Expr Refinement | <Expr><Refinement> |
ExprNew Expr Invocation | new |
ExprDelete Expr Refinement | delete |
data PrefixOperator Source
Pretty PrefixOperator | |
PrettyPrec PrefixOperator |
data InfixOperator Source
Pretty InfixOperator | |
PrettyPrec InfixOperator |
data Invocation Source
Concrete syntax:
<Expr>*
e.g.
()
(1)
(x,z,y)
Pretty Invocation | |
PrettyPrec Invocation |
data Refinement Source
Concrete syntax:
.<Name>
or
[<Expr>]
e.g.
.field_1
[i+1]
Pretty Refinement | |
PrettyPrec Refinement |
Interestingly, the syntax diagrams presented in the book don't include boolean literals. I can only assume this is an oversight as they are used throughout the book.
Concrete syntax:
{}
-- no fields
or
{<ObjectField> (, <ObjectField> )*}
-- one or more fields
data ObjectField Source
Concrete syntax:
<Name>: <Expr>
-- for Left
or
<String>: <Expr>
-- for Right
e.g.
x: y + 3
"value": 3 - z
Pretty ObjectField | |
PrettyPrec ObjectField |
Concrete syntax:
[]
-- empty array
or
[<Expr> (, <Expr>*) ]
-- non empty array
Concrete syntax:
function <Name>? <FnBody>
Concrete syntax:
{ <VarStmt>+ <Stmt>+ }