Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 118 additions & 118 deletions package-lock.json

Large diffs are not rendered by default.

141 changes: 126 additions & 15 deletions src/elm-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,85 @@
* All rights reserved.
*/

// types
/*
Declare a set of Abstract Syntax Tree types for generating ELM code
Also declare a set of functions for converting the AST to a string

These AST types are created during graphql->elm compilation instead of
manipulating strings the entire time.
*/



/* Types */
export abstract class ElmType {}


// Represents a type name such as "String" or "a"
export class ElmTypeName extends ElmType {
constructor(public name: string) {
super();
}
}


// Represents a type application such as "List Int"
export class ElmTypeApp extends ElmType {
constructor(public name: string, public args: Array<ElmType>) {
super();
}
}


// Represents a record type such as "{firstName: String}"
export class ElmTypeRecord extends ElmType {
constructor(public fields: Array<ElmFieldDecl>, public typeParam?: string) {
super();
}
}

// decls

// Represents the field in a record type declaration such as the firstName in "{firstName: String}"
export class ElmFieldDecl {
constructor(public name: string,
public type: ElmType) {}
}



/* Top Level Declarations (The things that can appear left-most unindented in a .elm file like type declarations, import statements, etc. */
export abstract class ElmDecl {}


// Represents a module statement like "module Main exposing (...)"
export class ElmModuleDecl extends ElmDecl {
constructor(public name: string,
public exposing: Array<string>) {
super();
}
}


// Represents an import statement like "import Html as H exposing (text, Html)"
export class ElmImportDecl extends ElmDecl {
constructor(public name: string,
public alias: string = null,
public exposing: Array<string> = []) {
super();
}
}


// Represents a type declaration like "type Msg = Increment | Decrement"
export class ElmTypeDecl extends ElmDecl {
constructor(public name: string,
public constructors: Array<string>) {
super();
}
}


// Represents a type alias declaration like "type alias Person = {firstName: String}"
export class ElmTypeAliasDecl extends ElmDecl {
constructor(public name: string,
public type: ElmType,
Expand All @@ -44,6 +90,9 @@ export class ElmTypeAliasDecl extends ElmDecl {
}
}


// Represents a function declaration like "update msg state = ..."
// Also represents variable declarations like "numberFive = 5" (these declarations have an empty array of parameters)
export class ElmFunctionDecl extends ElmDecl {
constructor(public name: string,
public parameters: Array<ElmParameterDecl>,
Expand All @@ -53,52 +102,103 @@ export class ElmFunctionDecl extends ElmDecl {
}
}

export class ElmFieldDecl {
constructor(public name: string,
public type: ElmType) {}
}

// Represents the parameter declaration in a function declaration (the "msg" in "update msg state = ...")
export class ElmParameterDecl {
constructor(public name: string,
public type: ElmType) { }
}

// expressions





/* Expressions */
// TODO:
// Actually represent expressions as an AST.
// Currently they are just represented as strings.
// Then again, is this really a problem?
export interface ElmExpr {
expr: string; // todo: expression trees
expr: string;
}

export function moduleToString(name: string, expose: Array<string>, imports: Array<string>,
decls: Array<ElmDecl>) {
let warn = '{-\n This file was automatically generated by elm-graphql.\n-}\n';
return warn + 'module ' + name + ' exposing (' + expose.join(', ') + ')\n' +
imports.map(str => '\nimport ' + str).join('') + '\n\n' +
decls.map(declToString).join('\n\n');


/* AST to String conversion functions */
// TODO: Should these functions be in a separate file, or maybe as methods on their respective classes?


export function fileToString(
module: ElmModuleDecl,
imports: Array<ElmImportDecl>,
declarations: Array<ElmDecl>
): string {
return '{-\n This file was automatically generated by elm-graphql.\n-}\n'
+ moduleToString(module)
+ "\n\n"
+ imports.map(importToString).join('\n')
+ "\n\n"
+ declarations.map(declToString).join('\n\n');
}


// Convert the provided top level declaration to a string
// TODO: Should this be a function defined on the appropriate classes above?
export function declToString(decl: ElmDecl): string {
if (decl instanceof ElmTypeDecl) {
return typeDeclToString(decl);
} else if (decl instanceof ElmFunctionDecl) {
return funtionToString(decl);
} else if (decl instanceof ElmTypeAliasDecl) {
return typeAliasDeclToString(decl);
} else if (decl instanceof ElmModuleDecl) {
return moduleToString(decl);
} else if (decl instanceof ElmImportDecl) {
return importToString(decl);
} else {
throw new Error('unexpected decl: ' + decl.constructor.name + ' ' + JSON.stringify(decl));
}
}


// Convert the provided module declaration to a string
export function moduleToString(type: ElmModuleDecl): string {
return `module ${type.name} exposing (${type.exposing.join(", ")})`;
}


// Convert the provided import declaration to a string
export function importToString(type: ElmImportDecl): string {
let result = `import ${type.name}`;

if (type.alias) {
result += ` as ${type.alias}`;
}

if (type.exposing.length > 0) {
result += ` exposing (${type.exposing.join(", ")})`;
}

return result;
}


// Convert the provided type declaration to a string
export function typeDeclToString(type: ElmTypeDecl): string {
return 'type ' + type.name + '\n' +
' = ' + type.constructors.join('\n | ') + '\n';
}


// Convert the provided type alias declaration to a string
export function typeAliasDeclToString(type: ElmTypeAliasDecl): string {
return 'type alias ' + type.name + ' ' + type.typeParams.join(' ') + '\n' +
' = ' + typeToString(type.type, 0) + '\n';
}


// Convert the provided function declaration to a string
export function funtionToString(func: ElmFunctionDecl): string {
let paramTypes = func.parameters.map(p => typeToString(p.type, 1)).join(' -> ');
let paramNames = func.parameters.map(p => p.name).join(' ');
Expand All @@ -108,10 +208,14 @@ export function funtionToString(func: ElmFunctionDecl): string {
func.name + space + paramNames + ' =\n ' + exprToString(func.body, 0) + '\n';
}


// Convert the provided record field to a string
function fieldToString(field: ElmFieldDecl, level: number): string {
return field.name + ' : ' + typeToString(field.type, level, true) + '\n';
}


// Convert the provided type to a string
export function typeToString(ty: ElmType, level: number, isField?: boolean): string {
if (ty instanceof ElmTypeName) {
return ty.name;
Expand All @@ -133,14 +237,21 @@ export function typeToString(ty: ElmType, level: number, isField?: boolean): str
}
}


// Return some spaces equal to the requested indent level
// As per the Elm coding standard, an indent is four spaces
function makeIndent(level: number) {
let str = '';

for (let i = 0; i < level; i++) {
str += ' ';
}

return str;
}


// Convert the provided expression to a string
export function exprToString(expr: ElmExpr, level: number): string {
return expr.expr; // todo: expression trees
return expr.expr;
}
Loading