#!/usr/bin/perl
# ISDK - Information System Development Kit
# Copyright (C) 1998-1999 Szilard Hajba, Peter Vazsonyi
#
# 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; either version 2 of
# the License, or (at your option) any later version.
#
# 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
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free
# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

require "getopts.pl";

# .ec format definition:
#     The file must contain c-code.
#     The '@' character is used by the preprocessor, so U shouldn't
#     use it outside a preprocessor item, unless U know what U R doing
#     Here is a list of the known preprocessor commands:
#	@^form		->	Form 'form'
#	@$widget	->	Widget value 'widget' in the actual form
#	@$form:widget	->	Widget value 'widget' in form 'form'
#	@*widget	->	Widget 'widget' in the actual form
#	@*form:widget	->	Widget 'widget' in form 'form'
#	@@list		->	List 'list'

%parent=(
	"object" => "",
	"scalar" => "editable",
	"bool"=> "scalar",
	"int"=> "scalar",
	"float"=> "scalar",
	"string"=> "scalar",
	"ref"=> "scalar",
	"list"=> "object",

	"tlist"=> "",
	"pixmap"=> "",
	"alarm"=> "",

	"form"=> "object",

	"widget"=> "object",
	"menubar"=> "widget",
	"menu"=> "widget",
	"hbox"=> "widget",
	"vbox"=> "widget",
	"border"=> "widget",
	"scrollwin"=> "widget",
	"notebook"=> "widget",
	"label"=> "widget",
	"picture"=> "widget",
	"hrule"=> "widget",
	"vrule"=> "widget",

	"callable"=> "widget",
	"button"=> "callable",
	"menuitem"=> "callable",

	"editable"=> "widget",
	"view"=> "editable",
	"entry"=> "editable",
	"text"=> "editable",
	"combo"=> "editable",
	"popup"=> "editable",
	"date"=> "editable",
	"chkbox"=> "editable",
	"browser"=> "editable",
	"column"=> "object",
	"clist"=> "editable",
	"ctree"=> "clist",
	"lcolumn"=> "object"
);
# Converting methods in VIM:
#	:s/^.*\(isdk_[a-z]\+_\)\([a-zA-Z0-9_]\+\).*$/		"\2" => "\1\2",/
%method=(
	"object" => {
		"init" => "isdk_object_init",
		"destroy" => "isdk_object_destroy",
		"reset" => "isdk_object_reset",
		"get" => "isdk_object_get",
		"set" => "isdk_object_set",
		"get_str" => "isdk_object_get_str",
		"add" => "isdk_object_add"
		},
	"scalar" => {
		"get_ptr" => "isdk_ref_get_ptr",
		},
	"bool"=> {
		},
	"int"=> {
		},
	"float"=> {
		},
	"string"=> {
		},
	"ref"=> {
		},
	"list"=> {
		},

	"param"=> {
		},
	"tlist"=> {
		},
	"pixmap"=> {
		},
	"alarm"=> {
		},

	"form"=> {
		"init" => "isdk_form_init",
		"destroy" => "isdk_form_destroy",
		"reset" => "isdk_form_reset",
		"add" => "isdk_form_add",
		"show" => "isdk_form_show",
		"hide" => "isdk_form_hide",
		"is_visible" => "isdk_form_is_visible"
		},
	"widget"=> {
		"transform_perms" => "isdk_widget_transform_perms",
		"set_perms" => "isdk_widget_set_perms",
		"get_perms" => "isdk_widget_get_perms",
		"set_invalid" => "isdk_widget_set_invalid",
		"set_sens" => "isdk_widget_set_sens",
		"get_state" => "isdk_widget_get_state",
		"set_state" => "isdk_widget_set_state",
		"set_gate" => "isdk_widget_set_gate",
		"call_gate" => "isdk_widget_call_gate",

		"set_alignment" => "isdk_widget_set_alignment",
		"set_focus" => "isdk_widget_set_focus",
		"set_default" => "isdk_widget_set_default",
		"set tip" => "isdk_widget_set tip",
		},
	"menubar"=> {
		},
	"menu"=> {
		},
	"hbox"=> {
		},
	"vbox"=> {
		},
	"border"=> {
		},
	"scrollwin"=> {
		},
	"notebook"=> {
		},
	"label"=> {
		},
	"picture"=> {
		},
	"hrule"=> {
		},
	"vrule"=> {
		},

	"callable"=> {
		},
	"button"=> {
		},
	"menuitem"=> {
		},

	"editable"=> {
		},
	"view"=> {
		},
	"entry"=> {
		},
	"text"=> {
		},
	"combo"=> {
		},
	"popup"=> {
		},
	"date"=> {
		},
	"chkbox"=> {
		},
	"browser"=> {
		"set_default" => "isdk_widget_set_default",
		"resync" => "isdk_browser_resync",
		"row_insert" => "isdk_browser_row_insert",
		"row_delete" => "isdk_browser_row_delete",
		"row_delete_id" => "isdk_browser_row_delete_id",
		"row_delete_selected" => "isdk_browser_row_delete_selected",
		"table_insert_func" => "isdk_browser_table_insert_func",
		"table_update_func" => "isdk_browser_table_update_func",
		"get_item" => "isdk_browser_get_item",
		"get_item_selected" => "isdk_browser_get_item_selected",
		"set_item" => "isdk_browser_set_item",
		"get_row" => "isdk_browser_get_row",
		"get_row_selected" => "isdk_browser_get_row_selected",
		"get_rownum" => "isdk_browser_get_rownum",
		"get_browse" => "isdk_browser_get_browse",
		"set_default_index" => "isdk_browser_set_default_index",
		"refresh" => "db_browser_refresh" 
		},
	"column"=> {
		},
	"clist"=> {
		},
	"ctree"=> {
		"row_insert" => "isdk_ctree_row_insert",
		"get_item" => "isdk_ctree_get_item",
		"set_item_selected" => "isdk_ctree_set_item_selected",
		"get_item_selected" => "isdk_ctree_get_item_selected",
		"get_path_selected" => "isdk_ctree_get_path_selected",
		"set_item" => "isdk_ctree_set_item",
		"freeze" => "isdk_ctree_freeze",
		"thaw" => "isdk_ctree_thaw",
		"foreach" => "isdk_ctree_foreach"
		},
	"lcolumn"=> {
		}
);

