Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] - 8.4 Asymmetric Visibility support #1148

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
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
32 changes: 2 additions & 30 deletions src/ast/classconstant.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
const ConstantStatement = require("./constantstatement");
const KIND = "classconstant";

const IS_UNDEFINED = "";
const IS_PUBLIC = "public";
const IS_PROTECTED = "protected";
const IS_PRIVATE = "private";

/**
* Defines a class/interface/trait constant
* @constructor ClassConstant
Expand All @@ -37,35 +32,12 @@ const ClassConstant = ConstantStatement.extends(
location,
) {
ConstantStatement.apply(this, [kind || KIND, constants, docs, location]);
this.parseFlags(flags);
this.nullable = nullable;
this.type = type;
this.attrGroups = attrGroups;
this.visibility = flags.compute_visibility;
this.final = flags.isFinal;
},
);

/**
* Generic flags parser
* @function
* @name ClassConstant#parseFlags
* @memberOf module:php-parser
* @param {Array<number|null>} flags
* @return {void}
*/
ClassConstant.prototype.parseFlags = function (flags) {
if (flags[0] === -1) {
this.visibility = IS_UNDEFINED;
} else if (flags[0] === null) {
/* istanbul ignore next */
this.visibility = null;
} else if (flags[0] === 0) {
this.visibility = IS_PUBLIC;
} else if (flags[0] === 1) {
this.visibility = IS_PROTECTED;
} else if (flags[0] === 2) {
this.visibility = IS_PRIVATE;
}
this.final = flags[2] === 2;
};

module.exports = ClassConstant;
28 changes: 6 additions & 22 deletions src/ast/declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
const Statement = require("./statement");
const KIND = "declaration";

const IS_UNDEFINED = "";
const IS_PUBLIC = "public";
const IS_PROTECTED = "protected";
const IS_PRIVATE = "private";

/**
* A declaration statement (function, class, interface...)
* @constructor Declaration
Expand All @@ -33,27 +28,16 @@ const Declaration = Statement.extends(
* @function
* @name Declaration#parseFlags
* @memberOf module:php-parser
* @param {Array<number|null>} flags
* @param {MemberFlags} flags
* @return {void}
*/
Declaration.prototype.parseFlags = function (flags) {
this.isAbstract = flags[2] === 1;
this.isFinal = flags[2] === 2;
this.isReadonly = flags[3] === 1;
this.isAbstract = flags.isAbstract;
this.isFinal = flags.isFinal;
this.isReadonly = flags.isReadonly;
if (this.kind !== "class") {
if (flags[0] === -1) {
this.visibility = IS_UNDEFINED;
} else if (flags[0] === null) {
/* istanbul ignore next */
this.visibility = null;
} else if (flags[0] === 0) {
this.visibility = IS_PUBLIC;
} else if (flags[0] === 1) {
this.visibility = IS_PROTECTED;
} else if (flags[0] === 2) {
this.visibility = IS_PRIVATE;
}
this.isStatic = flags[1] === 1;
this.visibility = flags.compute_visibility;
this.isStatic = flags.isStatic;
}
};

Expand Down
31 changes: 2 additions & 29 deletions src/ast/propertystatement.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
const Statement = require("./statement");
const KIND = "propertystatement";

const IS_UNDEFINED = "";
const IS_PUBLIC = "public";
const IS_PROTECTED = "protected";
const IS_PRIVATE = "private";

/**
* Declares a properties into the current scope
* @constructor PropertyStatement
Expand All @@ -27,31 +22,9 @@ const PropertyStatement = Statement.extends(
function PropertyStatement(kind, properties, flags, docs, location) {
Statement.apply(this, [KIND, docs, location]);
this.properties = properties;
this.parseFlags(flags);
this.visibility = flags.compute_visibility;
this.isStatic = flags.isStatic;
},
);

/**
* Generic flags parser
* @function PropertyStatement#parseFlags
* @memberOf module:php-parser
* @param {Array<number|null>} flags
* @return {void}
*/
PropertyStatement.prototype.parseFlags = function (flags) {
if (flags[0] === -1) {
this.visibility = IS_UNDEFINED;
} else if (flags[0] === null) {
this.visibility = null;
} else if (flags[0] === 0) {
this.visibility = IS_PUBLIC;
} else if (flags[0] === 1) {
this.visibility = IS_PROTECTED;
} else if (flags[0] === 2) {
this.visibility = IS_PRIVATE;
}

this.isStatic = flags[1] === 1;
};

