Skip to content
Draft
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
2 changes: 2 additions & 0 deletions aeneas/src/main/CLOptions.v3
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ component CLOptions {
"Enable function expressions for the Virgil language.").onSet(implySimpleBodies), falseOption);
def SIMPLE_BODIES = if(Debug.UNSTABLE, langOpt.newBoolOption("simple-bodies", false,
"Enable simple function body syntax for the Virgil language."), falseOption);
def TRAITS = if(Debug.UNSTABLE, langOpt.newBoolOption("traits", false,
"Enable traits for the Virgil language."), falseOption);
def OPT = sharedOpt.newStringOption("opt", null,
"Set optimization configuration options.");
def O0 = sharedOpt.newEmptyOption("O0",
Expand Down
3 changes: 3 additions & 0 deletions aeneas/src/main/Error.v3
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ class SourceCodeError extends Error {
def UnimplementedError(msg: string) {
set("UnimplementedError", msg);
}
def MethodDeclError(msg: string) {
set("MethodDeclError", msg);
}
def TypeParamArityError(kind: string, name: string, expect: int, got: int) {
var decl = Strings.format2("%s \"%s\"", kind, name), msg: string;
if (expect == 0) msg = Strings.format1("%s cannot have type arguments", decl);
Expand Down
36 changes: 33 additions & 3 deletions aeneas/src/vst/Parser.v3
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ component Keywords {
("if", KC_OTHER),
("import", KC_OTHER),
("in", KC_OTHER),
// [RV]: fix this compilation error
// ("impl", KC_OTHER),
("layout", KC_OTHER),
("match", KC_OTHER),
("new", KC_OTHER),
Expand All @@ -43,6 +45,7 @@ component Keywords {
("struct", KC_OTHER),
("super", KC_OTHER),
("thread", KC_OTHER),
("trait", KC_OTHER),
("true", KC_TRUE),
("type", KC_OTHER),
("var", KC_VAR),
Expand Down Expand Up @@ -102,6 +105,7 @@ component Parser {
var p = ParserState.new(file, ERROR, skipToNextToken, typeCache);
p.enableFunExprs = CLOptions.FUN_EXPRS.get();
p.enableSimpleBodies = CLOptions.SIMPLE_BODIES.get();
p.enableTraits = CLOptions.TRAITS.get();

file.input = input;
file.lineEnds = p.lineEnds;
Expand All @@ -120,15 +124,15 @@ component Parser {
p.advance(skip);
return (file, parseList(0, p, 0, COMMA, 0, parseRedefField));
}
// [RV]: top-level decl parsing
def parseToplevelDecl(p: ParserState, file: VstFile) -> bool {
match (p.curByte) {
'c' => {
if (optKeyword(p, "class") != null) return parseClass(p, file, false);
if (optKeyword(p, "component") != null) return parseComponent(p, file, false, false);
}
'i' => {
var importToken = optKeyword(p, "import");
if (importToken != null) {
if (optKeyword(p, "import") != null) {
var importName = parseOptString(p);
if (reqKeyword(p, "component") != null) {
var id = parseIdentVoid(p).name;
Expand All @@ -138,7 +142,7 @@ component Parser {
file.components.put(decl);
return true;
}
}
} else if (Debug.UNSTABLE && p.enableTraits && optKeyword(p, "impl") != null) return parseImpl(p, file);
}
'v' => if (optKeyword(p, "var") != null) {
var synthetic = file.getSyntheticComponent();
Expand All @@ -153,6 +157,7 @@ component Parser {
't' => {
if (optKeyword(p, "type") != null) return parseVariant(p, file, false);
if (optKeyword(p, "thread") != null && optKeyword(p, "component") != null) return parseComponent(p, file, false, true);
if (Debug.UNSTABLE && p.enableTraits && optKeyword(p, "trait") != null) return parseTrait(p, file, false);
}
'e' => {
if (optKeyword(p, "enum") != null) return parseEnum(p, file, false);
Expand All @@ -162,6 +167,7 @@ component Parser {
if (optKeyword(p, "private") != null) {
if (optKeyword(p, "class") != null) return parseClass(p, file, true);
if (optKeyword(p, "component") != null) return parseComponent(p, file, true, false);
if (optKeyword(p, "trait") != null) return parseTrait(p, file, true);
if (optKeyword(p, "thread") != null && optKeyword(p, "component") != null) return parseComponent(p, file, true, true);
if (optKeyword(p, "type") != null) return parseVariant(p, file, true);
if (optKeyword(p, "enum") != null) return parseEnum(p, file, true);
Expand Down Expand Up @@ -199,6 +205,29 @@ component Parser {
file.components.put(decl);
return true;
}
def parseTrait(p: ParserState, file: VstFile, isPrivate: bool) -> bool {
var selfTypeList = parseList(1, p, '<', COMMA, '>', parseTypeParam);
if (selfTypeList.length() != 1) {
p.errorAt(selfTypeList.src, "trait expects one self-referencing type variable");
return false;
}
var selfType = selfTypeList.first(); // TODO: make nullable
var id = parseIdent(p, parseTypeParam);
var members = parseMembers(p, false);
var decl = VstTrait.new(isPrivate, id.name, selfType, id.list(), members);
file.traits.put(decl);
return true;
}
def parseImpl(p: ParserState, file: VstFile) -> bool {
var typeParams = if(p.curByte == '<', parseList(1, p, '<', COMMA, '>', parseTypeParam).list);
var trait_ = parseIdent(p, parseTypeRef);
reqKeyword(p, "for");
var clazz = parseIdent(p, parseTypeRef);
var members = parseMembers(p, false);
var decl = VstImpl.new(clazz.name, typeParams, trait_, clazz, members);
file.impls.put(decl);
return true;
}
def parseVariant(p: ParserState, file: VstFile, isPrivate: bool) -> bool {
var id = parseIdent(p, parseTypeParam);
var params = parseOptionalParams(p, parseVariantCaseParam);
Expand Down Expand Up @@ -457,6 +486,7 @@ component Parser {
p.req1(';');
return List.new(VstLayoutField.new(offset, type_ref, id, repHints), prev);
}
// [RV]: figure out what {allowImportName} does, and if it applies to traits
def parseMembers(p: ParserState, allowImportName: bool) -> List<VstMember> {
p.req1('{');
var list: List<VstMember>;
Expand Down
1 change: 1 addition & 0 deletions aeneas/src/vst/ParserState.v3
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ParserState {
var errors: List<int>; // errors generated so far
var enableFunExprs: bool; // enable function expression parsing
var enableSimpleBodies: bool; // enable simple function body parsing
var enableTraits: bool; // enable trait parsing

new(file, ERROR, skipFunc, typeCache) {
reset();
Expand Down
44 changes: 40 additions & 4 deletions aeneas/src/vst/Verifier.v3
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ class Verifier(compiler: Compiler, prog: Program) {
for (f in vst.files) buildFile(f);
forAll(vst.classes, resolveSuperClass);
forAll(vst.components, verifyComponent);
forAll(vst.traits, verifyTrait);
forAll(vst.classes, verifyClass);
forAll(vst.impls, verifyImpl);
forAll(vst.enums, verifyEnum);
forAll(vst.layouts, verifyLayout);
forAll(vst.exports, verifyExport);
Expand Down Expand Up @@ -68,6 +70,13 @@ class Verifier(compiler: Compiler, prog: Program) {
if (!componentDecl.isSynthetic) cv.bindTypeCon();
cv.buildMembers();
}
// [RV]: build file for `trait`
prog.vst.traits.putv(file.traits);
for (i < file.traits.length) {
var traitDecl = file.traits[i];
var cv = makeVerifier(traitDecl, null, file);
cv.buildMembers();
}
prog.vst.classes.putv(file.classes);
for (i < file.classes.length) {
var classDecl = file.classes[i];
Expand All @@ -76,6 +85,13 @@ class Verifier(compiler: Compiler, prog: Program) {
if (!classDecl.isSynthetic) cv.bindTypeCon();
cv.buildMembers();
}
// [RV]: build file for `impl`
prog.vst.impls.putv(file.impls);
for (i < file.impls.length) {
var implDecl = file.impls[i];
var cv = makeVerifier(implDecl, null, file);
cv.buildMembers();
}
prog.vst.enums.putv(file.enums);
for (i < file.enums.length) {
var enumDecl = file.enums[i];
Expand Down Expand Up @@ -165,6 +181,14 @@ class Verifier(compiler: Compiler, prog: Program) {
if (compiler.target != null) compiler.target.verifyMain(mainMethod, cv.MainMethodError);
}
}
def verifyTrait(decl: VstTrait) {
var cv = decl.verifier;
cv.verify();
}
def verifyImpl(decl: VstImpl) {
var cv = decl.verifier;
cv.verify();
}
def verifyClass(decl: VstClass) {
var cv = decl.verifier;
if (cv.verified || cv.onstack) return;
Expand Down Expand Up @@ -446,6 +470,7 @@ class VstCompoundVerifier {
var onstack: bool;
var verified: bool;
var isImport: bool;
var methodOnly: bool;

new(compound, thisType, verifier, file) {
ERROR = verifier.ERROR;
Expand All @@ -454,6 +479,8 @@ class VstCompoundVerifier {
classType = ClassType.!(thisType);
} else if (VstComponent.?(compound)) {
isImport = VstComponent.!(compound).importName != null;
} else if (VstTrait.?(compound) || VstImpl.?(compound)) {
methodOnly = true;
}
}
def resolveSuperClass() {
Expand Down Expand Up @@ -505,7 +532,8 @@ class VstCompoundVerifier {
mdecl.receiver = decl;
}
var constructor = decl.constructor;
if (constructor == null && !VstLayout.?(decl)) {
// TODO: more readable code for deciding when to generate constructors
if (constructor == null && !VstLayout.?(decl) && !VstTrait.?(decl) && !VstImpl.?(decl)) {
// fill in a default constructor if one wasn't declared
var name = Token.new(decl.token.fileName, "new", decl.token.beginLine, decl.token.beginColumn);
var superclause = decl.superclause;
Expand Down Expand Up @@ -555,7 +583,6 @@ class VstCompoundVerifier {
if (compound.constructor != null) {
compound.constructor.memberinits = Lists.reverse(memberinits);
}
verified = true;
if (classType != null) {
initMax = 1 + superInitOrder;
for (l = fields; l != null; l = l.tail) {
Expand All @@ -566,6 +593,7 @@ class VstCompoundVerifier {
if (p.member != null) p.member.initOrder = initMax;
}
}
verified = true;
}
def buildTypeEnv(parent: TypeEnv, typeParams: List<TypeParamType>) -> TypeEnv {
var typeEnv = TypeEnv.new(parent, typeParams, null);
Expand All @@ -578,6 +606,7 @@ class VstCompoundVerifier {
}
def checkField(decl: VstField) {
if (isImport) errAtDecl(decl).ImportError("imported components cannot contain field declarations");
if (methodOnly) errAtDecl(decl).ImportError("trait/impl cannot contain field declarations");
if (decl.tref != null) {
decl.vtype = resolveType(decl.tref, compound.typeEnv);
} else if (decl.init == null) {
Expand All @@ -602,6 +631,7 @@ class VstCompoundVerifier {
if (compound.isVariant() && !decl.synthetic) errAtDecl(decl).FieldDeclError("type cannot have an explicit field");
}
def checkNew(decl: VstNew) {
if (methodOnly) errAtDecl(decl).ConstructorError("trait/impl cannot contain constructors");
typeEnv = compound.typeEnv;
var map: Map<string, Decl> = Strings.newMap();
Lists.apply(decl.func.params.list, checkNewParam(_, decl, map));
Expand Down Expand Up @@ -637,9 +667,14 @@ class VstCompoundVerifier {
"index operation cannot be declared in component");
errAtDecl(decl).IndexDeclError(msg);
}
if (compound.isVariantCase() && EmptyStmt.?(decl.func.body)) {
errAtStmt(decl.func.body).UnimplementedError("variant method must have a body");
// checks for empty method body
if (EmptyStmt.?(decl.func.body)) {
if (VstImpl.?(compound)) errAtStmt(decl.func.body).UnimplementedError("impl method must have a body");
if (compound.isVariantCase()) errAtStmt(decl.func.body).UnimplementedError("variant method must have a body");
} else {
if (VstTrait.?(compound)) errAtStmt(decl.func.body).MethodDeclError("trait must only contain signatures");
}
// params handling
if (decl.typeParams != null) typeEnv = decl.typeEnv = buildTypeEnv(typeEnv, decl.typeParams);
if (decl.func.params.list != null) {
var map = Strings.newMap<Decl>();
Expand Down Expand Up @@ -669,6 +704,7 @@ class VstCompoundVerifier {
}
def checkLayoutField(decl: VstLayoutField) {
if (isImport) errAtDecl(decl).ImportError("imported layouts cannot contain field declarations");
if (methodOnly) errAtDecl(decl).ImportError("trait/impl cannot contain layout field declarations");

var mtref = decl.mtref;
mtref.repeatCount = tryUnboxPositiveInt(decl.mtref.repeat, 1);
Expand Down
19 changes: 19 additions & 0 deletions aeneas/src/vst/Vst.v3
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
class VstModule {
def classes = Vector<VstClass>.new();
def components = Vector<VstComponent>.new();
def traits = Vector<VstTrait>.new();
def impls = Vector<VstImpl>.new();
def enums = Vector<VstEnum>.new();
def exports = Vector<ExportDecl>.new();
def layouts = Vector<VstLayout>.new();
Expand All @@ -19,6 +21,8 @@ class VstModule {
class VstFile extends ParsedFile {
def classes = Vector<VstClass>.new();
def components = Vector<VstComponent>.new();
def traits = Vector<VstTrait>.new();
def impls = Vector<VstImpl>.new();
def enums = Vector<VstEnum>.new();
def exports = Vector<ExportDecl>.new();
def layouts = Vector<VstLayout>.new();
Expand Down Expand Up @@ -174,6 +178,20 @@ class VstClass extends VstCompound {
superclass, superclause: SuperClause, members: List<VstMember>)
super(isPrivate, name, typeParams, params, superclause, members) { }
}
// Parsed "trait X { ... }"
class VstTrait extends VstCompound {
def selfType: TypeParamType; // nullable, if not null then {typeParams[0] == selfType}
new(isPrivate: bool, name: Token, selfType, typeParams: List<TypeParamType>, members: List<VstMember>)
super(isPrivate, name, if(selfType == null, typeParams, List.new(selfType, typeParams)), null, null, members) {}
}
// Parsed "impl MyTrait for X { ... }"
// [RV]: what to set for {name}?
class VstImpl extends VstCompound {
def trait_: VstIdent<TypeRef>;
def clazz: VstIdent<TypeRef>;
new(name: Token, typeParams: List<TypeParamType>, trait_, clazz, members: List<VstMember>)
super(false, name, typeParams, null, null, members) {}
}
// Parsed "component X { ... }"
class VstComponent extends VstCompound {
def importName: Token;
Expand Down Expand Up @@ -441,6 +459,7 @@ class VstField extends VstMember {
def getType() -> Type { return vtype; }
}

// [RV]: figure out what super-clause does
class SuperClause {
def point: FilePoint;
def args: TupleExpr;
Expand Down
4 changes: 4 additions & 0 deletions aeneas/src/vst/VstPrinter.v3
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ class Printer(printer: VstPrinter) {
def printComponent(cdecl: VstComponent) {
printVstCompound("component ", cdecl);
}
def printTrait(tdecl: VstTrait) {
printVstCompound("trait ", tdecl);
}
def printLayout(p: VstLayout) {
printVstCompound("layout ", p);
}
Expand Down Expand Up @@ -210,6 +213,7 @@ class VstPrinter extends VstVisitor<int, void> {
}
def printProgram(prog: Program) {
prog.vst.components.apply(p.printComponent);
prog.vst.traits.apply(p.printTrait);
prog.vst.classes.apply(p.printClass);
prog.vst.packings.apply(p.printPacking);
prog.vst.layouts.apply(p.printLayout);
Expand Down
24 changes: 24 additions & 0 deletions test/feature/traits.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// v3c -print-vst -traits -symbols test/feature/traits.v3

// Represents a class that can be serialized into {T}/
trait<A> Serializable<T> {
def fromData(data: T) -> A;
def toData(self: A) -> T;
}

impl Serializable<string> for i32 {
def fromData(data: string) -> i32 { return 0; }
def toData(self: i32) -> string { return "0"; }
}

class ConstStringBox<T>(value: T) {}

impl<T> Serializable<string> for ConstStringBox<T> {
def fromData(data: string) -> ConstStringBox<T> { return ConstStringBox.new("foo"); }
def toData(self: ConstStringBox<T>) -> string { return "bar"; }
}

def main() -> int {
var a = 12;
return a;
}