$excep_labelnum=0;
$excep_count=0;
$excep_needlabel=0;

sub ec_error {
    my($errmsg)=@_;
    print STDERR "$file:".($line-1).": error: $errmsg\n";
    exit 1;
}

sub ec_write_out {
    my($text)=@_;
    $text=~s/#!!#/@/go;
    print OUT $text;
#    print $text;
}

sub ec_read_in {
    my($ln);
    while (($ln=<IN>) && ($ln=~/^\s*\/\//)) {
	$line++;
    }
    ec_write_out "#line $line \"$file\"\n";
    $line++;
    $ln=~s/\\@/#!!#/go;
    return $ln;
}

#sub ec_read_in {
#    my($ln)="";
#    print ">>>>".(split(/"/, $ln)%2)."\n";
#    while (($ln.=<IN>) && (split(/"/, $ln)%2) && (split(/'/, $ln)%2)){};
#    print "-----------------------------\n$text====================\n";
#    $ln=~s/\n/'\n#line '.$line++.'"$file"\n'/ge;
##    $ln=~s/\\\n[^\n]*\n/\n/g;
#    $ln=~s/\\@/#!!#/go;
#    return $ln;
#}

sub tree_depth {
    my($zilu)=1+split "/", @_;
    return $zilu;
}

sub tree_list {
    my ($zilu)=@_;
    $zilu =~ s+/+", "+g;
    return $zilu;
}

sub ec_parse_params {
   my(@wargs)= split( /\s*,\s*/, $_[0] );
   my(@gargs)= split( /\s*,\s*/, $_[1] );
   my ($x);
   my (%h,@l);

   foreach $k (keys %P::) {
   	$a=P;
	$a.='::'.$k;
#   	print "$k->",$$a,"\n";
	$$a="";
   };

   foreach $x (@gargs) {
   	if ( $x =~ /^(.+)=(.+)$/ ) {
		grep (/^!?$1$/,@wargs) || ec_error("Invalid arg: ".$1);
		@wargs = grep (!/^!?$1$/,@wargs);
		$p=P;
		$p.="::".$1;
		$$p=$2;
	} else {
		$l[$#l+1]=$x;
	}
   }
   (@wargs = grep (/^!/, @wargs)) && 
   	ec_error("Obligate args: " . join(",",@wargs));
   return @l;
}

# ============================================== Class/object functions
sub is_compatible {
    my ($obj, $class)=@_;
    my ($cls)=$objclass{$obj};
    while (($cls ne $class) && $cls) {
	$cls=$parent{$cls};
    }
    return $cls eq $class;
}

# ====================================================== Error checking
sub ec_test_form {
    my ($obj)=@_;
    if (!is_compatible($obj, "form")) {
	ec_error "Invalid form \@\^$obj";
    }
}
#
#sub ec_test_tlist {
#    my ($obj)=@_;
#    if (!$bttlist{$obj}) {
#	ec_error "Invalid tlist \@\@$obj";
#    }
#}

# ======================================================= Token parsers
sub ec_parse_value {
    my ($ln)=@_;
    if ($ln =~ /\@\$(\w+):(\w+)/) {
	my ($obj)="$1:$2";
	if (is_compatible($obj, "browser")) {
	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\]\[,]+)\]=([^;]+);/isdk_browser_set_item_selected(BT_FORM_WIDGET("$1", "$2"), ($3), ($4));/o;
	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+),\s*([^\],]+)\]=([^;]+);/isdk_browser_set_item(BT_FORM_WIDGET("$1", "$2"), ($3), ($4), ($5));/o;
	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\]\[,]+)\]/isdk_browser_get_item_selected(BT_FORM_WIDGET("$1", "$2"), ($3))/o;
	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+),\s*([^\],]+)\]/isdk_browser_get_item(BT_FORM_WIDGET("$1", "$2"), ($3), ($4))/o;
	}
	if (is_compatible($obj, "ctree")) {
	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+)\]=([^;]+);/isdk_ctree_set_item_selected(BT_FORM_WIDGET("$1", "$2"), ($3), ($4));/o;
