Skip to content

Commit

Permalink
Moved SuperConstructor command into ConstructorStart
Browse files Browse the repository at this point in the history
C# seems to require using the `: base` syntax. Different language have various restrictions around running code before a child class' contructor logic.

Continues #506 by revamping the `Point Class` end-to-end test .
  • Loading branch information
Josh Goldberg committed Nov 7, 2018
1 parent 1d1b3a9 commit bb72073
Show file tree
Hide file tree
Showing 197 changed files with 865 additions and 313 deletions.
7 changes: 3 additions & 4 deletions docs/syntax/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,11 @@ class Noun(Word):
Constructors, or initialization methods, are called when a new instance of a class is created.
It's declared with `constructor start`, which takes the publicity of the constructor, the name of the class, and any number of (name, type) arguments, and `constructor end`.

Inherited classes may use `super constructor` to call to their parent class' constructor.
Inherited classes that define a constructor must provide an additional `base` argument along with any parameters to call to their parent class' constructor.

```gls
class start : Noun extends Word
constructor start : public Noun name string
super constructor
constructor start : public Noun name string base
print : { concatenate : ("Creating ") name }
constructor end
class end
Expand All @@ -64,8 +63,8 @@ In C#:
class Noun : Word
{
Noun(string name)
: base()
{
base();
Console.WriteLine("Creating " + name);
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/Rendering/Commands/CommandsBagFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ import { StringLengthCommand } from "./StringLengthCommand";
import { StringSubstringIndexCommand } from "./StringSubstringIndexCommand";
import { StringSubstringLengthCommand } from "./StringSubstringLengthCommand";
import { StringTrimCommand } from "./StringTrimCommand";
import { SuperConstructorCommand } from "./SuperConstructorCommand";
import { ThisCommand } from "./ThisCommand";
import { ThrowCommand } from "./ThrowCommand";
import { TryEndCommand } from "./TryEndCommand";
Expand Down Expand Up @@ -274,7 +273,6 @@ export class CommandsBagFactory {
new StringSubstringIndexCommand(context),
new StringSubstringLengthCommand(context),
new StringTrimCommand(context),
new SuperConstructorCommand(context),
new ThisCommand(context),
new ThrowCommand(context),
new TryEndCommand(context),
Expand Down
142 changes: 110 additions & 32 deletions src/Rendering/Commands/ConstructorStartCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export class ConstructorStartCommand extends Command {
new SingleParameter("parameterName", "A named parameter for the constructor.", true),
new SingleParameter("parameterType", "The type of the parameter.", true),
]),
new KeywordParameter([KeywordNames.Base], "Keyword to call a base class constructor.", false),
new RepeatingParameters("Function parameters", [new SingleParameter("parameter", "Argument for the base constructor.", true)]),
]);

/**
Expand All @@ -43,64 +45,121 @@ export class ConstructorStartCommand extends Command {
*/
public render(parameters: string[]): LineResults {
const imports: Import[] = [];
let declaration = "";
let output: CommandResult[];
const privacy: string = parameters[1];
const className: string = parameters[2];
const baseIndex: number = parameters.indexOf(KeywordNames.Base);

declaration += this.getPublicity(parameters[1]);
// public
let declaration = this.getPublicity(privacy);

if (this.language.syntax.classes.constructors.useKeyword) {
// public constructor
declaration += this.language.syntax.classes.constructors.keyword;
} else {
declaration += parameters[2];
// public MyClass
declaration += className;
}

// public MyClass(
declaration += "(";

let endOfParameters = baseIndex;
if (endOfParameters === -1) {
endOfParameters = parameters.length;
}

if (this.language.syntax.classes.constructors.takeThis) {
// public MyClass(self
declaration += this.language.syntax.classes.this;

if (parameters.length > 4) {
if (endOfParameters > 3) {
// public MyClass(self,
declaration += ", ";
}
}

if (parameters.length > 4) {
const declarationLine = this.generateParameterVariable(parameters, 3);
declaration += declarationLine.commandResults[0].text;
imports.push(...declarationLine.addedImports);
for (let i = 3; i < endOfParameters; i += 2) {
// public MyClass(self, a
const parameterLine = this.generateParameterVariable(parameters, i);
declaration += parameterLine.commandResults[0].text;
imports.push(...parameterLine.addedImports);

for (let i = 5; i < parameters.length; i += 2) {
const nextDeclarationLine = this.generateParameterVariable(parameters, i);
declaration += ", " + nextDeclarationLine.commandResults[0].text;
imports.push(...nextDeclarationLine.addedImports);
// public MyClass(self, a, b, c
if (i !== endOfParameters - 2) {
declaration += ", ";
}
}

declaration += ")";
// public MyClass(self, a, b, c)
const output = [new CommandResult(declaration + ")", 0)];
let addSemicolon = false;

output = [new CommandResult(declaration, 0)];
this.addLineEnder(output, this.language.syntax.functions.defineStartRight, 1);
// Case: no super
if (baseIndex === -1) {
// public MyClass(self, a, b, c) {
this.addLineEnder(output, this.language.syntax.functions.defineStartRight, 1);
}
// Case: super with the ": base(...)" shorthand
else if (this.language.syntax.classes.constructors.baseShorthand) {
// public MyClass(self, a, b, c)
// : base(
let nextLine = "\n : " + this.language.syntax.classes.constructors.baseConstructor + "(";

// public MyClass(self, a, b, c)
// : base(a
for (let i = baseIndex + 1; i < parameters.length; i += 1) {
nextLine += parameters[i];

// public MyClass(self, a, b, c)
// : base(a, b, c
if (i !== parameters.length - 1) {
nextLine += ", ";
}
}

return new LineResults(output).withImports(imports);
}
// public MyClass(self, a, b, c)
// : base(a, b, c)
nextLine += ")";
this.addLineEnder(output, nextLine, 1);

/**
* Generates a line result for a parameter.
*
* @param parameters An ordered sequence of [parameterName, parameterType, ...].
* @param i An index in the parameters of a parameterName.
* @remarks This assumes that if a language doesn't declare variables, it doesn't declare types.
*/
private generateParameterVariable(parameters: string[], i: number): LineResults {
if (!this.language.syntax.variables.declarationRequired) {
return LineResults.newSingleLine(parameters[i]);
// public MyClass(self, a, b, c)
// : base(a, b, c)
// {
output[output.length - 1].indentation -= 1;
this.addLineEnder(output, "\n{", 1);
}
// Case: super as the first line in the constructor
else {
addSemicolon = true;

let startLine = this.language.syntax.functions.defineStartRight + "\n";
startLine += this.language.syntax.classes.constructors.baseConstructor + "(";

// public MyClass(self, a, b, c) {
// super(
this.addLineEnder(output, startLine, 0);
output[output.length - 2].indentation += 1;

let nextLine: string = "";

// public MyClass(self, a, b, c) {
// super(a
for (let i = baseIndex + 1; i < parameters.length; i += 1) {
nextLine += parameters[i];

// public MyClass(self, a, b, c) {
// super(a, b, c
if (i !== parameters.length - 1) {
nextLine += ", ";
}
}

const parameterName: string = parameters[i];
const parameterTypeLine = this.context.convertParsed([CommandNames.Type, parameters[i + 1]]);
const parameterType = parameterTypeLine.commandResults[0].text;
// public MyClass(self, a, b, c) {
// super(a, b, c)
this.addLineEnder(output, nextLine + ")", 0);
}

return this.context.convertParsed([CommandNames.VariableInline, parameterName, parameterType]);
return new LineResults(output).withAddSemicolon(addSemicolon).withImports(imports);
}

/**
Expand All @@ -120,4 +179,23 @@ export class ConstructorStartCommand extends Command {

return this.language.syntax.classes.constructors.public;
}

/**
* Generates a line result for a parameter.
*
* @param parameters An ordered sequence of [parameterName, parameterType, ...].
* @param i An index in the parameters of a parameterName.
* @remarks This assumes that if a language doesn't declare variables, it doesn't declare types.
*/
private generateParameterVariable(parameters: string[], i: number): LineResults {
if (!this.language.syntax.variables.declarationRequired) {
return LineResults.newSingleLine(parameters[i]);
}

const parameterName: string = parameters[i];
const parameterTypeLine = this.context.convertParsed([CommandNames.Type, parameters[i + 1]]);
const parameterType = parameterTypeLine.commandResults[0].text;

return this.context.convertParsed([CommandNames.VariableInline, parameterName, parameterType]);
}
}
48 changes: 42 additions & 6 deletions src/Rendering/Commands/ImportCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export abstract class ImportCommand extends Command {
* @returns Line(s) of code in the language.
*/
public render(parameters: string[]): LineResults {
const usingSplit = parameters.indexOf("use");
const usingSplit = parameters.indexOf(KeywordNames.Use);
if (usingSplit === -1) {
throw new Error('A "use" parameter must be in import commands.');
}
Expand All @@ -43,18 +43,54 @@ export abstract class ImportCommand extends Command {
const relativity: ImportRelativity = this.getRelativity();
let packagePath: string[] = parameters.slice(1, usingSplit);

const lineResults = new LineResults([]);
const contextPackagePath = this.context.getFileMetadata().getPackagePath();
const relativePackagePath = ImportCommand.pathResolver.resolve(contextPackagePath, packagePath);

if (relativity === ImportRelativity.Local) {
packagePath = ImportCommand.pathResolver.resolve(this.context.getFileMetadata().getPackagePath(), packagePath);
packagePath = relativePackagePath;
} else if (!this.language.syntax.imports.useLocalRelativePaths && !this.language.syntax.imports.explicit) {
if (
this.isPackagePathOnlyParents(relativePackagePath.slice(0, relativePackagePath.length - 1)) ||
this.isPackagePathSubset(packagePath.slice(0, packagePath.length - 1), contextPackagePath)
) {
return lineResults;
}
}

const lineResults = new LineResults([]);
lineResults.withImports([new Import(packagePath, items, this.getRelativity())]);

return lineResults;
return lineResults.withImports([new Import(packagePath, items, this.getRelativity())]);
}

/**
* @returns Whether this is from an absolute package or local file.
*/
protected abstract getRelativity(): ImportRelativity;

private isPackagePathOnlyParents(relativePackagePath: string[]): boolean {
for (const component of relativePackagePath) {
if (component !== "..") {
return false;
}
}

return true;
}

private isPackagePathSubset(packagePath: string[], contextPackagePath: string[]): boolean {
if (packagePath.length > contextPackagePath.length) {
return false;
}

if (packagePath[0] === "..") {
return false;
}

for (let i = 0; i < packagePath.length; i += 1) {
if (packagePath[i] !== contextPackagePath[i]) {
return false;
}
}

return true;
}
}
8 changes: 8 additions & 0 deletions src/Rendering/Commands/MemberFunctionDeclareCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ export abstract class MemberFunctionDeclareCommand extends Command {
declaration += this.context.convertStringToCase(functionName, this.getPublicityCase(publicity));
declaration += "(";

if (this.language.syntax.classes.members.functions.includeThisReference) {
declaration += this.language.syntax.classes.this;

if (parameters.length > 4) {
declaration += ", ";
}
}

if (parameters.length > 4) {
const typeLine = this.generateParameterVariable(parameters, 4);
declaration += typeLine.commandResults[0].text;
Expand Down
12 changes: 10 additions & 2 deletions src/Rendering/Commands/MemberVariableDeclareCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,17 @@ export class MemberVariableDeclareCommand extends Command {
variableName = this.context.convertStringToCase(variableName, casingStyle);

const inlineParameters = [CommandNames.VariableInline, variableName, type];
const variableLine = this.context.convertParsed(inlineParameters);
const variableText = variableLine.commandResults[0].text;

output += this.context.convertParsed(inlineParameters).commandResults[0].text;
if (variableText === "\0") {
output += variableName;
} else {
output += variableText;
}

return LineResults.newSingleLine(output).withAddSemicolon(true);
return LineResults.newSingleLine(output)
.withAddSemicolon(true)
.withImports(variableLine.addedImports);
}
}
54 changes: 0 additions & 54 deletions src/Rendering/Commands/SuperConstructorCommand.ts

This file was deleted.

Loading

0 comments on commit bb72073

Please sign in to comment.