module.exports = PropertyStatement;
24 changes: 20 additions & 4 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ const Parser = function (lexer, ast) {
this.tok.T_FINAL,
].map(mapIt),
),
T_VISIBILITY_FLAGS: new Map(
[this.tok.T_PUBLIC, this.tok.T_PRIVATE, this.tok.T_PROTECTED].map(mapIt),
),
EOS: new Map([";", this.EOF, this.tok.T_INLINE_HTML].map(mapIt)),
EXPR: new Map(
[
Expand Down Expand Up @@ -706,14 +709,27 @@ Parser.prototype.lex = function () {

/**
* Check if token is of specified type
* @function Parser#is
* @function Parser#_is
* @memberOf module:php-parser
*/
Parser.prototype.is = function (type) {
Parser.prototype._is = function (token, type) {
if (Array.isArray(type)) {
return type.indexOf(this.token) !== -1;
return type.indexOf(token) !== -1;
}
return this.entries[type].has(this.token);
return this.entries[type].has(token);
};

/**
* Check if token is of specified type
* @function Parser#is
* @memberOf module:php-parser
*/
Parser.prototype.is = function (type) {
return this._is(this.token, type);
};

Parser.prototype.peek_is = function (type) {
return this._is(this.peek(), type);
};

// extends the parser with syntax files
Expand Down
119 changes: 57 additions & 62 deletions src/parser/class.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*/
"use strict";

const MemberFlags = require("./utils").MemberFlags;

module.exports = {
/*
* reading a class
Expand Down Expand Up @@ -36,35 +38,28 @@ module.exports = {
},

read_class_modifiers: function () {
const modifier = this.read_class_modifier({
readonly: 0,
final_or_abstract: 0,
});
return [0, 0, modifier.final_or_abstract, modifier.readonly];
const flags = new MemberFlags();
this.read_class_modifier(flags);
if (flags.isFinal && flags.isAbstract) {
this.raiseError("A class can not be final and abstract");
}
return flags;
},

read_class_modifier: function (memo) {
read_class_modifier: function (flags) {
if (this.token === this.tok.T_READ_ONLY) {
this.next();
memo.readonly = 1;
memo = this.read_class_modifier(memo);
} else if (
memo.final_or_abstract === 0 &&
this.token === this.tok.T_ABSTRACT
) {
flags.isReadonly = true;
this.read_class_modifier(flags);
} else if (this.token === this.tok.T_ABSTRACT) {
this.next();
memo.final_or_abstract = 1;
memo = this.read_class_modifier(memo);
} else if (
memo.final_or_abstract === 0 &&
this.token === this.tok.T_FINAL
) {
flags.isAbstract = true;
this.read_class_modifier(flags);
} else if (this.token === this.tok.T_FINAL) {
this.next();
memo.final_or_abstract = 2;
memo = this.read_class_modifier(memo);
flags.isFinal = true;
this.read_class_modifier(flags);
}

return memo;
},

/*
Expand Down Expand Up @@ -274,66 +269,66 @@ module.exports = {
},
/*
* Read member flags
* @return array
* 1st index : 0 => public, 1 => protected, 2 => private
* 2nd index : 0 => instance member, 1 => static member
* 3rd index : 0 => normal, 1 => abstract member, 2 => final member
* @return MemberFlags
*/
read_member_flags: function (asInterface) {
const result = [-1, -1, -1];
const flags = new MemberFlags();

if (this.is("T_MEMBER_FLAGS")) {
let idx = 0,
val = 0;
do {
switch (this.token) {
case this.tok.T_PUBLIC:
idx = 0;
val = 0;
break;
case this.tok.T_PROTECTED:
idx = 0;
val = 1;
break;
case this.tok.T_PRIVATE:
idx = 0;
val = 2;
break;
case this.tok.T_STATIC:
idx = 1;
val = 1;
flags.isStatic = true;
break;
case this.tok.T_ABSTRACT:
idx = 2;
val = 1;
flags.isAbstract = true;
break;
case this.tok.T_FINAL:
idx = 2;
val = 2;
flags.isFinal = true;
break;
}

if (this.is("T_VISIBILITY_FLAGS")) {
if (this.peek_is("T_VISIBILITY_FLAGS")) {
flags.read_visibility = this.token;
this.next();
}
if (this.version >= 804 && this.peek() === "(") {
const visibility_token = this.token;
this.next();
this.next();
if (this.text() !== "set") {
this.raiseError("Expecting set keyword");
}
this.next();
this.expect(")");
flags.write_visibility = visibility_token;
} else {
flags.visibility = this.token;
// this.next();
}
}

if (flags.isAbstract && flags.isFinal) {
this.error();
}

if (asInterface) {
if (idx === 0 && val === 2) {
// an interface can't be private
this.expect([this.tok.T_PUBLIC, this.tok.T_PROTECTED]);
val = -1;
} else if (idx === 2 && val === 1) {
if (flags.isAbstract) {
// an interface cant be abstract
this.error();
val = -1;
flags.isAbstract = false;
}
// an interface can't be private
if (flags.isPrivate) {
this.expect([this.tok.T_PUBLIC, this.tok.T_PROTECTED]);
flags.visibility = null;
}
}
if (result[idx] !== -1) {
// already defined flag
this.error();
} else if (val !== -1) {
result[idx] = val;
}
} while (this.next().is("T_MEMBER_FLAGS"));
}

if (result[1] === -1) result[1] = 0;
if (result[2] === -1) result[2] = 0;
return result;
return flags;
},

/*
Expand Down
10 changes: 9 additions & 1 deletion src/parser/expr.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
"use strict";

const { MemberFlags } = require("./utils");
module.exports = {
read_expr: function (expr) {
const result = this.node();
Expand Down Expand Up @@ -768,7 +769,14 @@ module.exports = {
if (this.expect("{")) {
body = this.next().read_class_body(true, false);
}
const whatNode = what(null, propExtends, propImplements, body, [0, 0, 0]);

const whatNode = what(
null,
propExtends,
propImplements,
body,
new MemberFlags(),
);
whatNode.attrGroups = attrs;
return result(whatNode, args);
}
Expand Down
Loading
Loading