#	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+),\s*([^\],]+)\]=([^;]+);/isdk_ctree_set_item(BT_FORM_WIDGET("$1", "$2"), ($3), ($4), ($5));/o;
	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+)\]/isdk_ctree_get_item_selected(BT_FORM_WIDGET("$1", "$2"), ($3))/o;
#	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+),\s*([^\],]+)\]/isdk_ctree_get_item(BT_FORM_WIDGET("$1", "$2"), ($3), ($4))/o;
	}
#	if (is_compatible($obj, "clist")) {
#	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+)\]=([^;]+);/isdk_ctree_set_item_selected(BT_FORM_WIDGET("$1", "$2"), ($3), ($4));/o;
#	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+),\s*([^\],]+)\]=([^;]+);/isdk_ctree_set_item(BT_FORM_WIDGET("$1", "$2"), ($3), ($4), ($5));/o;
#	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+)\]/isdk_ctree_get_item_selected(BT_FORM_WIDGET("$1", "$2"), ($3))/o;
#	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\[([^\],]+),\s*([^\],]+)\]/isdk_ctree_get_item(BT_FORM_WIDGET("$1", "$2"), ($3), ($4))/o;
#	}
	if (is_compatible($obj, "editable")) {
	    return $ln if $ln =~ s/@\$(\w+):(\w+)=([^;]+);/isdk_object_set(BT_FORM_WIDGET("$1", "$2"), ($3));/o;
	    return $ln if $ln =~ s/\@\$(\w+):(\w+)\b/isdk_object_get(BT_FORM_WIDGET("$1", "$2"))/o;
	}
	ec_error "Invalid object \@\$$obj";
    } elsif ($ln =~ /\@\$:(\w+)/) {
	my ($obj)="$with_form:$1";
	if (is_compatible($obj, "browser")) {
	    return $ln if $ln =~ s/\@\$:(\w+)\[([^\],]+)\]=([^;]+);/isdk_browser_set_item_selected(BT_FORM_WIDGET("$with_form", "$1"), ($2), ($3));/o;
	    return $ln if $ln =~ s/\@\$:(\w+)\[([^\],]+),([^\],]+)\]=([^;]+);/isdk_browser_set_item(BT_FORM_WIDGET("$with_form", "$1"), ($2), ($3), ($4));/o;
	    return $ln if $ln =~ s/\@\$:(\w+)\[([^\],]+)\]/isdk_browser_get_item_selected(BT_FORM_WIDGET("$with_form", "$1"), ($2))/o;
	    return $ln if $ln =~ s/\@\$:(\w+)\[([^\],]+),([^\],]+)\]/isdk_browser_get_item(BT_FORM_WIDGET("$with_form", "$1"), ($2), ($3))/o;
	}
	if (is_compatible($obj, "ctree")) {
	    return $ln if $ln =~ s/\@\$:(\w+)\[([^\],]+)\]=([^;]+);/isdk_ctree_set_item_selected(BT_FORM_WIDGET("$with_form", "$1"), ($2), ($3));/o;
	    return $ln if $ln =~ s/\@\$:(\w+)\[([^\],]+)\]/isdk_ctree_get_item_selected(BT_FORM_WIDGET("$with_form", "$1"), ($2))/o;
	}
