From f070daab5ebd2c6162d4a68bacb0c867e0e86bc3 Mon Sep 17 00:00:00 2001 From: johnpatrickmorgan Date: Thu, 5 Nov 2020 22:42:31 +0000 Subject: [PATCH] Ensures wrapped VFL extents are parsed correctly (#20) --- ...nstraintsParser+AutoLayoutPrimitives.swift | 10 ++ ...ConstraintsParser+EquationConstraint.swift | 3 +- .../ConstraintsParser+VFLConstraint.swift | 12 +- .../ParserTests/testGitHubIssues.9.json | 125 ++++++++++++++++++ 4 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 Tests/AppTests/__Snapshots__/ParserTests/testGitHubIssues.9.json diff --git a/Sources/App/Parsing/ConstraintsParser+AutoLayoutPrimitives.swift b/Sources/App/Parsing/ConstraintsParser+AutoLayoutPrimitives.swift index f0aac47..b51622d 100644 --- a/Sources/App/Parsing/ConstraintsParser+AutoLayoutPrimitives.swift +++ b/Sources/App/Parsing/ConstraintsParser+AutoLayoutPrimitives.swift @@ -23,6 +23,12 @@ extension ConstraintsParser { static let number = numberString.map(parseDouble) .named("number") + + static let spacing = number.otherwise(spacedNumber) + + static let defaultedRelation = optional(relation).map { $0 ?? .equal } + + static let extent = defaultedRelation.then(spacing) } private extension ConstraintsParser { @@ -35,6 +41,10 @@ private extension ConstraintsParser { static let decimal = signedInteger.then(optional(postDecimal)).map { "\($0)\($1 ?? "")" } static let numberString = decimal.then(optional(exponent)).map { "\($0)\($1 ?? "")" } + static let spacingTerm = string("NSSpace") + .otherwise(string("NSLayoutAnchorConstraintSpace")) + static let spacedNumber = spacingTerm.skipThen(string("(")).skipThen(number).thenSkip(")") + static func parserForAttribute(_ attribute: Attribute) -> Parser { let attributeParser = attribute.labels.reduce(pureError(), { string($1).otherwise($0) }) return attributeParser.map { _ in return attribute } diff --git a/Sources/App/Parsing/ConstraintsParser+EquationConstraint.swift b/Sources/App/Parsing/ConstraintsParser+EquationConstraint.swift index b324d54..4c85811 100644 --- a/Sources/App/Parsing/ConstraintsParser+EquationConstraint.swift +++ b/Sources/App/Parsing/ConstraintsParser+EquationConstraint.swift @@ -24,9 +24,8 @@ extension ConstraintsParser { private extension ConstraintsParser { static let layoutItemAttribute = partialInstance.then(dotAttribute) - static let anyConstant = nsSpaceConstant.otherwise(constant) + static let anyConstant = extent.map { Constant($1) }.otherwise(constant) static let constant = number.map(Constant.init).otherwise(pure(Constant())) - static let nsSpaceConstant = unbracketedNSSpace.map { Constant($0.1) } static let preMultiplier = optional(number.thenSkip(string("*")).map(Multiplier.init)) .named("prefixed multiplier") static let postMultiplier = optional(string("*").skipThen(wss).skipThen(number).map(Multiplier.init)) diff --git a/Sources/App/Parsing/ConstraintsParser+VFLConstraint.swift b/Sources/App/Parsing/ConstraintsParser+VFLConstraint.swift index e25edb2..a8225b9 100644 --- a/Sources/App/Parsing/ConstraintsParser+VFLConstraint.swift +++ b/Sources/App/Parsing/ConstraintsParser+VFLConstraint.swift @@ -6,19 +6,14 @@ import Sparse extension ConstraintsParser { - static let vflSpaceConstraint = vflAxis.then(vflBoundedEntity).thenSkip(dash).then(spacer).thenSkip(dash).then(vflBoundedEntity).thenSkip(vflDirection).then(optionalInfo) + static let vflSpaceConstraint = vflAxis.then(vflBoundedEntity).thenSkip(dash).then(vflExtent).thenSkip(dash).then(vflBoundedEntity).thenSkip(vflDirection).then(optionalInfo) .map(flatten).map(AnonymousConstraint.init) static let vflExtentConstraint = vflAxis.thenSkip(character("[")).then(vflEntity).then(vflExtent).thenSkip(character("]")).thenSkip(vflDirection).then(optionalInfo) .map(flatten).map(AnonymousConstraint.init) static let vflConstraint = vflExtentConstraint.otherwise(vflSpaceConstraint) - - static let unbracketedNSSpace = string("NSSpace") - .otherwise(string("NSLayoutAnchorConstraintSpace")) - .skipThen(vflExtent) - - static let nsSpacer = character("(").skipThen(unbracketedNSSpace).thenSkip(character(")")) + static let vflExtent = character("(").skipThen(extent).thenSkip(character(")")) } // MARK: - Private @@ -29,12 +24,9 @@ private extension ConstraintsParser { static let vAxis = character("V").map { _ in Attribute.Axis.vertical } static let superview = character("|") static let vflAxis = hAxis.otherwise(vAxis).thenSkip(colon) - static let vflRelation = optional(relation).map { $0 ?? .equal } - static let vflExtent = character("(").skipThen(vflRelation).then(number).thenSkip(character(")")) static let vflIdentifierCharacter = characterNot(in: "[]|()") static let vflIdentifier = many(identifierCharacter.and(vflIdentifierCharacter)).asString() static let vflEntity = instance.map({ PartialInstance.instance($0) }).otherwise(vflIdentifier.map({ PartialInstance.identifier($0) })) static let vflBoundedEntity = character("[").skipThen(vflEntity).thenSkip(character("]")).otherwise(string("|").map({ _ in PartialInstance.superview })) static let vflDirection = optional(string("(LTR)").otherwise(string("(RTL)"))) - static let spacer = vflExtent.otherwise(nsSpacer) } diff --git a/Tests/AppTests/__Snapshots__/ParserTests/testGitHubIssues.9.json b/Tests/AppTests/__Snapshots__/ParserTests/testGitHubIssues.9.json new file mode 100644 index 0000000..84b3f26 --- /dev/null +++ b/Tests/AppTests/__Snapshots__/ParserTests/testGitHubIssues.9.json @@ -0,0 +1,125 @@ +{ + "constraints" : [ + { + "constant" : { + "prefix" : "+ ", + "value" : "8" + }, + "description" : "Label<\/code>'s top edge should be at least ClassName<\/code>'s top edge plus 8.", + "first" : { + "attribute" : { + "includesMargin" : false, + "name" : "top" + }, + "instance" : { + "address" : "0x111b35c20", + "className" : "UILabel", + "color" : "rgb(243,156,18)", + "initial" : "L", + "name" : "Label", + "suffix" : "" + } + }, + "identity" : { + "address" : "0x2828051d0", + "className" : "_UISystemBaselineConstraint", + "color" : "rgb(26,188,156)", + "initial" : "U", + "name" : "_UISystemBaselineConstraint" + }, + "relation" : ">=", + "second" : { + "attribute" : { + "includesMargin" : false, + "name" : "top" + }, + "instance" : { + "address" : "0x111b35a60", + "className" : "ModuleName.ClassName", + "color" : "rgb(192,57,43)", + "initial" : "C", + "name" : "ClassName", + "suffix" : "" + } + } + }, + { + "description" : "Label<\/code>'s vertical center should equal ClassName<\/code>'s vertical center.", + "first" : { + "attribute" : { + "includesMargin" : false, + "name" : "centerY" + }, + "instance" : { + "address" : "0x111b35c20", + "className" : "UILabel", + "color" : "rgb(243,156,18)", + "initial" : "L", + "name" : "Label", + "suffix" : "" + } + }, + "identity" : { + "address" : "0x282805130", + "className" : "NSLayoutConstraint", + "color" : "rgb(26,188,156)", + "initial" : "N", + "name" : "NSLayoutConstraint" + }, + "relation" : "==", + "second" : { + "attribute" : { + "includesMargin" : false, + "name" : "centerY" + }, + "instance" : { + "address" : "0x111b35a60", + "className" : "ModuleName.ClassName", + "color" : "rgb(192,57,43)", + "initial" : "C", + "name" : "ClassName", + "suffix" : "" + } + } + }, + { + "constant" : { + "value" : "0" + }, + "description" : "ClassName<\/code>'s height should equal 0.", + "first" : { + "attribute" : { + "includesMargin" : false, + "name" : "height" + }, + "instance" : { + "address" : "0x111b35a60", + "className" : "ModuleName.ClassName", + "color" : "rgb(192,57,43)", + "initial" : "C", + "name" : "ClassName", + "suffix" : "" + } + }, + "footnote" : { + "marker" : "†", + "text" : "This constraint was added by a table or collection view to enforce its cell size." + }, + "identity" : { + "address" : "0x282803ed0", + "className" : "NSLayoutConstraint", + "color" : "rgb(26,188,156)", + "identifier" : "UIView-Encapsulated-Layout-Height", + "initial" : "U", + "name" : "UIView-Encapsulated-Layout-Height" + }, + "relation" : "==" + } + ], + "footnotes" : [ + { + "marker" : "†", + "text" : "This constraint was added by a table or collection view to enforce its cell size." + } + ] +} \ No newline at end of file