aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/checks/check.mli5
-rw-r--r--lib/checks/compose.ml7
-rw-r--r--lib/checks/default.ml84
-rw-r--r--lib/checks/dune4
-rw-r--r--lib/checks/dup_test.ml66
-rw-r--r--lib/checks/dynamics.ml262
-rw-r--r--lib/checks/dynamics.mli5
-rw-r--r--lib/checks/locations.ml51
-rw-r--r--lib/checks/nested_strings.ml6
-rw-r--r--lib/checks/type_of.ml16
-rw-r--r--lib/checks/write_only.ml12
-rw-r--r--lib/qparser/analyzer.ml36
-rw-r--r--lib/qparser/analyzer.mli8
-rw-r--r--lib/qparser/lexbuf.ml6
-rw-r--r--lib/qparser/lexbuf.mli37
-rw-r--r--lib/qparser/lexer.ml22
-rw-r--r--lib/qparser/lexer.mli2
-rw-r--r--lib/qparser/parser.mly19
-rw-r--r--lib/qparser/tokens.mly1
-rw-r--r--lib/syntax/S.ml11
-rw-r--r--lib/syntax/catalog.ml36
-rw-r--r--lib/syntax/catalog.mli6
-rw-r--r--lib/syntax/dune4
23 files changed, 523 insertions, 183 deletions
diff --git a/lib/checks/check.mli b/lib/checks/check.mli
index 8502753..ebed0df 100644
--- a/lib/checks/check.mli
+++ b/lib/checks/check.mli
@@ -24,6 +24,9 @@ val get : 'a Type.Id.t -> result -> 'a option
module Make (A : sig
val t : Qsp_syntax.Catalog.ex array
end) : sig
- include Qsp_syntax.S.Analyzer with type Location.t = result array
+ include
+ Qsp_syntax.S.Analyzer
+ with type Location.t = result array
+ and type context = result array
end
[@@warning "-67"]
diff --git a/lib/checks/compose.ml b/lib/checks/compose.ml
index 4517755..b29c22e 100644
--- a/lib/checks/compose.ml
+++ b/lib/checks/compose.ml
@@ -41,8 +41,8 @@ module Lazier (E : S.Expression) :
end
(** Build an expression module with the result from another expression. The
- signature of the fuctions is a bit different, as they all receive the
- result from the previous evaluated element in argument. *)
+ signature of the fuctions is a bit different, as they all receive the result
+ from the previous evaluated element in argument. *)
module Expression (E : S.Expression) = struct
module type SIG = sig
type t
@@ -125,3 +125,6 @@ module Expression (E : S.Expression) = struct
(t', M.boperator pos op (v' t1, expr1) (v' t2, expr2) (v' t'))
end
end
+
+module TypeBuilder = Expression (Get_type)
+(** Builder adding the type for the expression *)
diff --git a/lib/checks/default.ml b/lib/checks/default.ml
index a2b53f6..0c4d761 100644
--- a/lib/checks/default.ml
+++ b/lib/checks/default.ml
@@ -1,25 +1,23 @@
-(** Default implementation which does nothing.
+(** Default implementation which does nothing.
-This module is expected to be used when you only need to implement an analyze
-over a limited part of the whole syntax. *)
+ This module is expected to be used when you only need to implement an
+ analyze over a limited part of the whole syntax. *)
+open StdLabels
module S = Qsp_syntax.S
module T = Qsp_syntax.T
module Report = Qsp_syntax.Report
-module type T = sig
+module Expression (T' : sig
type t
val default : t
-end
-
-module Expression (T' : T) = struct
- (**
- Describe a variable, using the name in capitalized text, and an optionnal
+end) =
+struct
+ (** Describe a variable, using the name in capitalized text, and an optionnal
index.
- If missing, the index should be considered as [0].
- *)
+ If missing, the index should be considered as [0]. *)
type t' = T'.t
@@ -43,3 +41,67 @@ module Expression (T' : T) = struct
let boperator : S.pos -> T.boperator -> T'.t -> T'.t -> T'.t =
fun _ _ _ _ -> T'.default
end
+
+module Instruction (Expression : sig
+ type t'
+end) (T : sig
+ type t
+
+ val default : t
+ val fold : t Seq.t -> t
+end) =
+struct
+ let call : S.pos -> Qsp_syntax.T.keywords -> Expression.t' list -> T.t =
+ fun _ _ _ -> T.default
+
+ let location : S.pos -> string -> T.t =
+ fun position name ->
+ ignore position;
+ ignore name;
+ T.default
+
+ let comment : S.pos -> T.t =
+ fun position ->
+ ignore position;
+ T.default
+
+ let expression : Expression.t' -> T.t =
+ fun expr ->
+ ignore expr;
+ T.default
+
+ let map_clause : (Expression.t', T.t) S.clause -> T.t Seq.t =
+ fun (_, _, els) -> List.to_seq els
+
+ let if_ :
+ S.pos ->
+ (Expression.t', T.t) S.clause ->
+ elifs:(Expression.t', T.t) S.clause list ->
+ else_:(S.pos * T.t list) option ->
+ T.t =
+ fun pos clause ~elifs ~else_ ->
+ ignore pos;
+
+ let seq = List.to_seq (clause :: elifs) |> Seq.flat_map map_clause in
+
+ let seq =
+ match else_ with
+ | None -> seq
+ | Some (_, ts) -> Seq.append seq (List.to_seq ts)
+ in
+ T.fold seq
+
+ let act : S.pos -> label:Expression.t' -> T.t list -> T.t =
+ fun pos ~label instructions ->
+ ignore pos;
+ ignore label;
+ T.fold (List.to_seq instructions)
+
+ let assign :
+ S.pos ->
+ (S.pos, Expression.t') S.variable ->
+ Qsp_syntax.T.assignation_operator ->
+ Expression.t' ->
+ T.t =
+ fun _ _ _ _ -> T.default
+end
diff --git a/lib/checks/dune b/lib/checks/dune
index d7db2f3..3bd22e0 100644
--- a/lib/checks/dune
+++ b/lib/checks/dune
@@ -5,5 +5,7 @@
)
(preprocess (pps
- ppx_deriving.show ppx_deriving.enum
+ ppx_deriving.show
+ ppx_deriving.enum
+ ppx_deriving.ord
ppx_deriving.eq )))
diff --git a/lib/checks/dup_test.ml b/lib/checks/dup_test.ml
index 9ffe7c5..c29eca9 100644
--- a/lib/checks/dup_test.ml
+++ b/lib/checks/dup_test.ml
@@ -1,9 +1,7 @@
(** This module check for duplicated tests in the source.contents
-
- This in intended to identify the copy/paste errors, where one location
- check for the same arguments twice or more.
- *)
+ This in intended to identify the copy/paste errors, where one location check
+ for the same arguments twice or more. *)
open StdLabels
module S = Qsp_syntax.S
@@ -23,8 +21,8 @@ let finalize () = []
module Expression = Tree.Expression
-(** Build a Hashtbl over the expression, ignoring the location in the
- expression *)
+(** Build a Hashtbl over the expression, ignoring the location in the expression
+*)
module Table = Hashtbl.Make (struct
type t = Expression.t'
@@ -37,23 +35,33 @@ module Instruction = struct
predicates : (Expression.t' * S.pos) list;
duplicates : (Expression.t' * S.pos list) list;
}
- (** Keep the list of all the predicates and their position in a block, and
- the list of all the identified duplicated values. *)
+ (** Keep the list of all the predicates and their position in a block, and the
+ list of all the identified duplicated values. *)
type t = state
type t' = state
- let v : t -> t' = fun t -> t
let default = { predicates = []; duplicates = [] }
- (** Label for a loop *)
- let location : S.pos -> string -> t = fun _ _ -> default
+ include
+ Default.Instruction
+ (Expression)
+ (struct
+ type nonrec t = t
- (** Comment *)
- let comment : S.pos -> t = fun _ -> default
+ let default = default
- (** Raw expression *)
- let expression : Expression.t' -> t = fun _ -> default
+ let fold sequence =
+ Seq.fold_left
+ (fun state ex ->
+ {
+ predicates = [];
+ duplicates = List.rev_append ex.duplicates state.duplicates;
+ })
+ default sequence
+ end)
+
+ let v : t -> t' = fun t -> t
let check_duplicates :
(Expression.t' * S.pos) list -> (Expression.t' * S.pos list) list =
@@ -74,10 +82,9 @@ module Instruction = struct
| other -> Some (hd, other)))
|> List.of_seq
- (** Evaluate a clause.
- This function does two things :
- - report all errors from the bottom to top
- - add the clause in the actual level *)
+ (** Evaluate a clause. This function does two things :
+ - report all errors from the bottom to top
+ - add the clause in the actual level *)
let predicate_of_clause : ?pos:S.pos -> t -> (Expression.t', t) S.clause -> t
=
fun ?pos t (pos2, predicate, blocks) ->
@@ -118,27 +125,6 @@ module Instruction = struct
state with
duplicates = check_duplicates state.predicates @ state.duplicates;
}
-
- let act : S.pos -> label:Expression.t' -> t list -> t =
- fun _pos ~label expressions ->
- ignore label;
- (* Collect all the elements reported from bottom to up. *)
- List.fold_left ~init:default expressions ~f:(fun state ex ->
- {
- predicates = [];
- duplicates = List.rev_append ex.duplicates state.duplicates;
- })
-
- let assign :
- S.pos ->
- (S.pos, Expression.t') S.variable ->
- T.assignation_operator ->
- Expression.t' ->
- t =
- fun _ _ _ _ -> default
-
- let call : S.pos -> T.keywords -> Expression.t' list -> t =
- fun _ _ _ -> default
end
module Location = struct
diff --git a/lib/checks/dynamics.ml b/lib/checks/dynamics.ml
new file mode 100644
index 0000000..0c16ff8
--- /dev/null
+++ b/lib/checks/dynamics.ml
@@ -0,0 +1,262 @@
+open StdLabels
+module S = Qsp_syntax.S
+module T = Qsp_syntax.T
+module Report = Qsp_syntax.Report
+
+let identifier = "dynamics"
+let description = "Report all dynamics string in the module"
+let is_global = true
+let active = ref false
+
+type text = { content : string; position : S.pos } [@@deriving eq, ord]
+
+module StringSet = Set.Make (struct
+ type t = text [@@deriving ord]
+end)
+
+type context = StringSet.t ref
+
+let initialize () = ref StringSet.empty
+
+module Expression = struct
+ (** Elements wich can be given to dynamic.
+
+ For Text, I do not evaluate text containing expression. This need to be a
+ plain text.
+
+ In the case of variable, indexes will probably not work if they include
+ function or complex expression *)
+ type t = None | Text of text | Variable of (unit, t) S.variable
+ [@@deriving eq, ord]
+
+ (** Remove all the locations inside a variable in order to be able to compare
+ two of them at differents locations *)
+ let rec anonymize_variable : (unit, t) S.variable -> (unit, t) S.variable =
+ fun ({ index; _ } as variable) ->
+ let index =
+ Option.map
+ (function
+ | None -> None
+ | Text { content; _ } ->
+ let position = (Lexing.dummy_pos, Lexing.dummy_pos) in
+ Text { content; position }
+ | Variable var -> Variable (anonymize_variable var))
+ index
+ in
+ { variable with index }
+
+ include Default.Expression (struct
+ type nonrec t = t
+
+ let default = None
+ end)
+
+ let v : t -> t' = Fun.id
+
+ (** Only keep the raw strings *)
+ let literal : S.pos -> t T.literal list -> t =
+ fun position content ->
+ ignore position;
+ match content with
+ | [ T.Text content ] -> Text { content; position }
+ | _ -> (
+ (* Here I analyse if the expression is a string or
+ numeric. In case of numeric, it is possible to replace it with a
+ default value *)
+ let buffer = Buffer.create 16 in
+ let res =
+ List.fold_left ~init:`Ok content ~f:(fun state literal ->
+ match (state, literal) with
+ | `None, _ -> `None
+ | `Ok, T.Expression None -> `None
+ | `Ok, T.Expression (Text content) ->
+ Buffer.add_string buffer content.content;
+ `Ok
+ | `Ok, T.Text content ->
+ Buffer.add_string buffer content;
+ `Ok
+ | `Ok, T.Expression (Variable { name; _ }) ->
+ let res =
+ if Char.equal '$' name.[0] then `None
+ else (
+ Buffer.add_char buffer '0';
+ `Ok)
+ in
+ res)
+ in
+ match res with
+ | `Ok -> Text { content = Buffer.contents buffer; position }
+ | _ -> None)
+
+ (** Consider the integer as text. This is easier for evaluating the indices in
+ the arrays (it use the same code as text indices), and will report bad use
+ of dynamics. *)
+ let integer : S.pos -> string -> t =
+ fun position content -> Text { content; position }
+
+ (** If the identifier uses any unmanaged expression in the indices, ignore it.
+ *)
+ let ident : (S.pos, t) S.variable -> t =
+ fun ({ index; _ } as ident) ->
+ let is_valid =
+ Option.fold ~none:true index ~some:(fun opt ->
+ match opt with None -> false | _ -> true)
+ in
+ match is_valid with
+ | false -> None
+ | true -> Variable (anonymize_variable { ident with pos = () })
+end
+
+module Instruction = struct
+ (** This map holds the values for each variable seen in the code *)
+ module StringMap = struct
+ include Hashtbl.Make (struct
+ type t = (unit, Expression.t) S.variable [@@deriving eq]
+
+ let hash = Hashtbl.hash
+ end)
+
+ (** Recursive search in the table *)
+ let rec_find :
+ Expression.t' t -> (unit, Expression.t) S.variable -> StringSet.t =
+ fun table key ->
+ let rec _f init key =
+ let values = find_all table key in
+ List.fold_left values ~init ~f:(fun acc value ->
+ match value with
+ | Expression.None -> acc
+ | Expression.Text text -> StringSet.add text acc
+ | Expression.Variable variable -> _f acc variable)
+ in
+ _f StringSet.empty key
+ end
+
+ module VariableSet = Set.Make (struct
+ type t = (unit, Expression.t) S.variable [@@deriving ord]
+ end)
+
+ type context = {
+ catalog : Expression.t' StringMap.t;
+ texts : StringSet.t;
+ blacklist : VariableSet.t;
+ variable_called : VariableSet.t;
+ }
+ (** Keep the content of each string in order to parse it later *)
+
+ (** This module do two things : keep a track of the raw strings in the
+ location, and identify the calls to the function dynamic.
+
+ The dynamic parameter are reported as is, and are evaluated only at the
+ end of the module. *)
+
+ type t = context -> context
+ type t' = t
+
+ let v = Fun.id
+
+ include
+ Default.Instruction
+ (Expression)
+ (struct
+ type nonrec t = t
+
+ let fold : t Seq.t -> t =
+ fun seq init_context ->
+ let result =
+ Seq.fold_left
+ (fun context (instr : t) -> instr context)
+ init_context seq
+ in
+ result
+
+ let default context = context
+ end)
+
+ (** Keep the track of dynamic instructions *)
+ let call : S.pos -> T.keywords -> Expression.t' list -> t =
+ fun position keyword arg context ->
+ ignore position;
+ ignore arg;
+ match keyword with
+ | T.Dynamic -> (
+ match arg with
+ | [ Expression.Text text ] ->
+ let texts = StringSet.add text context.texts in
+
+ { context with texts }
+ | [ Expression.Variable var ] ->
+ let variable_called = VariableSet.add var context.variable_called in
+ { context with variable_called }
+ | _ -> context)
+ | _ -> context
+
+ let assign :
+ S.pos ->
+ (S.pos, Expression.t') S.variable ->
+ T.assignation_operator ->
+ Expression.t' ->
+ t =
+ fun pos variable op expression context ->
+ ignore pos;
+ let variable' = Expression.anonymize_variable { variable with pos = () } in
+ let is_blacklisted = VariableSet.mem variable' context.blacklist in
+ let is_string = variable.name.[0] = '$' in
+ match (op, expression, is_blacklisted, is_string) with
+ | T.Eq', Expression.Text content, false, true
+ when not (String.equal content.content "") ->
+ StringMap.add context.catalog variable' expression;
+ context
+ | T.Eq', Expression.Variable _, false, _ ->
+ StringMap.add context.catalog variable' expression;
+ context
+ | _ ->
+ (* If the assignation is not direct, we **remove** all the bindings
+ from the catalog. *)
+ StringMap.find_all context.catalog variable'
+ |> List.iter ~f:(fun _ -> StringMap.remove context.catalog variable');
+
+ (* We also black list this variable and prevent further additions *)
+ let blacklist = VariableSet.add variable' context.blacklist in
+ { context with blacklist }
+end
+
+module Location = struct
+ type t = unit
+ type instruction = Instruction.t'
+
+ let location : context -> S.pos -> instruction list -> t =
+ fun context pos instr ->
+ ignore pos;
+ let catalog = Instruction.StringMap.create 16 in
+ let init =
+ Instruction.
+ {
+ catalog;
+ texts = !context;
+ blacklist = VariableSet.empty;
+ variable_called = VariableSet.empty;
+ }
+ in
+ let res = List.fold_left instr ~init ~f:(fun acc instr -> instr acc) in
+
+ (* Now, for each dynamics calling a variable, looks in the catalog if we
+ can find the associated string *)
+ let texts =
+ Instruction.VariableSet.fold
+ (fun variable acc ->
+ let indirects = Instruction.StringMap.rec_find res.catalog variable in
+
+ StringSet.union acc indirects)
+ res.variable_called res.texts
+ in
+ context := texts
+
+ let v : t -> Report.t list = fun _ -> []
+end
+
+let finalize context =
+ ignore context;
+ []
+
+let dynamics_string : context -> text Seq.t =
+ fun context -> StringSet.to_seq !context
diff --git a/lib/checks/dynamics.mli b/lib/checks/dynamics.mli
new file mode 100644
index 0000000..b4cdc96
--- /dev/null
+++ b/lib/checks/dynamics.mli
@@ -0,0 +1,5 @@
+include Qsp_syntax.S.Analyzer
+
+type text = { content : string; position : Qsp_syntax.S.pos }
+
+val dynamics_string : context -> text Seq.t
diff --git a/lib/checks/locations.ml b/lib/checks/locations.ml
index 8ee6ffa..8e5f500 100644
--- a/lib/checks/locations.ml
+++ b/lib/checks/locations.ml
@@ -74,7 +74,7 @@ let registerLocation : string -> t -> t =
{ calls; locations }
(** The module Expression is pretty simple, we are only interrested by the
- strings ( because only the first argument of [gt …] is read ).
+ strings ( because only the first argument of [gt …] is read ).
If the string is too much complex, we just ignore it. *)
module Expression = struct
@@ -99,6 +99,18 @@ module Instruction = struct
let v : t -> t' = Fun.id
+ include
+ Default.Instruction
+ (Expression)
+ (struct
+ type nonrec t = t
+
+ let default = Fun.id
+
+ let fold : t Seq.t -> t =
+ fun sequence t -> Seq.fold_left (fun acc t -> t acc) t sequence
+ end)
+
(** Keep a track of every gt or gs instruction *)
let call : S.pos -> T.keywords -> Expression.t' list -> t =
fun pos fn args t ->
@@ -106,43 +118,6 @@ module Instruction = struct
| T.Goto, Some dest :: _ -> registerCall pos dest t
| T.Gosub, Some dest :: _ -> registerCall pos dest t
| _ -> t
-
- let location : S.pos -> string -> t = fun _ _ -> Fun.id
- let comment : S.pos -> t = fun _ -> Fun.id
- let expression : Expression.t' -> t = fun _ -> Fun.id
-
- let if_ :
- S.pos ->
- (Expression.t', t) S.clause ->
- elifs:(Expression.t', t) S.clause list ->
- else_:(S.pos * t list) option ->
- t =
- fun _ clause ~elifs ~else_ t ->
- let traverse_clause t clause =
- let _, _, block = clause in
- List.fold_left block ~init:t ~f:(fun t instruction -> instruction t)
- in
-
- let t = traverse_clause t clause in
- let t = List.fold_left ~init:t ~f:traverse_clause elifs in
- match else_ with
- | None -> t
- | Some (_, instructions) ->
- List.fold_left instructions ~init:t ~f:(fun t instruction ->
- instruction t)
-
- let act : S.pos -> label:Expression.t' -> t list -> t =
- fun _ ~label instructions t ->
- ignore label;
- List.fold_left instructions ~init:t ~f:(fun t instruction -> instruction t)
-
- let assign :
- S.pos ->
- (S.pos, Expression.t') S.variable ->
- T.assignation_operator ->
- Expression.t' ->
- t =
- fun _ _ _ _ -> Fun.id
end
module Location = struct
diff --git a/lib/checks/nested_strings.ml b/lib/checks/nested_strings.ml
index e4ffb68..51c5258 100644
--- a/lib/checks/nested_strings.ml
+++ b/lib/checks/nested_strings.ml
@@ -13,16 +13,14 @@ type context = unit
let initialize = Fun.id
let finalize () = []
-module TypeBuilder = Compose.Expression (Get_type)
-
-module Expression = TypeBuilder.Make (struct
+module Expression = Compose.TypeBuilder.Make (struct
type t = Report.t list
type t' = Report.t list
let v : Get_type.t Lazy.t * t -> t' = snd
(** Identify the expressions reprented as string. That’s here that the report
- are added.
+ are added.
All the rest of the module only push thoses warning to the top level. *)
let literal :
diff --git a/lib/checks/type_of.ml b/lib/checks/type_of.ml
index 70ae324..42f9a2d 100644
--- a/lib/checks/type_of.ml
+++ b/lib/checks/type_of.ml
@@ -20,8 +20,8 @@ module Helper = struct
type nonrec t = Get_type.t -> Get_type.t
(** Dynamic type is a type unknown during the code.
- For example, the equality operator accept either Integer or String, but
- we expect that both sides of the equality uses the same type.*)
+ For example, the equality operator accept either Integer or String, but
+ we expect that both sides of the equality uses the same type.*)
(** Build a new dynamic type *)
let t : unit -> t =
@@ -35,11 +35,11 @@ module Helper = struct
| Some t -> t
end
- (** Declare an argument for a function.
+ (** Declare an argument for a function.
- - Either we already know the type and we just have to compare.
- - Either the type shall constrained by another one
- - Or we have a variable number of arguments. *)
+ - Either we already know the type and we just have to compare.
+ - Either the type shall constrained by another one
+ - Or we have a variable number of arguments. *)
type argument =
| Fixed of Get_type.type_of
| Dynamic of DynType.t
@@ -143,8 +143,6 @@ module Helper = struct
msg :: report
end
-module TypeBuilder = Compose.Expression (Get_type)
-
type t' = { result : Get_type.t Lazy.t; pos : S.pos }
let arg_of_repr : Get_type.t Lazy.t -> S.pos -> Helper.argument_repr =
@@ -360,7 +358,7 @@ module TypedExpression = struct
({ pos }, report)
end
-module Expression = TypeBuilder.Make (TypedExpression)
+module Expression = Compose.TypeBuilder.Make (TypedExpression)
module Instruction = struct
type t = Report.t list
diff --git a/lib/checks/write_only.ml b/lib/checks/write_only.ml
index 8363703..e2c3d7e 100644
--- a/lib/checks/write_only.ml
+++ b/lib/checks/write_only.ml
@@ -16,16 +16,8 @@ let active = ref false
let is_global = true
-module Key = struct
- type t = string
-
- let equal = String.equal
- let hash = Hashtbl.hash
- let compare = String.compare
-end
-
-module StringMap = Hashtbl.Make (Key)
-module Set = Set.Make (Key)
+module StringMap = Hashtbl.Make (String)
+module Set = Set.Make (String)
type data = { write : bool; read : bool; position : S.pos list }
type context = (string * data) StringMap.t
diff --git a/lib/qparser/analyzer.ml b/lib/qparser/analyzer.ml
index ca2b54f..b4eeba0 100644
--- a/lib/qparser/analyzer.ml
+++ b/lib/qparser/analyzer.ml
@@ -1,15 +1,23 @@
type 'a result = { content : 'a; report : Qsp_syntax.Report.t list }
+type lexer = Location | Dynamic
-(**
- Run the QSP parser and apply the analyzer over it.
+let get_lexer :
+ Lexbuf.t ->
+ lexer ->
+ unit ->
+ Tokens.token * Lexing.position * Lexing.position =
+ fun l -> function
+ | Location -> Lexbuf.tokenize Lexer.main l
+ | Dynamic -> Lexbuf.tokenize Lexer.dynamics l
+
+(** Run the QSP parser and apply the analyzer over it.
- See [syntax/S]
- *)
-let rec parse :
- type a context.
+ See [syntax/S] *)
+let rec parse : type a context.
(module Qsp_syntax.S.Analyzer
with type Location.t = a
and type context = context) ->
+ lexer ->
Lexbuf.t ->
context ->
(a result, Qsp_syntax.Report.t) Result.t =
@@ -19,10 +27,18 @@ let rec parse :
let module Parser = Parser.Make (S) in
let module IncrementalParser =
Interpreter.Interpreter (Parser.MenhirInterpreter) in
- fun l context ->
- let lexer = Lexbuf.tokenize Lexer.main l in
+ fun lexer_type l context ->
+ let get_parser :
+ lexer ->
+ Lexing.position ->
+ (context -> a) Parser.MenhirInterpreter.checkpoint = function
+ | Location -> Parser.Incremental.main
+ | Dynamic -> Parser.Incremental.dynamics
+ in
+
+ let lexer = get_lexer l lexer_type in
- let init = Parser.Incremental.main (fst (Lexbuf.positions l)) in
+ let init = (get_parser lexer_type) (fst (Lexbuf.positions l)) in
(* Firslty, check if we are able to read the whole syntax from the source *)
let evaluation =
@@ -59,7 +75,7 @@ let rec parse :
application attempt to start from a clean state in the next
location, but may fail to detect the correct position. If so, we
just start again until we hook the next location *)
- parse (module S) l context
+ parse (module S) lexer_type l context
| Error e, _ ->
let message =
match e.IncrementalParser.code with
diff --git a/lib/qparser/analyzer.mli b/lib/qparser/analyzer.mli
index 949db16..817be6c 100644
--- a/lib/qparser/analyzer.mli
+++ b/lib/qparser/analyzer.mli
@@ -1,13 +1,15 @@
type 'a result = { content : 'a; report : Qsp_syntax.Report.t list }
+type lexer = Location | Dynamic
val parse :
(module Qsp_syntax.S.Analyzer
with type Location.t = 'a
and type context = 'context) ->
+ lexer ->
Lexbuf.t ->
'context ->
('a result, Qsp_syntax.Report.t) Result.t
-(** Read the source and build a analyzis over it.
+(** Read the source and build a analyzis over it.
-This method make the link between the source file and how to read it
-(encoding…) and the AST we want to build. *)
+ This method make the link between the source file and how to read it
+ (encoding…) and the AST we want to build. *)
diff --git a/lib/qparser/lexbuf.ml b/lib/qparser/lexbuf.ml
index afc3bac..dbed622 100644
--- a/lib/qparser/lexbuf.ml
+++ b/lib/qparser/lexbuf.ml
@@ -62,8 +62,10 @@ let positions : t -> Lexing.position * Lexing.position =
let content : t -> string = fun t -> Sedlexing.Utf8.lexeme t.buffer
-let from_lexbuf : ?reset_line:bool -> Sedlexing.lexbuf -> t =
- fun ?(reset_line = true) t ->
+let from_lexbuf :
+ ?position:Lexing.position -> ?reset_line:bool -> Sedlexing.lexbuf -> t =
+ fun ?position ?(reset_line = true) t ->
+ Option.iter (Sedlexing.set_position t) position;
{
buffer = t;
start_p = None;
diff --git a/lib/qparser/lexbuf.mli b/lib/qparser/lexbuf.mli
index 4283db1..d656642 100644
--- a/lib/qparser/lexbuf.mli
+++ b/lib/qparser/lexbuf.mli
@@ -3,8 +3,11 @@
type t
(** The state of the buffer *)
-val from_lexbuf : ?reset_line:bool -> Sedlexing.lexbuf -> t
-(** Create a new buffer *)
+val from_lexbuf :
+ ?position:Lexing.position -> ?reset_line:bool -> Sedlexing.lexbuf -> t
+(** Create a new buffer.
+
+ If a position is given, start from this position in the file. *)
val start : t -> unit
(** Intialize a new run. *)
@@ -13,11 +16,10 @@ val buffer : t -> Sedlexing.lexbuf
(** Extract the sedlex buffer. Required in each rule. *)
val positions : t -> Lexing.position * Lexing.position
-(** Extract the starting and ending position for the matched token.
+(** Extract the starting and ending position for the matched token.
- This function is used outside of the parser, in order to get the position
- of the latest token in the case of an error.
- *)
+ This function is used outside of the parser, in order to get the position of
+ the latest token in the case of an error. *)
val content : t -> string
(** Extract the token matched by the rule *)
@@ -33,15 +35,14 @@ val tokenize : (t -> 'a) -> t -> unit -> 'a * Lexing.position * Lexing.position
val rollback : t -> unit
(** Rollback the latest token matched *)
-(** {1 State in expressions}
+(** {1 State in expressions}
- The comment system is terrible. The same symbol can be used for :
- - starting a comment
- - inequality operation
+ The comment system is terrible. The same symbol can be used for :
+ - starting a comment
+ - inequality operation
- In order to manage this, I try to identify the context in a very basic way,
- using a stack for determining the token to send.
-*)
+ In order to manage this, I try to identify the context in a very basic way,
+ using a stack for determining the token to send. *)
type lexer = t -> Tokens.token
and buffer_builder = ?nested:bool -> Buffer.t -> t -> Tokens.token
@@ -64,14 +65,14 @@ type state =
| String of stringWraper (** String enclosed by [''] *)
| MString of int (** String enclosed by [{}]*)
| EndString of stringWraper
- (** State raised just before closing the string.
- The buffer is rollbacked and the position is the closing symbol. *)
+ (** State raised just before closing the string. The buffer is rollbacked
+ and the position is the closing symbol. *)
| Expression (** Expression where [!] is an operator *)
val pp_state : Format.formatter -> state -> unit
val state : t -> state option
-(** Get the current state for the lexer.
+(** Get the current state for the lexer.
@return [None] when in the default state *)
@@ -84,8 +85,8 @@ val leave_state : t -> unit
val overlay : t -> lexer -> lexer
val start_recovery : t -> unit
-(** Set the lexer in recovery mode, the lexer raise this mode after an error,
- in order to ignore the further errors until a new location *)
+(** Set the lexer in recovery mode, the lexer raise this mode after an error, in
+ order to ignore the further errors until a new location *)
val is_recovery : t -> bool
(** Check if the lexer is in recovery mode *)
diff --git a/lib/qparser/lexer.ml b/lib/qparser/lexer.ml
index 814c97f..470cdc7 100644
--- a/lib/qparser/lexer.ml
+++ b/lib/qparser/lexer.ml
@@ -1,6 +1,4 @@
-(**
- Lexer using sedlex
- *)
+(** Lexer using sedlex *)
open Tokens
open StdLabels
@@ -12,7 +10,8 @@ exception EOF
(* Extract the location name from the pattern *)
let location_name = Str.regexp {|# *\(.*\)|}
-(** Remove all the expression state when we are leaving the expression itself. *)
+(** Remove all the expression state when we are leaving the expression itself.
+*)
let rec leave_expression buffer =
match Lexbuf.state buffer with
| Some Lexbuf.Expression ->
@@ -21,7 +20,7 @@ let rec leave_expression buffer =
| _ -> ()
(** Try to read the identifier and check if this is a function, a keyword, or
- just a variable.
+ just a variable.
See the tests [Syntax.Operator2] and [Syntax.Call Nl] for two cases. *)
let build_ident buffer =
@@ -124,8 +123,7 @@ let rec read_long_string : ?nested:bool -> int -> Buffer.t -> Lexbuf.t -> token
rollbacked, leaving the state in [Lexbuf.EndString _].
The next call to [main] will call the associated function, effectively
- leaving the string mode in the parser.
- *)
+ leaving the string mode in the parser. *)
let rec read_quoted_string : Lexbuf.stringWraper -> Lexbuf.buffer_builder =
fun f ?(nested = false) buf buffer ->
let lexbuf = Lexbuf.buffer buffer in
@@ -153,11 +151,9 @@ let rec read_quoted_string : Lexbuf.stringWraper -> Lexbuf.buffer_builder =
(f.wrap ~nested (read_quoted_string f)) buf buffer
| _ -> raise Not_found
-(** Track the kind of nested string inside a multiline string inside a
- comment.
+(** Track the kind of nested string inside a multiline string inside a comment.
- Some constructions are not allowed in this specific case (see later)
-*)
+ Some constructions are not allowed in this specific case (see later) *)
type commentedString = None | Quote | DQuote
let rec skip_comment buffer =
@@ -333,6 +329,10 @@ let main buffer =
in
parser buffer
+(** Function used inside the dynamics expressions. Here, we give the EOF token
+ to the parser. *)
+let dynamics buffer = try main buffer with EOF -> Tokens.EOF
+
let rec discard buffer =
let () = Lexbuf.start_recovery buffer in
let lexbuf = Lexbuf.buffer buffer in
diff --git a/lib/qparser/lexer.mli b/lib/qparser/lexer.mli
index 854bb1e..70902e6 100644
--- a/lib/qparser/lexer.mli
+++ b/lib/qparser/lexer.mli
@@ -18,3 +18,5 @@ val discard : Lexbuf.t -> unit
val main : Lexbuf.t -> Tokens.token
(** Main entry point. This function is called after each token returned *)
+
+val dynamics : Lexbuf.t -> Tokens.token
diff --git a/lib/qparser/parser.mly b/lib/qparser/parser.mly
index d075e3e..469cf79 100644
--- a/lib/qparser/parser.mly
+++ b/lib/qparser/parser.mly
@@ -19,11 +19,13 @@
%parameter<Analyzer: Qsp_syntax.S.Analyzer>
%start <(Analyzer.context -> Analyzer.Location.t)>main
+%start<(Analyzer.context -> Analyzer.Location.t)>dynamics
+
%on_error_reduce expression instruction unary_operator assignation_operator
%%
-main:
+main:
| before_location*
start_location
EOL+
@@ -34,6 +36,21 @@ main:
fun context -> Analyzer.Location.location context $loc instructions
}
+dynamics:
+ | EOL*
+ instructions = line_statement+
+ EOF
+ {
+ let instructions = List.map instructions ~f:(Analyzer.Instruction.v) in
+ fun context -> Analyzer.Location.location context $loc instructions
+ }
+ | EOL*
+ b = inlined_block(EOF)
+ {
+ let instruction = (Analyzer.Instruction.v b) in
+ fun context -> Analyzer.Location.location context $loc [instruction]
+ }
+
before_location:
| EOL {}
| COMMENT EOL { }
diff --git a/lib/qparser/tokens.mly b/lib/qparser/tokens.mly
index 0ba5486..42856ef 100644
--- a/lib/qparser/tokens.mly
+++ b/lib/qparser/tokens.mly
@@ -20,6 +20,7 @@
%token AND OR
%token EOL
+%token EOF
%token <string>IDENT
%token <string>LITERAL
diff --git a/lib/syntax/S.ml b/lib/syntax/S.ml
index 918d8e6..a3c74ca 100644
--- a/lib/syntax/S.ml
+++ b/lib/syntax/S.ml
@@ -8,11 +8,20 @@
(** {1 Generic types used in the module} *)
-type pos = Lexing.position * Lexing.position
+type position = Lexing.position = {
+ pos_fname : string;
+ pos_lnum : int;
+ pos_bol : int;
+ pos_cnum : int;
+}
+[@@deriving eq, ord]
+
+type pos = position * position [@@deriving eq, ord]
(** The type pos is used to track the starting and ending position for the given
location. *)
type ('a, 'b) variable = { pos : 'a; name : string; index : 'b option }
+[@@deriving eq, ord]
(** Describe a variable, using the name in capitalized text, and an optionnal
index.
diff --git a/lib/syntax/catalog.ml b/lib/syntax/catalog.ml
index b516976..5ad0bbd 100644
--- a/lib/syntax/catalog.ml
+++ b/lib/syntax/catalog.ml
@@ -18,31 +18,31 @@ type ex =
-> ex (** Type of check to apply *)
let build :
+ ?location_id:'a Type.Id.t ->
+ ?context_id:'b Type.Id.t ->
(module S.Analyzer
with type Expression.t = _
and type Expression.t' = _
and type Instruction.t = _
and type Instruction.t' = _
and type Location.t = 'a
- and type context = _) ->
- 'a Type.Id.t * ex =
- fun module_ ->
+ and type context = 'b) ->
+ ex =
+ fun ?location_id ?context_id module_ ->
let expr_witness = Type.Id.make ()
and expr' = Type.Id.make ()
and instr_witness = Type.Id.make ()
and instr' = Type.Id.make ()
- and location_witness = Type.Id.make ()
- and context = Type.Id.make () in
- let t =
- E
- {
- module_;
- expr_witness;
- expr';
- instr_witness;
- instr';
- location_witness;
- context;
- }
- in
- (location_witness, t)
+ and location_witness =
+ match location_id with Some v -> v | None -> Type.Id.make ()
+ and context = match context_id with Some v -> v | None -> Type.Id.make () in
+ E
+ {
+ module_;
+ expr_witness;
+ expr';
+ instr_witness;
+ instr';
+ location_witness;
+ context;
+ }
diff --git a/lib/syntax/catalog.mli b/lib/syntax/catalog.mli
index a256c17..a386d4a 100644
--- a/lib/syntax/catalog.mli
+++ b/lib/syntax/catalog.mli
@@ -18,13 +18,15 @@ type ex =
-> ex (** Type of check to apply *)
val build :
+ ?location_id:'a Type.Id.t ->
+ ?context_id:'b Type.Id.t ->
(module S.Analyzer
with type Expression.t = _
and type Expression.t' = _
and type Instruction.t = _
and type Instruction.t' = _
and type Location.t = 'a
- and type context = _) ->
- 'a Type.Id.t * ex
+ and type context = 'b) ->
+ ex
(** Build a new check from a module following S.Analyzer signature. ypeid Return
the result type which hold the final result value, and checker itself. *)
diff --git a/lib/syntax/dune b/lib/syntax/dune
index 666273f..9832809 100644
--- a/lib/syntax/dune
+++ b/lib/syntax/dune
@@ -2,5 +2,7 @@
(name qsp_syntax)
(preprocess (pps
- ppx_deriving.show ppx_deriving.enum
+ ppx_deriving.show
+ ppx_deriving.enum
+ ppx_deriving.ord
ppx_deriving.eq )))