(* Bitstring persistent patterns.
 * Copyright (C) 2008 Red Hat Inc., Richard W.M. Jones
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version,
 * with the OCaml linking exception described in COPYING.LIB.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * $Id$
 *)

open Printf

open Camlp4.PreCast
open Syntax
open Ast

type patt = Camlp4.PreCast.Syntax.Ast.patt
type expr = Camlp4.PreCast.Syntax.Ast.expr
type loc_t = Camlp4.PreCast.Syntax.Ast.Loc.t

(* Field.  In bitmatch (patterns) the type is [patt field].  In
 * BITSTRING (constructor) the type is [expr field].
 *)
type 'a field = {
  field : 'a;				(* field ('a is either patt or expr) *)
  flen : expr;				(* length in bits, may be non-const *)
  endian : endian_expr;			(* endianness *)
  signed : bool;			(* true if signed, false if unsigned *)
  t : field_type;			(* type *)
  _loc : Loc.t;				(* location in source code *)
  offset : expr option;			(* offset expression *)
  check : expr option;			(* check expression [patterns only] *)
  bind : expr option;			(* bind expression [patterns only] *)
  save_offset_to : patt option;		(* save_offset_to [patterns only] *)
}
and field_type = Int | String | Bitstring (* field type *)
and endian_expr =
  | ConstantEndian of Bitstring.endian	(* a constant little/big/nativeendian *)
  | EndianExpr of expr			(* an endian expression *)

type pattern = patt field list

type constructor = expr field list

type named = string * alt
and alt =
  | Pattern of pattern
  | Constructor of constructor

(* Work out if an expression is an integer constant.
 *
 * Returns [Some i] if so (where i is the integer value), else [None].
 *
 * Fairly simplistic algorithm: we can only detect simple constant
 * expressions such as [k], [k+c], [k-c] etc.
 *)
let rec expr_is_constant = function
  | <:expr< $int:i$ >> ->		(* Literal integer constant. *)
    Some (int_of_string i)
  | <:expr< $a$ + $b$ >> ->		(* Addition of constants. *)
    (match expr_is_constant a, expr_is_constant b with
     | Some a, Some b -> Some (a+b)
     | _ -> None)
  | <:expr< $a$ - $b$ >> ->		(* Subtraction. *)
    (match expr_is_constant a, expr_is_constant b with
     | Some a, Some b -> Some (a-b)
     | _ -> None)
  | <:expr< $a$ * $b$ >> ->	        (* Multiplication. *)
    (match expr_is_constant a, expr_is_constant b with
     | Some a, Some b -> Some (a*b)
     | _ -> None)
  | <:expr< $a$ / $b$ >> ->	        (* Division. *)
    (match expr_is_constant a, expr_is_constant b with
     | Some a, Some b -> Some (a/b)
     | _ -> None)
  | <:expr< $a$ lsl $b$ >> ->	        (* Shift left. *)
    (match expr_is_constant a, expr_is_constant b with
     | Some a, Some b -> Some (a lsl b)
     | _ -> None)
  | <:expr< $a$ lsr $b$ >> ->	        (* Shift right. *)
    (match expr_is_constant a, expr_is_constant b with
     | Some a, Some b -> Some (a lsr b)
     | _ -> None)
  | _ -> None				(* Anything else is not constant. *)

let string_of_field_type = function
  | Int -> "int"
  | String -> "string"
  | Bitstring -> "bitstring"

let patt_printer = function
  | <:patt< $lid:id$ >> -> id
  | <:patt< _ >> -> "_"
  | _ -> "[pattern]"

let rec expr_printer = function
  | <:expr< $lid:id$ >> -> id
  | <:expr< $int:i$ >> -> i
  | <:expr< $lid:op$ $a$ $b$ >> ->
    sprintf "%s %s %s" op (expr_printer a) (expr_printer b)
  | _ -> "[expr]"

let _string_of_field { flen = flen;
		       endian = endian; signed = signed; t = t;
		       _loc = _loc;
		       offset = offset; check = check; bind = bind;
		       save_offset_to = save_offset_to } =
  let flen = expr_printer flen in
  let endian =
    match endian with
    | ConstantEndian endian -> Bitstring.string_of_endian endian
    | EndianExpr expr -> sprintf "endian(%s)" (expr_printer expr) in
  let signed = if signed then "signed" else "unsigned" in
  let t = string_of_field_type t in

  let offset =
    match offset with
    | None -> ""
    | Some expr -> sprintf ", offset(%s)" (expr_printer expr) in

  let check =
    match check with
    | None -> ""
    | Some expr -> sprintf ", check(%s)" (expr_printer expr) in

  let bind =
    match bind with
    | None -> ""
    | Some expr -> sprintf ", bind(%s)" (expr_printer expr) in

  let save_offset_to =
    match save_offset_to with
    | None -> ""
    | Some patt ->
	match patt with
	| <:patt< $lid:id$ >> -> sprintf ", save_offset_to(%s)" id
	| _ -> sprintf ", save_offset_to([patt])" in

  let loc_fname = Loc.file_name _loc in
  let loc_line = Loc.start_line _loc in
  let loc_char = Loc.start_off _loc - Loc.start_bol _loc in

  sprintf "%s : %s, %s, %s%s%s%s%s (* %S:%d %d *)"
    flen t endian signed offset check bind save_offset_to
    loc_fname loc_line loc_char