#	if (is_compatible($obj, "clist")) {
#	    return $ln if $ln =~ s/\@\$:(\w+)\[([^\],]+)\]=([^;]+);/isdk_ctree_set_item_selected(BT_FORM_WIDGET("$with_form", "$1"), ($2), ($3))/o;
#	    return $ln if $ln =~ s/\@\$:(\w+)\[([^\],]+)\]/isdk_ctree_get_item_selected(BT_FORM_WIDGET("$with_form", "$1"), ($2))/o;
#	}
	if (is_compatible($obj, "editable")) {
	    return $ln if $ln =~ s/@\$:(\w+)=([^;]+);/isdk_object_set(BT_FORM_WIDGET("$with_form", "$1"), ($2));/o;
	    return $ln if $ln =~ s/@\$:(\w+)\b/isdk_object_get(BT_FORM_WIDGET("$with_form", "$1"))/o;
	}
	ec_error "Invalid object \@\$$obj";
    } elsif ($ln =~ /\@\$(\w+)/) {
	my ($obj)="$act_form:$1";
	if (is_compatible($obj, "browser")) {
	    return $ln if $ln =~ s/\@\$(\w+)\[([^\],]+)\]=([^;]+);/isdk_browser_set_item_selected(BT_WIDGET("$1"), ($2), ($3));/o;
	    return $ln if $ln =~ s/\@\$(\w+)\[([^\],]+),([^\],]+)\]=([^;]+);/isdk_browser_set_item(BT_WIDGET("$1"), ($2), ($3), ($4));/o;
	    return $ln if $ln =~ s/\@\$(\w+)\[([^\],]+)\]/isdk_browser_get_item_selected(BT_WIDGET("$1"), ($2))/o;
	    return $ln if $ln =~ s/\@\$(\w+)\[([^\],]+),([^\],]+)\]/isdk_browser_get_item(BT_WIDGET("$1"), ($2), ($3))/o;
	}
	if (is_compatible($obj, "ctree")) {
	    return $ln if $ln =~ s/\@\$(\w+)\[([^\],]+)\]=([^;]+);/isdk_ctree_set_item_selected(BT_WIDGET("$1"), ($2), ($3));/o;
	    return $ln if $ln =~ s/\@\$(\w+)\[([^\],]+)\]/isdk_ctree_get_item_selected(BT_WIDGET("$1"), ($2))/o;
	}
#	if (is_compatible($obj, "clist")) {
#	    return $ln if $ln =~ s/\@\$(\w+)\[([^\],]+)\]=([^;]+);/isdk_ctree_set_item_selected(BT_WIDGET("$1"), ($2), ($3));/o;
#	    return $ln if $ln =~ s/\@\$(\w+)\[([^\],]+)\]/isdk_ctree_get_item_selected(BT_WIDGET("$1"), ($2))/o;
#	}
	if (is_compatible($obj, "editable")) {
	    return $ln if $ln =~ s/@\$(\w+)=([^;]+);/isdk_object_set(BT_WIDGET("$1"), ($2));/o;
	    return $ln if $ln =~ s/@\$(\w+)\b/isdk_object_get(BT_WIDGET("$1"))/o;
	}
	ec_error "Invalid object \@\$$obj";
    } elsif ($ln =~ /\@\$/) {
	return $ln if $ln =~ s/\@\$/_isdk_obj/o;
	ec_error "Invalid operands to '\@\$'";
    } else {
	ec_error "Invalid operands to '\@\$'";
    }
    return $ln;
}

sub ec_parse_form {
    my ($ln)=@_;
    if ($ln =~ /\@\^(\w+)/) {
	my ($form)=$1;
	if (is_compatible($form, "form")) {
	    return $ln if ($ln =~ s/@\^(\w+)/BT_FORM("$1")/o);
	} else {
	    ec_error "Invalid form \@\^$form";
	}
    } elsif ($ln =~ /\@\^:/) {
	return $ln if $ln =~
	s/\@\^:/BT_FORM("$with_form")/o;
    } elsif ($ln =~ /\@\^/) {
	return $ln if $ln =~
	s/\@\^/(_isdk_obj->class==BT_CLASS("form")?ISDK_FORM(_isdk_obj):ISDK_WIDGET(_isdk_obj)->form)/o;
    }
}

