@@ -48,39 +48,35 @@ function Base.read!(io::IO, model::Model{T}) where {T}
4848 end
4949 state = _LexerState (io)
5050 cache = _ReadCache (model)
51- keyword = :UNKNOWN
5251 while (token = peek (state, _Token)) != = nothing
53- if token. kind == _TOKEN_KEYWORD
54- _ = read (state, _Token)
55- keyword = Symbol (token. value)
56- elseif token. kind == _TOKEN_NEWLINE
52+ if token. kind == _TOKEN_NEWLINE
5753 _ = read (state, _Token, _TOKEN_NEWLINE)
58- elseif keyword == :MINIMIZE
54+ continue
55+ end
56+ keyword = _parse_keyword (state, cache)
57+ if keyword == :MINIMIZE
5958 MOI. set (cache. model, MOI. ObjectiveSense (), MOI. MIN_SENSE)
6059 _parse_objective (state, cache)
61- keyword = :UNKNOWN
6260 elseif keyword == :MAXIMIZE
6361 MOI. set (cache. model, MOI. ObjectiveSense (), MOI. MAX_SENSE)
6462 _parse_objective (state, cache)
65- keyword = :UNKNOWN
6663 elseif keyword == :CONSTRAINTS
67- _parse_constraint (state, cache)
64+ while _parse_constraint (state, cache)
65+ end
6866 elseif keyword == :BINARY
69- x = _parse_identifier (state, cache)
70- MOI . add_constraint (cache . model, x, MOI . ZeroOne ())
67+ while _parse_binary (state, cache)
68+ end
7169 elseif keyword == :INTEGER
72- x = _parse_identifier (state, cache)
73- MOI . add_constraint (cache . model, x, MOI . Integer ())
70+ while _parse_integer (state, cache)
71+ end
7472 elseif keyword == :BOUNDS
75- _parse_bound_expression (state, cache)
73+ while _parse_bound_expression (state, cache)
74+ end
7675 elseif keyword == :SOS
77- _parse_constraint (state, cache)
76+ while _parse_constraint (state, cache)
77+ end
7878 elseif keyword == :END
79- _throw_parse_error (
80- state,
81- token,
82- " No file contents are allowed after `end`." ,
83- )
79+ break
8480 else
8581 _throw_parse_error (
8682 state,
@@ -89,9 +85,11 @@ function Base.read!(io::IO, model::Model{T}) where {T}
8985 )
9086 end
9187 end
92- # if keyword != :END
93- # TODO (odow): decide if we should throw an error here.
94- # end
88+ _skip_newlines (state)
89+ if (p = peek (state, _Token)) != = nothing
90+ msg = " No file contents are allowed after `end`."
91+ _throw_parse_error (state, p, msg)
92+ end
9593 for x in cache. variable_with_default_bound
9694 MOI. add_constraint (model, x, MOI. GreaterThan (0.0 ))
9795 end
@@ -164,7 +162,6 @@ Hopefully they're all self-explanatory.
164162"""
165163@enum (
166164 _TokenKind,
167- _TOKEN_KEYWORD,
168165 _TOKEN_IDENTIFIER,
169166 _TOKEN_NUMBER,
170167 _TOKEN_ADDITION,
@@ -190,7 +187,6 @@ This dictionary makes `_TokenKind` to a string that is used when printing error
190187messages. The string must complete the sentence "We expected this token to be ".
191188"""
192189const _KIND_TO_MSG = Dict {_TokenKind,String} (
193- _TOKEN_KEYWORD => " a keyword" ,
194190 _TOKEN_IDENTIFIER => " a variable name" ,
195191 _TOKEN_NUMBER => " a number" ,
196192 _TOKEN_ADDITION => " the symbol `+`" ,
@@ -372,59 +368,6 @@ function Base.peek(state::_LexerState, ::Type{_Token}, n::Int = 1)
372368 return nothing
373369 end
374370 push! (state. peek_tokens, token)
375- if token. kind != _TOKEN_IDENTIFIER
376- continue
377- end
378- # Here we have a _TOKEN_IDENTIFIER. But if it is not preceeded by a
379- # _TOKEN_NEWLINE, it cannot be a _TOKEN_KEYWORD.
380- if ! _nothing_or_newline (_prior_token (state))
381- continue
382- end
383- # It might be a _TOKEN_KEYWORD.
384- (kw = _case_insenstive_identifier_to_keyword (token. value))
385- if kw != = nothing
386- # The token matches a single word keyword. All keywords are followed
387- # by a new line, or an EOF.
388- t = _peek_inner (state)
389- if _nothing_or_newline (t)
390- state. peek_tokens[end ] = _Token (_TOKEN_KEYWORD, kw, token. pos)
391- end
392- if t != = nothing
393- push! (state. peek_tokens, t)
394- end
395- continue
396- end
397- # There are two keyword that contain whitespace: `subject to` and
398- # `such that`
399- for (a, b) in (" subject" => " to" , " such" => " that" )
400- if ! _compare_case_insenstive (token, a)
401- continue
402- end
403- # This _might_ be `subject to`, or it might just be a variable
404- # named `subject`, like `obj:\n subject\n`.
405- token_b = _peek_inner (state)
406- if token_b === nothing
407- # The next token is EOF. Nothing to do here.
408- break
409- elseif ! _compare_case_insenstive (token_b, b)
410- # The second token doesn't match. Store `token_b` and break
411- push! (state. peek_tokens, token_b)
412- break
413- end
414- # We have something that matches (a, b), but a TOKEN_KEYWORD needs
415- # to be followed by a new line.
416- token_nl = _peek_inner (state)
417- if _nothing_or_newline (token_nl)
418- state. peek_tokens[end ] =
419- _Token (_TOKEN_KEYWORD, " CONSTRAINTS" , token. pos)
420- else
421- push! (state. peek_tokens, token_b)
422- end
423- if token_nl != = nothing
424- push! (state. peek_tokens, token_nl)
425- end
426- break
427- end
428371 end
429372 return state. peek_tokens[n]
430373end
@@ -523,6 +466,33 @@ function _next_non_newline(state::_LexerState)
523466 end
524467end
525468
469+ function _parse_keyword (state:: _LexerState , cache:: _ReadCache ):: Symbol
470+ token = read (state, _Token, _TOKEN_IDENTIFIER)
471+ kw = _case_insenstive_identifier_to_keyword (token. value)
472+ if kw != = nothing
473+ return Symbol (kw)
474+ end
475+ # Check `subject to`
476+ if _compare_case_insenstive (token, " subject" )
477+ token_b = peek (state, _Token)
478+ if _compare_case_insenstive (token_b, " to" )
479+ _ = read (state, _Token, _TOKEN_IDENTIFIER)
480+ return :CONSTRAINTS
481+ end
482+ elseif _compare_case_insenstive (token, " such" )
483+ token_b = peek (state, _Token)
484+ if _compare_case_insenstive (token_b, " that" )
485+ _ = read (state, _Token, _TOKEN_IDENTIFIER)
486+ return :CONSTRAINTS
487+ end
488+ end
489+ return _throw_parse_error (
490+ state,
491+ token,
492+ " Expected a keyword." ,
493+ )
494+ end
495+
526496# <identifier> :== "string"
527497#
528498# There _are_ rules to what an identifier can be. We handle these when lexing.
@@ -637,10 +607,16 @@ function _parse_quadratic_expression(
637607 while (p = peek (state, _Token)) != = nothing
638608 if p. kind == _TOKEN_ADDITION
639609 p = read (state, _Token)
640- push! (f. quadratic_terms, _parse_quadratic_term (state, cache, prefix))
610+ push! (
611+ f. quadratic_terms,
612+ _parse_quadratic_term (state, cache, prefix),
613+ )
641614 elseif p. kind == _TOKEN_SUBTRACTION
642615 p = read (state, _Token)
643- push! (f. quadratic_terms, _parse_quadratic_term (state, cache, - prefix))
616+ push! (
617+ f. quadratic_terms,
618+ _parse_quadratic_term (state, cache, - prefix),
619+ )
644620 elseif p. kind == _TOKEN_NEWLINE
645621 _ = read (state, _Token)
646622 elseif p. kind == _TOKEN_CLOSE_BRACKET
@@ -782,10 +758,7 @@ function _parse_expression(state::_LexerState, cache::_ReadCache{T}) where {T}
782758 p = read (state, _Token)
783759 _add_to_expression! (f, _parse_term (state, cache, - one (T)))
784760 elseif p. kind == _TOKEN_NEWLINE
785- if _next_token_is (state, _TOKEN_KEYWORD, 2 )
786- break
787- end
788- _ = read (state, _Token)
761+ _ = read (state, _Token, _TOKEN_NEWLINE)
789762 else
790763 break
791764 end
@@ -855,30 +828,40 @@ function _parse_set_prefix(state, cache)
855828 end
856829end
857830
858- # <name> :== [<identifier> :]
831+ # <name> :== <identifier> :
832+
833+ function _is_name (state:: _LexerState )
834+ return _next_token_is (state, _TOKEN_IDENTIFIER, 1 ) &&
835+ _next_token_is (state, _TOKEN_COLON, 2 )
836+ end
837+
859838function _parse_name (state:: _LexerState , cache:: _ReadCache )
860- _skip_newlines (state)
861- if _next_token_is (state, _TOKEN_IDENTIFIER, 1 ) &&
862- _next_token_is (state, _TOKEN_COLON, 2 )
863- name = read (state, _Token)
864- _ = read (state, _Token) # Skip :
865- return name. value
866- end
867- return nothing
839+ name = read (state, _Token, _TOKEN_IDENTIFIER)
840+ _ = read (state, _Token, _TOKEN_COLON)
841+ return name. value
868842end
869843
870844# <objective> :== <name> [<expression>]
871845function _parse_objective (state:: _LexerState , cache:: _ReadCache )
872846 _ = _parse_name (state, cache)
873847 _skip_newlines (state)
874- if _next_token_is (state, _TOKEN_KEYWORD)
875- return # A line like `obj:\nsubject to`
876- end
877848 f = _parse_expression (state, cache)
878849 MOI. set (cache. model, MOI. ObjectiveFunction {typeof(f)} (), f)
879850 return
880851end
881852
853+ function _parse_integer (state:: _LexerState , cache:: _ReadCache )
854+ x = _parse_identifier (state, cache)
855+ MOI. add_constraint (cache. model, x, MOI. Integer ())
856+ return true
857+ end
858+
859+ function _parse_binary (state:: _LexerState , cache:: _ReadCache )
860+ x = _parse_identifier (state, cache)
861+ MOI. add_constraint (cache. model, x, MOI. ZeroOne ())
862+ return true
863+ end
864+
882865function _add_bound (
883866 cache:: _ReadCache ,
884867 x:: MOI.VariableIndex ,
@@ -922,21 +905,21 @@ function _parse_bound_expression(state, cache)
922905 x = _parse_identifier (state, cache)
923906 set = _parse_set_suffix (state, cache)
924907 _add_bound (cache, x, set)
925- return
926- end
927- # `a op x` or `a op x op b`
928- lhs_set = _parse_set_prefix (state, cache)
929- x = _parse_identifier (state, cache )
930- _add_bound (cache, x, lhs_set)
931- if _next_token_is (state, _TOKEN_GREATER_THAN ) ||
932- _next_token_is (state, _TOKEN_LESS_THAN) ||
933- _next_token_is (state, _TOKEN_EQUAL_TO) # `a op x op b`
934- # We don't add MOI.Interval constraints to follow JuMP's convention of
935- # separate bounds.
936- rhs_set = _parse_set_suffix (state, cache )
937- _add_bound (cache, x, rhs_set)
908+ else
909+ # `a op x` or `a op x op b`
910+ lhs_set = _parse_set_prefix (state, cache)
911+ x = _parse_identifier (state, cache)
912+ _add_bound (cache, x, lhs_set )
913+ if _next_token_is (state, _TOKEN_GREATER_THAN) ||
914+ _next_token_is (state, _TOKEN_LESS_THAN ) ||
915+ _next_token_is (state, _TOKEN_EQUAL_TO) # `a op x op b`
916+ # We don't add MOI.Interval constraints to follow JuMP's convention of
917+ # separate bounds.
918+ rhs_set = _parse_set_suffix (state, cache)
919+ _add_bound (cache, x, rhs_set )
920+ end
938921 end
939- return
922+ return true
940923end
941924
942925function _is_sos_constraint (state)
@@ -1022,11 +1005,15 @@ function _parse_constraint_indicator(
10221005end
10231006
10241007# <constraint> :==
1025- # <name> <expression> <set-suffix>
1026- # | <name> <constraint-sos>
1027- # | <name> <constraint-indicator>
1008+ # [ <name>] <expression> <set-suffix>
1009+ # | [ <name>] <constraint-sos>
1010+ # | [ <name>] <constraint-indicator>
10281011function _parse_constraint (state:: _LexerState , cache:: _ReadCache )
1029- name = _parse_name (state, cache)
1012+ name = if _is_name (state)
1013+ _parse_name (state, cache)
1014+ else
1015+ nothing
1016+ end
10301017 # Check if this is an SOS constraint
10311018 c = if _is_sos_constraint (state)
10321019 _parse_constraint_sos (state, cache)
@@ -1040,5 +1027,5 @@ function _parse_constraint(state::_LexerState, cache::_ReadCache)
10401027 if name != = nothing
10411028 MOI. set (cache. model, MOI. ConstraintName (), c, name)
10421029 end
1043- return
1030+ return true
10441031end
0 commit comments