let rec string_of_pattern_field ({ field = patt } as field) =
  sprintf "%s : %s" (patt_printer patt) (_string_of_field field)

and string_of_constructor_field ({ field = expr } as field) =
  sprintf "%s : %s" (expr_printer expr) (_string_of_field field)

let string_of_pattern pattern =
  "{ " ^
    String.concat ";\n  " (List.map string_of_pattern_field pattern) ^
    " }\n"

let string_of_constructor constructor =
  "{ " ^
    String.concat ";\n  " (List.map string_of_constructor_field constructor) ^
    " }\n"

let named_to_channel chan n = Marshal.to_channel chan n []

let named_to_string n = Marshal.to_string n []

let named_to_buffer str ofs len n = Marshal.to_buffer str ofs len n []

let named_from_channel = Marshal.from_channel

let named_from_string = Marshal.from_string

let create_pattern_field _loc =
  {
    field = <:patt< _ >>;
    flen = <:expr< 32 >>;
    endian = ConstantEndian Bitstring.BigEndian;
    signed = false;
    t = Int;
    _loc = _loc;
    offset = None;
    check = None;
    bind = None;
    save_offset_to = None;
  }

let set_lident_patt field id =
  let _loc = field._loc in
  { field with field = <:patt< $lid:id$ >> }
let set_int_patt field i =
  let _loc = field._loc in
  { field with field = <:patt< $`int:i$ >> }
let set_string_patt field str =
  let _loc = field._loc in
  { field with field = <:patt< $str:str$ >> }
let set_unbound_patt field =
  let _loc = field._loc in
  { field with field = <:patt< _ >> }
let set_patt field patt = { field with field = patt }
let set_length_int field flen =
  let _loc = field._loc in
  { field with flen = <:expr< $`int:flen$ >> }
let set_length field flen = { field with flen = flen }
let set_endian field endian = { field with endian = ConstantEndian endian }
let set_endian_expr field expr = { field with endian = EndianExpr expr }
let set_signed field signed = { field with signed = signed }
let set_type_int field = { field with t = Int }
let set_type_string field = { field with t = String }
let set_type_bitstring field = { field with t = Bitstring }
let set_location field loc = { field with _loc = loc }
let set_offset_int field i =
  let _loc = field._loc in
  { field with offset = Some <:expr< $`int:i$ >> }
let set_offset field expr = { field with offset = Some expr }
let set_no_offset field = { field with offset = None }
let set_check field expr = { field with check = Some expr }
let set_no_check field = { field with check = None }
let set_bind field expr = { field with bind = Some expr }
let set_no_bind field = { field with bind = None }
let set_save_offset_to field patt = { field with save_offset_to = Some patt }
let set_save_offset_to_lident field id =
  let _loc = field._loc in
  { field with save_offset_to = Some <:patt< $lid:id$ >> }
let set_no_save_offset_to field = { field with save_offset_to = None }

let create_constructor_field _loc =
  {
    field = <:expr< 0 >>;
    flen = <:expr< 32 >>;
    endian = ConstantEndian Bitstring.BigEndian;
    signed = false;
    t = Int;
    _loc = _loc;
    offset = None;
    check = None;
    bind = None;
    save_offset_to = None;
  }

let set_lident_expr field id =
  let _loc = field._loc in
  { field with field = <:expr< $lid:id$ >> }
let set_int_expr field i =
  let _loc = field._loc in
  { field with field = <:expr< $`int:i$ >> }
let set_string_expr field str =
  let _loc = field._loc in
  { field with field = <:expr< $str:str$ >> }
let set_expr field expr =
  let _loc = field._loc in
  { field with field = expr }

let get_patt field = field.field
let get_expr field = field.field
let get_length field = field.flen
let get_endian field = field.endian
let get_signed field = field.signed
let get_type field = field.t
let get_location field = field._loc
let get_offset field = field.offset
let get_check field = field.check
let get_bind field = field.bind
let get_save_offset_to field = field.save_offset_to