sub ec_parse_object {
    my ($ln)=@_;
    if ($ln =~ /\@\*(\w+):(\w+)/) {
	my ($obj)="$1:$2";
	if (is_compatible($obj, "browser")) {
	}
	if (is_compatible($obj, "ctree")) {
	}
	if (is_compatible($obj, "editable")) {
	    return $ln if $ln =~ s/\@\*(\w+):(\w+)\b/BT_FORM_WIDGET("$1", "$2")/o;
	}
	if (is_compatible($obj, "callable")) {
	}
	if (is_compatible($obj, "widget")) {
	    return $ln if $ln =~ s/\@\*(\w+):(\w+)\b/BT_FORM_WIDGET("$1", "$2")/o;
	} else {
	    ec_error "Invalid object \@\*$obj";
	}
    } elsif ($ln =~ /\@\*:(\w+)/) {
	my ($obj)="$with_form:$1";
#	if (is_compatible($obj, "browser")) {
#	}
#	if (is_compatible($obj, "ctree")) {
#	}
	if (is_compatible($obj, "editable")) {
	    return $ln if $ln =~ s/@\*:(\w+)\b/BT_FORM_WIDGET("$with_form", "$1")/o;
	}
	if (is_compatible($obj, "callable")) {
	}
	if (is_compatible($obj, "widget")) {
	    return $ln if $ln =~ s/@\*:(\w+)\b/BT_FORM_WIDGET("$with_form", "$1")/o;
	} else {
	    ec_error "Invalid object \@\*$obj";
	}
    } elsif ($ln =~ /\@\*(\w+)/) {
	my ($obj)="$act_form:$1";
#	if (is_compatible($obj, "browser")) {
#	}
#	if (is_compatible($obj, "ctree")) {
#	}
	if (is_compatible($obj, "editable")) {
	    return $ln if $ln =~ s/@\*(\w+)\b/BT_WIDGET("$1")/o;
	}
	if (is_compatible($obj, "callable")) {
	}
	if (is_compatible($obj, "widget")) {
	    return $ln if $ln =~ s/@\*(\w+)\b/BT_WIDGET("$1")/o;
	} else {
	    ec_error "Invalid object \@\*$obj";
	}
    } elsif ($ln =~ /\@\*/) {
	    $line =~ s/\@\*/_isdk_obj/o;
    }
    return $ln;
}

sub ec_parse_tlist {
    my ($ln)=@_;
    if ($ln =~ /\@\@(\w+)/) {
	my ($tlist)=$1;
	if (is_compatible($tlist, "tlist")) {
# @@list[<kif>]
	    return $ln if $ln =~ s/\@\@(\w+)\[([^\]]+)\]/list_get(BT_TLIST("$1"), ($2))/go;
# @@list
	    return $ln if $ln =~ s/\@\@(\w+)\b/BT_TLIST("$1")/go;
	} else {
	    ec_error "Invalid tlist \@\^$tlist";
	}
    }
}

sub ec_transform_method {
    my ($obj, $method)=@_;
    $obj="$with_form$obj" if ($obj =~ /^:/ && $with_form);
    my ($class)=$obj =~ /:/ ? $objclass{$obj} : $objclass{"$act_form:$obj"};
    if ($method{$class}{$method}) {
	return $method{$class}{$method}."(@*$obj";
    }
    while (($class ne $classp) && $class) {
	$class=$parent{$class};
	if ($method{$class}{$method}) {
	    return $method{$class}{$method}."(@*$obj";
	}
    }
    ec_error("Invalid method called \@\*$obj.$method()");
}

