|
23 | 23 | Read `io` in the LP file format and store the result in `model`. |
24 | 24 |
|
25 | 25 | This reader attempts to follow the CPLEX LP format, because others like the |
26 | | -lpsolve version are very...flexible...in how they accept input. Read more about |
27 | | -them here: http://lpsolve.sourceforge.net |
| 26 | +lpsolve version are very...flexible...in how they accept input. |
| 27 | +
|
| 28 | +Read more about the format here: |
| 29 | + * http://lpsolve.sourceforge.net |
| 30 | + * https://web.mit.edu/lpsolve/doc/CPLEX-format.htm |
28 | 31 | """ |
29 | 32 | function Base.read!(io::IO, model::Model{T}) where {T} |
30 | 33 | if !MOI.is_empty(model) |
@@ -98,6 +101,7 @@ const _KEYWORDS = Dict( |
98 | 101 | "such that" => :CONSTRAINTS, |
99 | 102 | "st" => :CONSTRAINTS, |
100 | 103 | "s.t." => :CONSTRAINTS, |
| 104 | + "st." => :CONSTRAINTS, |
101 | 105 | # BOUNDS |
102 | 106 | "bounds" => :BOUNDS, |
103 | 107 | "bound" => :BOUNDS, |
@@ -244,7 +248,16 @@ function Base.read(state::LexerState, ::Type{Token}, kind::_TokenKind) |
244 | 248 | return _expect(token, kind) |
245 | 249 | end |
246 | 250 |
|
247 | | -_is_idenfifier(c::Char) = !(isspace(c) || c in ('+', '-', '*', '^', ':')) |
| 251 | +# We're a bit more relaxed than typical, allowing any letter or digit, not just |
| 252 | +# ASCII. |
| 253 | +function _is_identifier(c::Char) |
| 254 | + return isletter(c) || isdigit(c) || c in "!\"#\$%&()/,.;?@_`'{}|~" |
| 255 | +end |
| 256 | + |
| 257 | +function _is_starting_identifier(c::Char) |
| 258 | + return isletter(c) || c in "!\"#\$%&(),;?@_`'{}|~" |
| 259 | +end |
| 260 | + |
248 | 261 | _is_number(c::Char) = isdigit(c) || c in ('.', 'e', 'E', '+', '-') |
249 | 262 |
|
250 | 263 | function Base.peek(state::LexerState, ::Type{Token}, n::Int = 1) |
@@ -276,9 +289,9 @@ function _peek_inner(state::LexerState) |
276 | 289 | read(state, Char) |
277 | 290 | end |
278 | 291 | return Token(_TOKEN_NUMBER, String(take!(buf))) |
279 | | - elseif isletter(c) || c == '_' # Identifier / keyword |
| 292 | + elseif _is_starting_identifier(c) # Identifier / keyword |
280 | 293 | buf = IOBuffer() |
281 | | - while (c = peek(state, Char)) !== nothing && _is_idenfifier(c) |
| 294 | + while (c = peek(state, Char)) !== nothing && _is_identifier(c) |
282 | 295 | write(buf, c) |
283 | 296 | read(state, Char) |
284 | 297 | end |
@@ -382,7 +395,11 @@ function _parse_number(state::LexerState, cache::Cache{T})::T where {T} |
382 | 395 | end |
383 | 396 | end |
384 | 397 | _expect(token, _TOKEN_NUMBER) |
385 | | - return parse(T, token.value) |
| 398 | + ret = tryparse(T, token.value) |
| 399 | + if ret === nothing |
| 400 | + throw(UnexpectedToken(token)) |
| 401 | + end |
| 402 | + return ret |
386 | 403 | end |
387 | 404 |
|
388 | 405 | # QUAD_TERM := |
|
0 commit comments