Skip to content

Commit 4895a69

Browse files
authored
[FileFormats.LP] Add some keywords without surrounding new lines (#2853)
1 parent 4225805 commit 4895a69

File tree

2 files changed

+53
-22
lines changed

2 files changed

+53
-22
lines changed

src/FileFormats/LP/read.jl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,38 @@ function Base.read!(io::IO, model::Model{T}) where {T}
8282
"No file contents are allowed after `end`.",
8383
)
8484
else
85+
if token.kind == _TOKEN_IDENTIFIER
86+
# We didn't identify the token as an keyword during lexing. But
87+
# it might be one that is missing surrounding `\n`. Since our
88+
# alternative at this point is to throw an error, we ,might as
89+
# well attempt to see it can be interpreted as one.
90+
kw = _case_insenstive_identifier_to_keyword(token.value)
91+
if kw !== nothing
92+
_ = read(state, _Token, _TOKEN_IDENTIFIER)
93+
keyword = Symbol(kw)
94+
continue
95+
elseif _compare_case_insenstive(token.value, "subject")
96+
p = peek(state, _Token, 2)
97+
if p !== nothing && p.kind == _TOKEN_IDENTIFIER
98+
if _compare_case_insenstive(p.value, "to")
99+
_ = read(state, _Token, _TOKEN_IDENTIFIER)
100+
_ = read(state, _Token, _TOKEN_IDENTIFIER)
101+
keyword = :CONSTRAINTS
102+
continue
103+
end
104+
end
105+
elseif _compare_case_insenstive(token.value, "such")
106+
p = peek(state, _Token, 2)
107+
if p !== nothing && p.kind == _TOKEN_IDENTIFIER
108+
if _compare_case_insenstive(p.value, "that")
109+
_ = read(state, _Token, _TOKEN_IDENTIFIER)
110+
_ = read(state, _Token, _TOKEN_IDENTIFIER)
111+
keyword = :CONSTRAINTS
112+
continue
113+
end
114+
end
115+
end
116+
end
85117
_expect(state, token, _TOKEN_KEYWORD)
86118
end
87119
end

test/FileFormats/LP/LP.jl

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,10 +1490,7 @@ function test_parse_term()
14901490
term = LP._parse_term(state, cache, -1.0)
14911491
@test term == MOI.ScalarAffineTerm(-coef, x)
14921492
end
1493-
for (input, reason) in [
1494-
"subject to" => "Got a keyword defining a new section with value `CONSTRAINTS`.",
1495-
">= 1" => "Got the symbol `>=`.",
1496-
]
1493+
for (input, reason) in [">= 1" => "Got the symbol `>=`."]
14971494
io = IOBuffer(input)
14981495
state = LP._LexerState(io)
14991496
@test_parse_error(
@@ -1545,14 +1542,14 @@ function test_parse_set_prefix()
15451542
state = LP._LexerState(io)
15461543
@test LP._parse_set_prefix(state, cache) == set
15471544
end
1548-
io = IOBuffer("->")
1545+
io = IOBuffer("1.0 ->")
15491546
state = LP._LexerState(io)
15501547
@test_parse_error(
15511548
"""
15521549
Error parsing LP file on line 1:
1553-
->
1554-
^
1555-
Got the symbol `->`. We expected this token to be a number.""",
1550+
1.0 ->
1551+
^
1552+
Got the symbol `->`. We expected this to be an inequality like `>=`, `<=`, or `==`.""",
15561553
LP._parse_set_prefix(state, cache),
15571554
)
15581555
return
@@ -1642,7 +1639,7 @@ function test_new_line_edge_cases_sos()
16421639
return
16431640
end
16441641

1645-
function test_new_line_edge_case_fails()
1642+
function test_missing_new_line_edge_cases()
16461643
for input in [
16471644
# No newline between objective sense and objective
16481645
"minimize x",
@@ -1655,7 +1652,8 @@ function test_new_line_edge_case_fails()
16551652
]
16561653
io = IOBuffer(input)
16571654
model = LP.Model()
1658-
@test_throws LP.ParseError MOI.read!(io, model)
1655+
MOI.read!(io, model)
1656+
@test MOI.get(model, MOI.VariableIndex, "x") isa MOI.VariableIndex
16591657
end
16601658
return
16611659
end
@@ -1680,7 +1678,7 @@ function test_parse_keyword_edge_cases_identifier_is_keyword()
16801678
end
16811679

16821680
function test_parse_keyword_subject_to_errors()
1683-
for line in ["subject", "subject too", "subject to a:"]
1681+
for line in ["subject", "subject too"]
16841682
io = IOBuffer("""
16851683
maximize
16861684
obj: x
@@ -1755,17 +1753,18 @@ function test_parse_quadratic_expr_eof()
17551753
end
17561754

17571755
function test_ambiguous_case_1()
1758-
# Xpress allows this. We currently don't.
1759-
io = IOBuffer("maximize obj: x subject to c: x <= 1 end")
1760-
model = LP.Model()
1761-
@test_parse_error(
1762-
"""
1763-
Error parsing LP file on line 1:
1764-
maximize obj: x subject to c: x <= 1 end
1765-
^
1766-
Got an identifier with value `maximize`. We expected this token to be a keyword defining a new section.""",
1767-
MOI.read!(io, model),
1768-
)
1756+
# Xpress allows this. We currently do too.
1757+
for kw in ("subject to", "such that", "st")
1758+
io = IOBuffer("maximize obj: x $kw c: x <= 1\nend")
1759+
model = LP.Model()
1760+
MOI.read!(io, model)
1761+
@test MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE
1762+
F, S = MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}
1763+
@test isa(
1764+
MOI.get(model, MOI.ConstraintIndex, "c"),
1765+
MOI.ConstraintIndex{F,S},
1766+
)
1767+
end
17691768
return
17701769
end
17711770

0 commit comments

Comments
 (0)