@@ -35,11 +35,11 @@ function Base.read!(io::IO, model::Model{T}) where {T}
3535 keyword = :UNKNOWN
3636 while (token = peek (state, Token)) != = nothing
3737 if token. kind == _TOKEN_KEYWORD
38- read (state, Token)
38+ _ = read (state, Token)
3939 keyword = Symbol (token. value)
4040 continue
4141 elseif token. kind == _TOKEN_NEWLINE
42- read (state, Token)
42+ _ = read (state, Token)
4343 continue
4444 elseif keyword == :MINIMIZE
4545 MOI. set (cache. model, MOI. ObjectiveSense (), MOI. MIN_SENSE)
@@ -173,6 +173,28 @@ struct Token
173173 value:: Union{Nothing,String}
174174end
175175
176+ """
177+ struct UnexpectedToken <: Exception
178+ token::Token
179+ end
180+
181+ This error is thrown when we encounter an unexpected token when parsing the LP
182+ file. No other information is available.
183+
184+ TODO: we could improve this by storing line information or other context to help
185+ the user diagnose the problem.
186+ """
187+ struct UnexpectedToken <: Exception
188+ token:: Token
189+ end
190+
191+ function _expect (token:: Token , kind:: _TokenKind )
192+ if token. kind != kind
193+ throw (UnexpectedToken (token))
194+ end
195+ return token
196+ end
197+
176198"""
177199 mutable struct LexerState
178200 io::IO
@@ -210,10 +232,18 @@ end
210232
211233function Base. read (state:: LexerState , :: Type{Token} )
212234 token = peek (state, Token, 1 )
235+ if isempty (state. peek_tokens)
236+ throw (UnexpectedToken (Token (_TOKEN_UNKNOWN, " EOF" )))
237+ end
213238 popfirst! (state. peek_tokens)
214239 return token
215240end
216241
242+ function Base. read (state:: LexerState , :: Type{Token} , kind:: _TokenKind )
243+ token = read (state, Token)
244+ return _expect (token, kind)
245+ end
246+
217247_is_idenfifier (c:: Char ) = ! (isspace (c) || c in (' +' , ' -' , ' *' , ' ^' , ' :' ))
218248_is_number (c:: Char ) = isdigit (c) || c in (' .' , ' e' , ' E' , ' +' , ' -' )
219249
@@ -257,13 +287,13 @@ function _peek_inner(state::LexerState)
257287 if l_val == " subject"
258288 t = peek (state, Token)
259289 if t. kind == _TOKEN_IDENTIFIER && lowercase (t. value) == " to"
260- read (state, Token) # Skip "to"
290+ _ = read (state, Token) # Skip "to"
261291 return Token (_TOKEN_KEYWORD, " CONSTRAINTS" )
262292 end
263293 elseif l_val == " such"
264294 t = peek (state, Token)
265295 if t. kind == _TOKEN_IDENTIFIER && lowercase (t. value) == " that"
266- read (state, Token) # Skip "such"
296+ _ = read (state, Token) # Skip "such"
267297 return Token (_TOKEN_KEYWORD, " CONSTRAINTS" )
268298 end
269299 end
@@ -276,8 +306,11 @@ function _peek_inner(state::LexerState)
276306 if c == ' -' && peek (state, Char) == ' >'
277307 read (state, Char)
278308 return Token (_TOKEN_IMPLIES, " ->" )
309+ elseif c == ' =' && peek (state, Char) in (' <' , ' >' )
310+ c = read (state, Char) # Allow =< and => as <= and >=
311+ return Token (_OPERATORS[c], string (c))
279312 elseif c in (' <' , ' >' , ' =' ) && peek (state, Char) == ' ='
280- read (state, Char) # Allow <=, >=, and ==
313+ _ = read (state, Char) # Allow <=, >=, and ==
281314 end
282315 return Token (op, string (c))
283316 else
@@ -287,28 +320,6 @@ function _peek_inner(state::LexerState)
287320 return
288321end
289322
290- """
291- struct UnexpectedToken <: Exception
292- token::Token
293- end
294-
295- This error is thrown when we encounter an unexpected token when parsing the LP
296- file. No other information is available.
297-
298- TODO: we could improve this by storing line information or other context to help
299- the user diagnose the problem.
300- """
301- struct UnexpectedToken <: Exception
302- token:: Token
303- end
304-
305- function _expect (token:: Token , kind:: _TokenKind )
306- if token. kind != kind
307- throw (UnexpectedToken (token))
308- end
309- return
310- end
311-
312323"""
313324 _next_token_is(state::LexerState, kind::_TokenKind, n::Int = 1)
314325
@@ -323,19 +334,18 @@ end
323334
324335function _skip_newlines (state:: LexerState )
325336 while _next_token_is (state, _TOKEN_NEWLINE)
326- read (state, Token)
337+ _ = read (state, Token)
327338 end
328339 return
329340end
330341
331- # IDENTIFIER --> "string"
342+ # IDENTIFIER := "string"
332343#
333344# There _are_ rules to what an identifier can be. We handle these when lexing.
334345# Anything that makes it here is deemed acceptable.
335346function _parse_variable (state:: LexerState , cache:: Cache ):: MOI.VariableIndex
336347 _skip_newlines (state)
337- token = read (state, Token)
338- _expect (token, _TOKEN_IDENTIFIER)
348+ token = read (state, Token, _TOKEN_IDENTIFIER)
339349 x = get (cache. variable_name_to_index, token. value, nothing )
340350 if x != = nothing
341351 return x
@@ -370,47 +380,49 @@ function _parse_number(state::LexerState, cache::Cache{T})::T where {T}
370380 else
371381 throw (UnexpectedToken (token))
372382 end
373- else
374- _expect (token, _TOKEN_NUMBER)
375383 end
384+ _expect (token, _TOKEN_NUMBER)
376385 return parse (T, token. value)
377386end
378387
379388# QUAD_TERM :=
380389# "+" QUAD_TERM
381390# | "-" QUAD_TERM
382- # | [NUMBER] IDENTIFIER "^" "2"
383- # | [NUMBER] IDENTIFIER "*" IDENTIFIER
391+ # | [NUMBER] [*] IDENTIFIER "^" "2"
392+ # | [NUMBER] [*] IDENTIFIER "*" IDENTIFIER
384393function _parse_quad_term (
385394 state:: LexerState ,
386395 cache:: Cache{T} ,
387396 prefix:: T ,
388397) where {T}
389398 _skip_newlines (state)
390399 if _next_token_is (state, _TOKEN_ADDITION)
391- read (state, Token)
400+ _ = read (state, Token)
392401 return _parse_quad_term (state, cache, prefix)
393402 elseif _next_token_is (state, _TOKEN_SUBTRACTION)
394- read (state, Token)
403+ _ = read (state, Token)
395404 return _parse_quad_term (state, cache, - prefix)
396405 end
397406 coef = prefix
398407 if _next_token_is (state, _TOKEN_NUMBER)
399408 coef = prefix * _parse_number (state, cache)
400409 end
410+ if _next_token_is (state, _TOKEN_MULTIPLICATION)
411+ _skip_newlines (state)
412+ _ = read (state, Token) # Skip optional multiplication
413+ end
401414 x1 = _parse_variable (state, cache)
402415 _skip_newlines (state)
403416 if _next_token_is (state, _TOKEN_EXPONENT)
404- read (state, Token) # ^
417+ _ = read (state, Token) # ^
405418 _skip_newlines (state)
406- n = read (state, Token)
407- if n. kind != _TOKEN_NUMBER && n . value != " 2"
419+ n = read (state, Token, _TOKEN_NUMBER )
420+ if n. value != " 2"
408421 throw (UnexpectedToken (n))
409422 end
410423 return MOI. ScalarQuadraticTerm (T (2 ) * coef, x1, x1)
411424 end
412- token = read (state, Token)
413- _expect (token, _TOKEN_MULTIPLICATION)
425+ token = read (state, Token, _TOKEN_MULTIPLICATION)
414426 x2 = _parse_variable (state, cache)
415427 if x1 == x2
416428 coef *= T (2 )
@@ -426,8 +438,7 @@ function _parse_quad_expression(
426438 cache:: Cache{T} ,
427439 prefix:: T ,
428440) where {T}
429- token = read (state, Token)
430- _expect (token, _TOKEN_OPEN_BRACKET)
441+ token = read (state, Token, _TOKEN_OPEN_BRACKET)
431442 f = zero (MOI. ScalarQuadraticFunction{T})
432443 push! (f. quadratic_terms, _parse_quad_term (state, cache, prefix))
433444 while (p = peek (state, Token)) != = nothing
@@ -438,20 +449,20 @@ function _parse_quad_expression(
438449 p = read (state, Token)
439450 push! (f. quadratic_terms, _parse_quad_term (state, cache, - prefix))
440451 elseif p. kind == _TOKEN_NEWLINE
441- read (state, Token)
452+ _ = read (state, Token)
442453 elseif p. kind == _TOKEN_CLOSE_BRACKET
443- read (state, Token)
454+ _ = read (state, Token)
444455 break
445456 else
446457 return throw (UnexpectedToken (p))
447458 end
448459 end
449460 _skip_newlines (state)
450461 if _next_token_is (state, _TOKEN_DIVISION)
451- read (state, Token) # /
462+ _ = read (state, Token) # /
452463 # Must be /2
453- n = read (state, Token)
454- if n. kind != _TOKEN_NUMBER && n . value != " 2"
464+ n = read (state, Token, _TOKEN_NUMBER )
465+ if n. value != " 2"
455466 throw (UnexpectedToken (n))
456467 end
457468 for (i, term) in enumerate (f. quadratic_terms)
@@ -481,11 +492,11 @@ function _parse_term(
481492 _skip_newlines (state)
482493 if _next_token_is (state, _TOKEN_ADDITION)
483494 # "+" TERM
484- read (state, Token)
495+ _ = read (state, Token, _TOKEN_ADDITION )
485496 return _parse_term (state, cache, prefix)
486497 elseif _next_token_is (state, _TOKEN_SUBTRACTION)
487498 # "-" TERM
488- read (state, Token)
499+ _ = read (state, Token, _TOKEN_SUBTRACTION )
489500 return _parse_term (state, cache, - prefix)
490501 elseif _next_token_is (state, _TOKEN_IDENTIFIER)
491502 # IDENTIFIER
@@ -499,7 +510,7 @@ function _parse_term(
499510 return MOI. ScalarAffineTerm (coef, x)
500511 elseif _next_token_is (state, _TOKEN_MULTIPLICATION)
501512 # NUMBER * IDENTIFIER
502- read (state, token) # skip *
513+ _ = read (state, Token, _TOKEN_MULTIPLICATION)
503514 x = _parse_variable (state, cache)
504515 return MOI. ScalarAffineTerm (coef, x)
505516 else
@@ -510,7 +521,7 @@ function _parse_term(
510521 # QUADRATIC_EXPRESSION
511522 return _parse_quad_expression (state, cache, prefix)
512523 end
513- return nothing
524+ return throw ( UnexpectedToken ( peek (state, Token)))
514525end
515526
516527function _add_to_expression! (f:: MOI.ScalarQuadraticFunction{T} , x:: T ) where {T}
@@ -547,7 +558,7 @@ function _parse_expression(state::LexerState, cache::Cache{T}) where {T}
547558 p = read (state, Token)
548559 _add_to_expression! (f, _parse_term (state, cache, - one (T)))
549560 elseif p. kind == _TOKEN_NEWLINE
550- read (state, Token)
561+ _ = read (state, Token)
551562 else
552563 break
553564 end
@@ -609,19 +620,19 @@ function _parse_set_prefix(state, cache)
609620 end
610621end
611622
612- # NAME --> [IDENTIFIER OP_COLON ]
623+ # NAME := [IDENTIFIER : ]
613624function _parse_optional_name (state:: LexerState , cache:: Cache )
614625 _skip_newlines (state)
615626 if _next_token_is (state, _TOKEN_IDENTIFIER, 1 ) &&
616627 _next_token_is (state, _TOKEN_COLON, 2 )
617628 name = read (state, Token)
618- read (state, Token) # Skip :
629+ _ = read (state, Token) # Skip :
619630 return name. value
620631 end
621632 return nothing
622633end
623634
624- # OBJECTIVE --> [NAME] [EXPRESSION]
635+ # OBJECTIVE := [NAME] [EXPRESSION]
625636function _parse_objective (state:: LexerState , cache:: Cache )
626637 _ = _parse_optional_name (state, cache)
627638 _skip_newlines (state)
@@ -663,7 +674,7 @@ function _add_bound(cache::Cache, x::MOI.VariableIndex, ::Nothing)
663674 return
664675end
665676
666- # BOUND -->
677+ # BOUND :=
667678# IDENFITIER SET_SUFFIX
668679# | SET_PREFIX IDENTIFIER
669680# | SET_PREFIX IDENTIFIER SET_SUFFIX
@@ -689,22 +700,28 @@ function _parse_bound(state, cache)
689700 return
690701end
691702
703+ function _is_sos_constraint (state)
704+ return _next_token_is (state, _TOKEN_IDENTIFIER, 1 ) &&
705+ _next_token_is (state, _TOKEN_COLON, 2 ) &&
706+ _next_token_is (state, _TOKEN_COLON, 3 )
707+ end
708+
692709# SOS_CONSTRAINT :=
693710# [NAME] S1:: (IDENTIFIER:NUMBER)+ \n
694711# | [NAME] S2:: (IDENTIFIER:NUMBER)+ \n
695712#
696713# The newline character is required.
697714function _parse_sos_constraint (state:: LexerState , cache:: Cache{T} ) where {T}
698- t = read (state, Token) # Si
715+ t = read (state, Token, _TOKEN_IDENTIFIER ) # Si
699716 if ! (t. value == " S1" || t. value == " S2" )
700717 throw (UnexpectedToken (t))
701718 end
702- _expect ( read (state, Token) , _TOKEN_COLON)
703- _expect ( read (state, Token) , _TOKEN_COLON)
719+ _ = read (state, Token, _TOKEN_COLON)
720+ _ = read (state, Token, _TOKEN_COLON)
704721 f, w = MOI. VectorOfVariables (MOI. VariableIndex[]), T[]
705722 while true
706723 push! (f. variables, _parse_variable (state, cache))
707- _expect ( read (state, Token) , _TOKEN_COLON)
724+ _ = read (state, Token, _TOKEN_COLON)
708725 push! (w, _parse_number (state, cache))
709726 if _next_token_is (state, _TOKEN_NEWLINE)
710727 break
@@ -717,12 +734,6 @@ function _parse_sos_constraint(state::LexerState, cache::Cache{T}) where {T}
717734 end
718735end
719736
720- function _is_sos_constraint (state)
721- return _next_token_is (state, _TOKEN_IDENTIFIER, 1 ) &&
722- _next_token_is (state, _TOKEN_COLON, 2 ) &&
723- _next_token_is (state, _TOKEN_COLON, 3 )
724- end
725-
726737function _is_indicator_constraint (state)
727738 return _next_token_is (state, _TOKEN_IDENTIFIER, 1 ) &&
728739 _next_token_is (state, _TOKEN_EQUAL_TO, 2 ) &&
@@ -738,17 +749,16 @@ function _parse_indicator_constraint(
738749 cache:: Cache{T} ,
739750) where {T}
740751 z = _parse_variable (state, cache)
741- _expect (read (state, Token), _TOKEN_EQUAL_TO)
742- t = read (state, Token)
743- _expect (t, _TOKEN_NUMBER)
752+ _ = read (state, Token, _TOKEN_EQUAL_TO)
753+ t = read (state, Token, _TOKEN_NUMBER)
744754 indicator = if t. value == " 0"
745755 MOI. ACTIVATE_ON_ZERO
746756 elseif t. value == " 1"
747757 MOI. ACTIVATE_ON_ONE
748758 else
749759 throw (UnexpectedToken (t))
750760 end
751- _expect ( read (state, Token) , _TOKEN_IMPLIES)
761+ _ = read (state, Token, _TOKEN_IMPLIES)
752762 f = _parse_expression (state, cache)
753763 set = _parse_set_suffix (state, cache)
754764 return MOI. add_constraint (
0 commit comments