sub ec_parse_method {
    my ($ln)=@_;
    if ($ln =~ /\@\*(\w*:\w+).(\w+)\(\s*\)/) {
	my ($obj, $method)=($1, $2);
	return $ln if $ln =~ s/\@\*(\w*:\w+)\.(\w+)\(\s*\)/&ec_transform_method($obj, $2).")"/oe;
    } elsif ($ln =~ /\@\*(\w*:\w+).(\w+)\(/) {
	my ($obj, $method)=($1, $2);
	return $ln if $ln =~ s/\@\*(\w*:\w+)\.(\w+)\(/&ec_transform_method($obj, $2).", "/oe;
    } elsif ($ln =~ /\@\*(\w+)\.(\w+)\(\s*\)/) {
#	my ($obj, $method)=("$act_form:$1", $2);
	my ($obj, $method)=("$1", $2);
	return $ln if $ln =~ s/\@\*(\w+)\.(\w+)\(\s*\)/&ec_transform_method($obj, $2).")"/oe;
    } elsif ($ln =~ /\@\*(\w+)\.(\w+)\(/) {
#	my ($obj, $method)=("$act_form:$1", $2);
	my ($obj, $method)=("$1", $2);
	return $ln if $ln =~ s/\@\*(\w+)\.(\w+)\(/&ec_transform_method($obj, $2).", "/oe;
    }
}

sub ec_gen_line {
    my($line)=@_;
# ================================================= Multi column macros
    if ($line =~ /^@(\w+)\(([^\)]*)\)$/) {
	if ($1 eq 'gen_modify_funcs') {
	    &ec_gen_modify_funcs($2);
	} elsif ($1 eq 'gen_1level_browse_funcs') {
	    &ec_gen_1level_browse_funcs($2);
	} elsif ($1 eq 'gen_2level_browse_funcs') {
	    &ec_gen_2level_browse_funcs($2);
	}
# ================================================= Single column macros
    } elsif (!$opt_t) {
	if ($line =~ /^\s*\@func\s+(\w+)\s*\(\@\^(\w+)\)(.*)$/) {
	    $act_form=$2; $with_form="";
	    $reg.="    isdk_register_formfunc(ISDK_FORM_FUNC(f_$1), \"$1\");\n";
	    ec_write_out("void f_$1(IsdkObject *_isdk_obj, char **_isdk_param)$3\n");
	    ec_write_out("    int _ec_internal=isdk_debug(\"\@func called: $1(\%p)\\n\", _isdk_obj);\n") if $opt_d;
	} elsif ($line =~ /^\s*\@func\s+(\w+)(.*)$/) {
	    $act_form=""; $with_form="";
	    $reg.="    isdk_register_formfunc(ISDK_FORM_FUNC(f_$1), \"$1\");\n";
	    ec_write_out("void f_$1(IsdkObject *_isdk_obj, char **_isdk_param)$2\n");
	    ec_write_out("    int _ec_internal=isdk_debug(\"\@func called: $1(\%p)\\n\", _isdk_obj);\n") if $opt_d;
	# --- Exception handling
	} elsif ($line =~ /^(\s*)\@begin\s*{\s*$/) {
	    $excep_needlabel=0;
	    ec_write_out("$1isdk_exception_begin {\n");
	    $excep_count++;
	} elsif ($line =~ /^(\s*)}\s*\@exception\s*{\s*$/) {
	    ec_write_out("$1    isdk_exception_release();\n");
	    ec_write_out("$1} isdk_exception_end {\n");
	    ec_write_out("$1    isdk_exception_release();\n");
	    $excep_count--;
	} elsif ($line =~ /^(\s*)}\s*\@end\s*$/) {
	    ec_write_out("$1    isdk_exception_next();\n");
	    $excep_needlabel && do { 
		ec_write_out("$1exception_label_$excep_labelnum:\n");
	    	$excep_labelnum++; 
	    };
	    ec_write_out("$1}\n");
	} elsif ($line =~ /\@break\s*;/) {
	    $line =~ s/\@break\s*;/goto exception_label_$excep_labelnum;/g;
	    ec_write_out($line);
	    $excep_needlabel=1;
	# ---
	} elsif ($line =~ /^\s*\@decl\s+(\w+)\s+(\S+)\s*;\s*$/) {
	    $objclass{$2}=$1;
	} elsif ($line =~ /^\s*\@with\s+\@\^(\w*)\s*;\s*$/) {
	    $form=$1;
	    if ($form) {
		ec_test_form $form;
#		$act_form=$form;
		$with_form=$form;
	    } else {
#		$act_form=$act_form_save;
		$with_form=$form;
	    }
	} else {
#	@?			->	Exception number
	    $line =~ s/\@\?/isdk_exception/go;
#	@!param			->	Parameter 'param'
	    $line =~ s/@!(\w+)=([^;]+);/isdk_params_set_param("$1", ($2));/go;
	    $line =~ s/@!(\w+)\b/BT_PARAM("$1")/go;		# @!param

	    $line =~ s/\breturn\s*([^;]*);/"{".("isdk_exception_release(); " x $excep_count)."return $1;}"/goe if $excep_count;

	    while ($line =~ /\@\^/) {$line=ec_parse_form $line;}
	    while ($line =~ /\@\*\w*:\w+\.\w+\(/) {$line=ec_parse_method($line);}
	    while ($line =~ /\@\*\w+\.\w+\(/) {$line=ec_parse_method($line);}
	    while ($line =~ /\@\$/) {$line=ec_parse_value $line;}
	    while ($line =~ /\@\*/) {$line=ec_parse_object $line;}
	    while ($line =~ /\@\@/) {$line=ec_parse_tlist $line;}
	    ec_write_out($line);
	}
    } else {
	ec_write_out($line);
    }
}

sub ec_gen_text {
    my($text)=@_;
    my($line);
    foreach $line (split("\n", $text)) {
	ec_gen_line($line."\n");
    }
}

sub ec_parse_block {
    my($line, $ln);
    $line=ec_read_in;
    if ($line !~ /^\s*\@\{\s*$/) {
	ec_error("Block required".$line);
    }
    for ($ln=ec_read_in; $ln !~ /^\s*\@}\s*$/; $ln=ec_read_in) {
	ec_gen_line($ln);
    }
}

sub ec_parse_fml {
    my ($file)=@_;
    undef $/;
    open FILE, $file;
    my($fml, $line, $form)=(<FILE>, "");
    $fml =~ s/#[^\n]*\n//g;
    $fml =~ s/\n[ \t]*/ /g;
    while ($fml) {
	if ($fml =~ /^[^(]*\(\/?(\w+)[^)]*name=(\w+)[^)]*\)/) {
	    my ($tag, $name)=($1, $2);
	    $form=$name if ($tag eq 'form');
	    if (grep(/ $tag /, ' form tlist alarm pixmap ')) {
		$line.="$tag $name\n";
	    } else {
		$line.="$tag $form:$name\n";
	    }
	}
	$fml =~ s/^[^(]*\([^)]*\)[^(]*//;
    }
    close FILE;
    return $line;
}

sub ec_load_info {
    open INFO, "<".$opt_c;
    while ($line=<INFO>) {
	if ($line =~ /^(\w+) (\S+)$/) {
	    $objclass{$2}=$1;
	}
    }
    close INFO;
}

exit 1 if (!Getopts("hdgc:to:"));
if ($opt_g) {
    open OUT, ">".$opt_o;
    for (;$ARGV[0];shift) {
	if ($ARGV[0] =~ /\.fml$/) {
	    print OUT ec_parse_fml($ARGV[0]);
	}
    }
} else {
    ec_load_info($opt_c) if $opt_c;
    $file=$ARGV[0]; $line=1;
    open IN, "<".$file;
    open OUT, ">".$opt_o;
    ec_write_out(sprintf(<<EOM, $ARGV[0]));
#line 1 "ecpp_internal_code"
/* !!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!! *
 * This file was generated automatically by ec-preproc from %s.
 *                          DO NOT EDIT!!
 * !!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!! */

//#include <isdk/isdk.h>

EOM

#    $/=';';				#PV_PRB
    while ($ln=ec_read_in) {
#print STDERR "DEBUG $ln\n";
	ec_gen_line($ln);
    }
    $ARGV[0] =~ /^(\w+)\b/;
    $class=$1;
    ec_write_out(<<EOM);

#line 2 "ecpp_internal_code"
/* ==== Initialization ==== */
void ec_$class\_init() {
$reg
}
EOM
    close IN;
    close OUT;
}

#======================================================================= MODIFY
sub ec_gen_modify_funcs {
    my($params)=@_;
#    if ($params =~ /^([^,]+),(.+)$/) {
#	my($ret, $table, @key)=("", $1, split(/\s*,\s*/, $2));
#	ec_write_out(<<EOM);
    my (@key) = ec_parse_params("!table,gen,format",$params);
    my ($table, $gen, $format) = ($P::table, $P::gen, $P::format);
    if ($gen) {
    	    $format || ($format='"%d"');
	    $key[$#key+1]=$P::gen;
    }
    my ($keys)='_'.join '_', @key;
    $keys="" if !$#key;
    
	ec_gen_text(<<EOM);

// ======== MACRO: gen_modify_funcs    $table
// static Bool ${table}_new;
// static IsdkForm *${table}_last_form;

\@func ${table}_modify(\@^${table}${keys}) {
    isdk_form_reset(\@^${table}_edit);
    \@\$${table}_edit:_new="f";
EOM
	foreach $k (@key) {
	    ec_gen_text("    \@\$${table}_edit:$k=\@\$__$k;\n");
	    ec_gen_text("    \@\$${table}_edit:__$k=\@\$__$k;\n");
	    ec_gen_text("    \@\*${table}_edit:$k.set_sens(false);\n");
	}
	ec_gen_text(<<EOM);
    \@\$${table}_edit:_last_form=\@^->name;
    if(!db_form_load(conn, \@^${table}_edit)) {
	isdk_form_hide(\@^);
	isdk_form_show(\@^${table}_edit);
    }
}

\@func ${table}_delete(\@^${table}${keys}) {
    if (!db_form_delete(conn, \@^)) {
//	db_browser_refresh(@*_browser.get_ptr());
	db_browser_refresh(isdk_ref_get_ptr(@*_browser));
    }
}

\@func ${table}_ok(\@^${table}_edit) {
    int ok;
    if (isdk_form_validate(\@^)) return;
EOM
	&ec_parse_block;
	ec_gen_text(<<EOM);
    if ((\@\$_new)[0]=='t') {
EOM
	if ($gen) {
	ec_gen_text(<<EOM);
		int key;
		char s\[25\];
		key=atoi(db_func(conn, "SELECT nextval('seq_${table}_${gen}')"));
		sprintf(s,$format,key);
		\@\$$gen=s;
EOM
	}
	ec_gen_text(<<EOM);
	ok=db_form_insert(conn, \@^);
    } else
	ok=db_form_update(conn, \@^);
    switch (ok) {
	case 0:
	    isdk_form_hide(\@^${table}_edit);
	    db_browser_refresh(isdk_ref_get_ptr(
			BT_FORM_WIDGET(\@\$_last_form, "_browser")));
	    isdk_form_show(BT_FORM(\@\$_last_form));
	    break;
	case 2:
	    \@\$_new="f";
EOM
	foreach $k (@key) {
#	    ec_gen_text("            \@\$${table}_edit:$k=\@\$__$k;\n");
	    ec_gen_text("            \@\$${table}_edit:__$k=\@\$${table}_edit:$k;\n");
	}
	ec_gen_text(<<EOM);
	    db_form_load(conn, \@^);
    }
}

// \@func ${table}_edit_cancel {
//    isdk_form_hide(\@^);
//    isdk_form_show(BT_FORM(\@\$_last_form));
// }
EOM
#    } else {
#	ec_error("Parse error");
#    }
}

#====================================================================== 1 level
sub ec_gen_1level_browse_funcs {
    my($params)=@_;
    if ($params =~ /^([^,]+),([^,]+)$/) {
	my($table, $key, $ret)=($1, $2, "");
	ec_gen_text(<<EOM);

// ======== MACRO: gen_1level_browse_funcs    $table $key
\@func ${table} {
    isdk_form_show_reset(\@^${table});
}

\@func ${table}_new {
    isdk_form_reset(\@^${table}_edit);
    \@\$${table}_edit:_new="t";
    \@\*${table}_edit:${key}.set_sens(true);
\@with \@^${table}_edit;
EOM
	&ec_parse_block;
#	ec_write_out(<<EOM);
	ec_gen_text(<<EOM);
\@with \@^;
    \@\$${table}_edit:_last_form=\@^->name;
    isdk_form_hide(\@^);
    isdk_form_show(\@^${table}_edit);
}

\@func ${table}_copy(\@^${table}) {
    if (\@*__${key}.get_rownum()) {
	isdk_form_reset(\@^${table}_edit);
	\@\$${table}_edit:_new="t";
	\@\*${table}_edit:${key}.set_sens(true);
	\@\$${table}_edit:${key}=\@\$__${key};
	\@\$${table}_edit:__${key}=\@\$__${key};
	\@\$${table}_edit:_last_form=\@^->name;
	if(!db_form_load(conn, \@^${table}_edit)) {
	    \@\$${table}_edit:${key}="";
	    \@\$${table}_edit:__${key}="";
	    isdk_form_hide(\@^);
	    isdk_form_show(\@^${table}_edit);
	}
    }
}

EOM
    } else {
	ec_error("Parse error");
    }
}

#====================================================================== 2 level
sub ec_gen_2level_browse_funcs {
    my($params)=@_;
    if ($params =~ /^(.+)$/) {
	my($q, $ret, $query, $col, $root1, $key1, $root2, $key2)=("", "", "", split(/\s*,\s*/, $1));
	my $ln=ec_read_in;
	if ($ln !~ /^\s*\@\{\s*$/) {
	    ec_error("Block required".$ln);
	}
	for ($ln=ec_read_in; $ln !~ /^\s*\@}\s*$/ && !eof IN; $ln=ec_read_in) {
	    chomp $ln;
	    $query.=$ln;
	}
	$query =~ s/\s+/ /g;
	$query =~ s/(^\s+)|(\s+$)//g;
	$query =~ s/(last_modify)$/$1>=\%\%d/g;
	while ($query =~ s/(\@\$\w+:?\w+)/'\%s'/) {
	    $q.=", $1";
	}
#	ec_write_out(<<EOM);
	ec_gen_text(<<EOM);

// ======== MACRO: gen_2level_browse_funcs    $root1 $key1 $root2 $key2

\@func ${root2}_${key1}_modify(\@^${root1}) {
    if (\@*__${key1}.get_rownum()) {
	isdk_form_hide(\@^);
	isdk_form_reset(\@^${root2}_${key1}_${key2});
	\@\$${root2}_${key1}_${key2}:__${key1}=\@\$__${key1};
	\@\$${root2}_${key1}_${key2}:_filter=\@\$__${key1}\[$col\];
	isdk_debug("1:%s, 2:%s\n",\@\$__${key1}\[$col\], @\$${root2}_${key1}_${key2}:_filter);
	db_browser_init(conn, \@*${root2}_${key1}_${key2}:__${key2}, "${query}"${q});
	\@\$${root2}_${key1}_${key2}:_last_form=\@^->name;
	isdk_form_show(\@^${root2}_${key1}_${key2});
    }
}

//\@func ${root2}_${key1}_${key2}_cancel {
//    isdk_form_hide(\@^);
//    isdk_form_show(\@^${root1});
//}

\@func ${root2}_${key1}_new(\@^${root2}_${key1}_${key2}) {
    isdk_form_reset(\@^${root2}_edit);
    \@\$${root2}_edit:_new="t";
EOM
	&ec_parse_block;
#	ec_write_out(<<EOM);
	ec_gen_text(<<EOM);
    \@\$${root2}_edit:_last_form=\@^->name;
    isdk_form_hide(\@^);
    \@\$${root2}_edit:${key1}=\@\$__${key1};
    \@\*${root2}_edit:${key1}.set_sens(false);
    isdk_form_show(\@^${root2}_edit);
}
EOM
    } else {
	ec_error("Parse error");
    }
}
