/* * Parser for OMakefiles. * * ---------------------------------------------------------------- * * Copyright (C) 2000 Jason Hickey, Caltech * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Additional permission is given to link this library with the * with the Objective Caml runtime, and to redistribute the * linked executables. See the file LICENSE.OMake for more details. * * Author: Jason Hickey * jyh@cs.caltech.edu */ %{ open Lm_printf open Lm_symbol open Lm_location open Omake_env open Omake_ast open Omake_ast_util open Omake_symbol let replacesuffixes_sym = Lm_symbol.add "replacesuffixes" module Pos = MakePos (struct let name = "Omake_parse" end) open Pos;; (* * Define flags. *) let define_flag (s, loc) = match s with "=" -> DefineNormal | "+=" -> DefineAppend | _ -> raise (OmakeException (loc_exp_pos loc, StringStringError ("undefined assignment operator", s))) (* * Helper functions for building text. *) let string_pair_exp (s, loc) = StringExp (s, loc), loc let string_white_exp (_, loc) = StringExp (" ", loc), loc (* * Convert args to parameters. *) let param_of_arg arg = match arg with StringExp (s, _) | SequenceExp ([StringExp (s, _)], _) -> Lm_symbol.add s | _ -> raise (OmakeException (loc_exp_pos (loc_of_exp arg), StringError "bad function parameter")) let rec simplify e = match e with SequenceExp ([e], _) -> simplify e | _ -> e let sequence_exp l loc = match l with [e] -> e | _ -> SequenceExp (l, loc) let params_of_args args = List.map param_of_arg args (* * Intern the method name. *) let method_id_intern idl = List.map Lm_symbol.add idl (* * Get a string from a method name. *) let method_id_buf idl = let buf = Buffer.create 32 in let rec collect idl = match idl with [id] -> Buffer.add_string buf id | id :: idl -> Buffer.add_string buf id; Buffer.add_char buf '.'; collect idl | [] -> () in collect idl; buf let method_id_string idl = let buf = method_id_buf idl in Buffer.contents buf let method_id_string_exp idl loc = StringExp (method_id_string idl, loc) let method_id_prefix_string_exp idl loc = let buf = method_id_buf idl in Buffer.add_char buf '.'; StringExp (Buffer.contents buf, loc) let var_quote (strategy, s, loc) = KeyExp (strategy, s, loc), loc (* * Convert to a body flag and text. *) let get_optcolon_text opt loc = match opt with None -> OptBody, NullExp loc | Some (body, arg) -> body, arg (* * A 3-place rule. *) let rule3 multiple (target, loc1) ploc pattern source loc2 body = let loc = union_loc loc1 loc2 in match pattern with Some (pattern, _) -> RuleExp (multiple, target, pattern, source, body, loc) | None -> RuleExp (multiple, target, NullExp loc2, source, body, loc) let rule2 multiple target ploc source loc2 body = rule3 multiple target ploc None source loc2 body %} /* * Terminators */ %token TokEof %token TokEol /* * Whitespace. */ %token TokWhite /* * Characters. */ %token TokLeftParen %token TokRightParen %token TokComma %token TokColon %token TokDoubleColon %token TokNamedColon %token TokDollar %token TokEq %token TokArray %token TokDot /* * Words. */ %token TokId %token TokKeyword %token TokCatch %token TokClass %token TokString %token TokBeginQuote %token TokEndQuote %token TokBeginQuoteString %token TokEndQuoteString %token TokStringQuote %token TokVar %token TokVarQuote /* * A complete program. */ %start deps %type <(Omake_ast.exp * Omake_ast.exp * Lm_location.loc) list> deps %start shell %start string %type shell %type string %% /* * A string is just some text. */ string: TokEof { raise End_of_file } | text TokEol TokEof { NoBody, sequence_exp $1 $2 } ; /* * Commands in a shell. * Bodies are usually not allowed. */ shell: TokEof { raise End_of_file } | shell_line TokEof { $1 } ; shell_line: /* Blank lines */ opt_white TokEol { NoBody, sequence_exp [] $2 } /* Builtin functions */ | TokKeyword opt_white text_optcolon TokEol { let id, loc1 = $1 in let body, arg = get_optcolon_text $3 $4 in let loc = union_loc loc1 $4 in body, CommandExp (Lm_symbol.add id, arg, [], loc) } /* Keyword applications */ | TokKeyword opt_white TokLeftParen opt_params TokRightParen opt_colon TokEol { let id, loc1 = $1 in let body = $6 in let loc = union_loc loc1 $7 in let args = $4 in let e = ApplyExp (EagerApply, Lm_symbol.add id, args, loc) in body, e } /* Catch expression is special */ | TokCatch opt_white TokId opt_white TokLeftParen opt_white TokId opt_white TokRightParen opt_colon TokEol { let _, loc1 = $1 in let loc = union_loc loc1 $11 in let name, _ = $3 in let v, _ = $7 in $10, CatchExp (Lm_symbol.add name, Lm_symbol.add v, [], loc) } /* Class expression is special */ | TokClass opt_id_list TokEol { let _, loc1 = $1 in let loc = union_loc loc1 $3 in NoBody, ClassExp (List.map Lm_symbol.add $2, loc) } /* Variable definition with a body */ | method_id_opt_white TokEq opt_white TokEol { let id, loc1 = $1 in let loc2 = $4 in let id = method_id_intern id in let loc = union_loc loc1 loc2 in let add_flag = define_flag $2 in ColonBody, VarDefBodyExp (id, DefineString, add_flag, [], loc) } /* Object definition with a body */ | method_id_prefix_opt_white TokEq opt_white TokEol { let id, loc1 = $1 in let loc2 = $4 in let id = method_id_intern id in let loc = union_loc loc1 loc2 in let add_flag = define_flag $2 in ColonBody, ObjectDefExp (id, add_flag, [], loc) } /* Variable definition on one line */ | method_id_opt_white TokEq opt_white text_nonempty TokEol { let id, loc1 = $1 in let loc2 = $5 in let e = simplify $4 in let id = method_id_intern id in let loc = union_loc loc1 loc2 in let add_flag = define_flag $2 in NoBody, VarDefExp (id, DefineString, add_flag, e, loc) } /* Key definition with a body */ | var_quote_opt_white TokEq opt_white TokEol { let _, id, loc1 = $1 in let loc2 = $4 in let loc = union_loc loc1 loc2 in let add_flag = define_flag $2 in ColonBody, KeyDefBodyExp (id, DefineString, add_flag, [], loc) } /* Key definition on one line */ | var_quote_opt_white TokEq opt_white text_nonempty TokEol { let _, id, loc1 = $1 in let loc2 = $5 in let e = simplify $4 in let loc = union_loc loc1 loc2 in let add_flag = define_flag $2 in NoBody, KeyDefExp (id, DefineString, add_flag, e, loc) } /* Array definition */ | method_id_opt_white TokArray opt_white TokEq opt_white TokEol { let id, loc1 = $1 in let loc2 = $6 in let id = method_id_intern id in let loc = union_loc loc1 loc2 in let add_flag = define_flag $4 in OptStringBody, VarDefBodyExp (id, DefineArray, add_flag, [], loc) } /* Array definition on one line */ | method_id_opt_white TokArray opt_white TokEq opt_white text_nonempty TokEol { let id, loc1 = $1 in let loc2 = $7 in let id = method_id_intern id in let loc = union_loc loc1 loc2 in let add_flag = define_flag $4 in NoBody, VarDefExp (id, DefineArray, add_flag, $6, loc) } /* Applications that use parens may also have a body */ | method_id_opt_white TokLeftParen opt_params TokRightParen opt_colon TokEol { let id, loc1 = $1 in let body = $5 in let loc = union_loc loc1 $6 in let args = $3 in let e = match id with [id] -> ApplyExp (EagerApply, Lm_symbol.add id, args, loc) | _ -> MethodApplyExp (EagerApply, method_id_intern id, args, loc) in body, e } /* Function definition */ | method_id_opt_white TokLeftParen opt_params TokRightParen opt_white TokEq opt_white TokEol { let id, loc1 = $1 in let args = $3 in let id = method_id_intern id in let params = params_of_args args in let loc = union_loc loc1 $8 in ColonBody, FunDefExp (id, params, [], loc) } /* 2-place rule definition that starts with a name */ | other_id_target TokColon source TokEol { ColonBody, rule2 false $1 $2 $3 $4 [] } | other_id_target TokColon target TokColon source TokEol { ColonBody, rule3 false $1 $2 $3 $5 $6 [] } | other_target TokColon source TokEol { ColonBody, rule2 false $1 $2 $3 $4 [] } | other_target TokDoubleColon source TokEol { ColonBody, rule2 true $1 $2 $3 $4 [] } | other_target TokColon target TokColon source TokEol { ColonBody, rule3 false $1 $2 $3 $5 $6 [] } | other_target TokDoubleColon target TokColon source TokEol { ColonBody, rule3 true $1 $2 $3 $5 $6 [] } /* * Super section. * We have to be careful about distinguishing rules from * super calls. */ | method_id_opt_white TokDoubleColon opt_white source_nonapply TokEol { let idl, loc = $1 in let e = method_id_string_exp idl loc in ColonBody, rule2 true (e, loc) $2 $4 $5 [] } | method_id_prefix_opt_white TokDoubleColon source TokEol { let idl, loc = $1 in let e = method_id_prefix_string_exp idl loc in ColonBody, rule2 true (e, loc) $2 $3 $4 [] } | method_id_opt_white TokDoubleColon opt_white method_id_opt_white TokLeftParen opt_params TokRightParen opt_colon TokEol { let super, loc1 = $1 in let name, _ = $4 in let body = $8 in let loc = union_loc loc1 $9 in let args = $6 in let e = match super, name with [super], [name] -> SuperApplyExp (EagerApply, Lm_symbol.add super, Lm_symbol.add name, args, loc) | _, [_] -> raise (OmakeException (loc_exp_pos loc, StringStringError ("illegal super class", method_id_string super))) | _ -> raise (OmakeException (loc_exp_pos loc, StringStringError ("illegal field name", method_id_string name))) in body, e } /* Anything else is a command to run */ | other_id_target TokEol { let e, loc = $1 in NoBody, ShellExp (e, loc) } | other_target TokEol { let e, loc = $1 in NoBody, ShellExp (e, loc) } ; /* * Dependencies only. */ deps: rev_deps TokEof { List.rev $1 } ; rev_deps: /* empty */ { [] } | rev_deps dep { $2 :: $1 } | rev_deps TokEol { $1 } ; dep: /* 2-place rule dependency */ target TokColon target TokEol { let _, loc2 = $2 in let target, loc1 = match $1 with Some (e, loc1) -> e, loc1 | None -> NullExp loc2, loc2 in let source = match $3 with Some (e, _) -> e | None -> NullExp loc2 in let loc = union_loc loc1 $4 in target, source, loc } ; /* * A variable lookup. */ apply: TokDollar opt_white TokLeftParen opt_white method_name opt_args TokRightParen { let _, strategy, loc1 = $1 in let _, loc2 = $7 in let idl, _ = $5 in let loc = union_loc loc1 loc2 in match idl with [id] -> ApplyExp (strategy, Lm_symbol.add id, $6, loc), loc | _ -> MethodApplyExp (strategy, method_id_intern idl, $6, loc), loc } | TokDollar opt_white TokLeftParen opt_white method_name TokColon neq_arg TokEq arg TokRightParen { let _, strategy, loc1 = $1 in let _, loc2 = $10 in let idl, _ = $5 in let loc = union_loc loc1 loc2 in let arg = match idl with [id] -> ApplyExp (NormalApply, Lm_symbol.add id, [], loc) | _ -> MethodApplyExp (NormalApply, method_id_intern idl, [], loc) in ApplyExp (strategy, replacesuffixes_sym, [$7; $9; arg], loc), loc } | TokDollar opt_white TokLeftParen opt_white id TokDoubleColon id opt_args TokRightParen { let _, strategy, loc1 = $1 in let _, loc2 = $9 in let super, _ = $5 in let v, _ = $7 in let loc = union_loc loc1 loc2 in SuperApplyExp (strategy, Lm_symbol.add super, Lm_symbol.add v, $8, loc), loc } | TokVar { let strategy, id, loc = $1 in ApplyExp (strategy, Lm_symbol.add id, [], loc), loc } | TokBeginQuote rev_text TokEndQuote { let id1, loc1 = $1 in let id2, loc2 = $3 in let loc = union_loc loc1 loc2 in let el = StringExp (id1, loc1) :: List.rev_append $2 [StringExp (id2, loc2)] in QuoteExp (el, loc), loc } | TokBeginQuoteString rev_text TokEndQuoteString { let id, loc1 = $1 in let _, loc2 = $3 in let loc = union_loc loc1 loc2 in QuoteStringExp (id.[0], List.rev $2, loc), loc } | TokStringQuote { let s, loc = $1 in QuoteExp ([StringExp (s, loc)], loc), loc } ; /* * A quoted variable. */ var_quote_opt_white: var_quote { $1 } | var_quote_white { let strategy, id, _, loc = $1 in strategy, id, loc } ; var_quote_white: var_quote TokWhite { let strategy, id, loc = $1 in let s, _ = $2 in strategy, id, s, loc } ; var_quote: TokVarQuote { $1 } ; /* * Variable lookup. */ quote_opt_white: var_quote_opt_white { var_quote $1 } ; quote_white: var_quote_white { let strategy, id, s, loc = $1 in let e, _ = var_quote (strategy, id, loc) in e, s, loc } ; quote: var_quote { var_quote $1 } ; /* * Names separated by dots. */ method_name: rev_method_name { let idl, loc = $1 in List.rev idl, loc } ; rev_method_name: id { let id, loc = $1 in [id], loc } | rev_method_name TokDot id { let idl, loc1 = $1 in let id, loc2 = $3 in id :: idl, union_loc loc1 loc2 } ; id: TokId { $1 } | TokKeyword { $1 } | TokCatch { $1 } | TokClass { $1 } ; opt_id_list: /* empty */ { [] } | opt_id_list white { $1 } | opt_id_list id { let id, _ = $2 in id :: $1 } ; /* * A target after identifier text. * It may not begin with equals, left-paren, or . * and it may not contains colons. */ other_id_target: method_id_opt_white { let idl, loc = $1 in method_id_string_exp idl loc, loc } | method_id_prefix_opt_white { let idl, loc = $1 in method_id_prefix_string_exp idl loc, loc } | quote_opt_white { $1 } ; method_id_opt_white: rev_method_id { let id, loc = $1 in List.rev id, loc } | rev_method_id_white { let id, _, loc = $1 in List.rev id, loc } ; method_id_prefix_opt_white: rev_method_id_prefix { let id, loc = $1 in List.rev id, loc } | rev_method_id_prefix_white { let id, _, loc = $1 in List.rev id, loc } ; rev_method_id_white: rev_method_id TokWhite { let id, loc1 = $1 in let s, loc2 = $2 in let loc = union_loc loc1 loc2 in id, s, loc } ; rev_method_id_prefix_white: rev_method_id_prefix TokWhite { let id, loc1 = $1 in let s, loc2 = $2 in let loc = union_loc loc1 loc2 in id, s, loc } ; rev_method_id: TokId { let id, loc = $1 in [id], loc } | rev_method_id_prefix TokId { let idl, loc1 = $1 in let id, loc2 = $2 in let loc = union_loc loc1 loc2 in id :: idl, loc } ; rev_method_id_prefix: rev_method_id TokDot { let idl, loc1 = $1 in let _, loc2 = $2 in let loc = union_loc loc1 loc2 in idl, loc } ; /* * The other_target collects all the other stuff that * is not a valid command prefix, but it does not allow colons. * Don't worry about catching all the other cases--here * are the things we should not match: * TokKeyword anything * TokCatch anything * method_id_opt_white TokEq * method_id_prefix_opt_white TokEq * method_id_opt_white TokArray * method_id_opt_white TokLeftParen * * So here are the sequences that put us into other mode: * 1. [^ TokKeyword TokCatch TokId TokColon] * 2. method_id [^ TokEq TokArray TokLeftParen TokDot TokWhite TokColon] * 3. method_id_white [^ TokEq TokArray TokLeftParen TokColon] * 4. method_id_prefix [^ TokEq TokWhite TokColon] * 5. method_id_prefix_white [^ TokEq TokColon] * Then collect anything except TokColon */ other_target: rev_other_target { let l, loc = $1 in sequence_exp (List.rev l) loc, loc } ; rev_other_target: other_start { let e, loc = $1 in [e], loc } | rev_method_id other_method_id { let idl, loc1 = $1 in let e, loc2 = $2 in let loc = union_loc loc1 loc2 in let el = [e; method_id_string_exp (List.rev idl) loc1] in el, loc } | rev_method_id_white other_method_id_white { let idl, s, loc1 = $1 in let e, loc2 = $2 in let loc = union_loc loc1 loc2 in let el = [e; StringExp (s, loc1); method_id_string_exp (List.rev idl) loc1] in el, loc } | rev_method_id_prefix other_method_id_prefix { let idl, loc1 = $1 in let e, loc2 = $2 in let loc = union_loc loc1 loc2 in let el = [e; method_id_prefix_string_exp (List.rev idl) loc1] in el, loc } | rev_method_id_prefix_white other_method_id_prefix_white { let idl, s, loc1 = $1 in let e, loc2 = $2 in let loc = union_loc loc1 loc2 in let el = [e; StringExp (s, loc1); method_id_prefix_string_exp (List.rev idl) loc1] in el, loc } | quote other_quote_id { let id, loc1 = $1 in let e, loc2 = $2 in let loc = union_loc loc1 loc2 in let el = [id; e] in el, loc } | quote_white other_quote_id_white { let id, s, loc1 = $1 in let e, loc2 = $2 in let loc = union_loc loc1 loc2 in let el = [id; StringExp (s, loc1); e] in el, loc } | rev_other_target target_next { let el, loc1 = $1 in let e, loc2 = $2 in let loc = union_loc loc1 loc2 in e :: el, loc } ; /************************************************************************ * Source arguments allow named colons. */ source: target { match $1 with Some (e, _) -> SymbolTable.add SymbolTable.empty normal_sym e | None -> SymbolTable.empty } | source TokNamedColon target { let table = $1 in let name, _ = $2 in match $3 with Some (e, _) -> SymbolTable.add table (Lm_symbol.add name) e | None -> table } ; /* * This source cannot look like an application. */ source_nonapply: source_target { match $1 with Some (e, _) -> SymbolTable.add SymbolTable.empty normal_sym e | None -> SymbolTable.empty } | source_nonapply TokNamedColon target { let table = $1 in let name, _ = $2 in match $3 with Some (e, _) -> SymbolTable.add table (Lm_symbol.add name) e | None -> table } ; source_target: /* empty */ { None } | other_id_target { Some $1 } | other_target { Some $1 } ; /************************************************************************ * Sequence sections. */ /* * text: [^ TokEol]* * text_next: [^ TokEol] * Leading whitespace is not stripped. */ text: rev_text { List.rev $1 } ; rev_text: /* empty */ { [] } | rev_text text_next { let e, _ = $2 in e :: $1 } ; /* * target: [^ TokEol TokColon TokNamedColon]* * Leading whitespace is stripped: * target_start: [^ TokEol TokColon TokNamedColon TokWhite] * target_next: [^ TokEol TokColon TokNamedColon] */ target: opt_white { None } | opt_white rev_target { let l, loc = $2 in Some (sequence_exp (List.rev l) loc, loc) } ; rev_target: target_start { let e, loc = $1 in [e], loc } | rev_target target_next { let l, loc1 = $1 in let e, loc2 = $2 in e :: l, union_loc loc1 loc2 } ; /* * text_optcolon: text_colon | text_noncolon * text_colon: [^ TokEol]* TokColon * text_noncolon: ([^ TokEol]* [^ TokEol TokColon])? */ text_optcolon: /* empty */ { None } | rev_text_noncolon { let w, l, loc = $1 in Some (OptBody, sequence_exp (List.rev (w @ l)) loc) } | rev_text_colon { let l, loc = $1 in Some (ColonBody, sequence_exp (List.rev (List.tl l)) loc) } ; /* * Strip trailing whitespace from nonempty text. */ text_nonempty: rev_text_opt_colon { let _, l, loc = $1 in sequence_exp (List.rev l) loc } ; rev_text_opt_colon: rev_text_noncolon { $1 } | rev_text_colon { let l, loc = $1 in [], l, loc } ; rev_text_noncolon: keyword_target_start { let e, loc = $1 in [], [e], loc } | rev_text_opt_colon target_start { let w, l, loc1 = $1 in let e, loc2 = $2 in [], e :: (w @ l), union_loc loc1 loc2 } | rev_text_opt_colon white { let w, l, loc1 = $1 in let e, loc2 = $2 in e :: w, l, union_loc loc1 loc2 } ; rev_text_colon: colon { let e, loc = $1 in [e], loc } | rev_text_opt_colon colon { let w, l, loc1 = $1 in let e, loc2 = $2 in e :: (w @ l), union_loc loc1 loc2 } ; /* * arg: [^ TokEol TokComma TokLeftParen TokRightParen]* * leading whitespace is stripped. */ opt_args: opt_white { [] } | white args { $2 } ; opt_params: opt_white { [] } | args { $1 } ; args: rev_args { List.rev $1 } ; rev_args: arg { [$1] } | rev_args TokComma arg { $3 :: $1 } ; arg: opt_white rev_arg { let l, loc = $2 in sequence_exp (List.rev l) loc } ; rev_arg: arg_start { let e, loc = $1 in [e], loc } | rev_arg arg_next { let l, loc1 = $1 in let e, loc2 = $2 in e :: l, union_loc loc1 loc2 } ; neq_arg: opt_white rev_neq_arg { let l, loc = $2 in sequence_exp (List.rev l) loc } ; rev_neq_arg: neq_arg_start { let e, loc = $1 in [e], loc } | rev_neq_arg neq_arg_next { let l, loc1 = $1 in let e, loc2 = $2 in e :: l, union_loc loc1 loc2 } ; /* * Generated section. */ %%GENERATED%% /* * Optional white space. */ opt_colon: opt_white { OptBody } | opt_white colon opt_white { ColonBody } ; opt_white: /* empty */ { None } | TokWhite { Some $1 } ;