/* Copyright (C) 1996 Bjoern Beutel. */
/* Description. =============================================================*/
/* This program reads a project file and compiles all symbol files, rule files,
* and lexicon files that must be updated. */
/* Includes. ================================================================*/
#define _POSIX_SOURCE
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <setjmp.h>
#include <glib.h>
#include "basic.h"
#include "files.h"
#include "malaga_files.h"
#include "input.h"
#ifdef POSIX
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#endif
#ifdef WIN32
#include <process.h>
#include <sys/stat.h>
#endif
/* Types. ===================================================================*/
typedef struct /* A file name on which a malaga file depends. */
{
list_node_t *next; /* Next dependency in this list. */
string_t name; /* File name of dependency. */
} dependency_t;
typedef struct /* Source file names and object file name of a Malaga file. */
{
list_t dependencies;
string_t source_file;
string_t object_file;
} files_t;
/* Variables. ===============================================================*/
/* The names of the files needed by Malaga. */
static files_t symbol_files;
static files_t extended_symbol_files;
static files_t lexicon_files;
static files_t allomorph_files;
static files_t morphology_files;
static files_t syntax_files;
static string_t prelex_file;
/* Functions. ===============================================================*/
static void
execute( string_t command_name, string_t arg1, string_t arg2, string_t arg3,
string_t file_name )
/* Execute a process and wait for successful termination. */
{
#ifdef POSIX
pid_t pid;
int status;
if (file_name == NULL)
file_name = arg1;
fprintf( stderr, "Compiling \"%s\".\n", name_in_path( file_name ) );
switch (pid = fork())
{
case -1:
complain( "Can't execute \"%s\".", command_name );
break;
case 0:
execlp( command_name, command_name, arg1, arg2, arg3, NULL );
complain( "Can't execute \"%s\".", command_name );
break;
default:
waitpid( pid, &status, 0 );
if (! WIFEXITED( status ) || WEXITSTATUS( status ) != 0)
complain( "Compilation failed." );
break;
}
#endif
#ifdef WIN32
int status;
string_t a1, a2, a3;
a1 = (arg1 != NULL ? new_string_readable( arg1, NULL ) : NULL);
a2 = (arg2 != NULL ? new_string_readable( arg2, NULL ) : NULL);
a3 = (arg3 != NULL ? new_string_readable( arg3, NULL ) : NULL);
if (file_name == NULL)
file_name = arg1;
fprintf( stderr, "Compiling \"%s\".\n", name_in_path( file_name ) );
status = _spawnlp( P_WAIT, command_name, command_name, a1, a2, a3, NULL );
if (status == -1)
complain( "Can't execute \"%s\".", command_name );
if (status != 0)
complain( "Compilation failed." );
free( a1 ); free( a2 ); free( a3 );
#endif
}
/*---------------------------------------------------------------------------*/
static void
free_files( files_t *files )
/* Free the space occupied by FILES. */
{
dependency_t *file;
free_mem( &files->source_file );
free_mem( &files->object_file );
FOREACH_FREE( file, files->dependencies )
free_mem( &file->name );
}
/*---------------------------------------------------------------------------*/
static void
free_all_files( void )
/* Free the space occupied by the file names. */
{
free_files( &syntax_files );
free_files( &morphology_files );
free_files( &lexicon_files );
free_files( &extended_symbol_files );
free_files( &symbol_files );
free_files( &allomorph_files );
}
/*---------------------------------------------------------------------------*/
static time_t
file_time( string_t file_name )
/* Get the last modification time of FILE_NAME. */
{
struct stat status;
/* Assume earliest possible time for non-existing files. */
if (stat( file_name, &status ) == 0)
return status.st_mtime;
else
return 0;
}
/*---------------------------------------------------------------------------*/
static void
read_project_file( string_t project_file )
/* Read the project file. */
{
FILE *project_stream;
string_t project_line_p, argument, extension;
char_t *project_line;
files_t *files;
dependency_t *dependency;
string_t s;
volatile int_t line_count;
static bool_t err_pos_printed;
err_pos_printed = FALSE;
project_stream = open_stream( project_file, "r" );
line_count = 0;
while (TRUE)
{
project_line = read_line( project_stream );
if (project_line == NULL)
break;
line_count++;
cut_comment( project_line );
project_line_p = project_line;
if (*project_line_p != EOS)
{
argument = NULL;
TRY
{
argument = parse_word( &project_line_p );
extension = NULL;
files = NULL;
if (strcmp_no_case( argument, "sym:" ) == 0)
{
extension = "sym";
files = &symbol_files;
}
else if (strcmp_no_case( argument, "esym:" ) == 0)
{
extension = "esym";
files = &extended_symbol_files;
}
else if (strcmp_no_case( argument, "all:" ) == 0)
{
extension = "all";
files = &allomorph_files;
}
else if (strcmp_no_case( argument, "lex:" ) == 0)
{
extension = "lex";
files = &lexicon_files;
}
else if (strcmp_no_case( argument, "mor:" ) == 0)
{
extension = "mor";
files = &morphology_files;
}
else if (strcmp_no_case( argument, "syn:" ) == 0)
{
extension = "syn";
files = &syntax_files;
}
else if (strcmp_no_case( argument, "prelex:" ) == 0)
{
s = parse_absolute_path( &project_line_p, project_file );
parse_end( &project_line_p );
if (! has_extension( s, "prelex" ))
{
complain( "\"%s\" should have extension \"prelex\".",
name_in_path( s ) );
}
set_binary_file_name( &prelex_file, s );
free_mem( &s );
}
else if (strcmp_no_case( argument, "split-hangul-syllables:" ) == 0)
{
split_hangul_syllables = parse_yes_no( &project_line_p );
parse_end( &project_line_p );
}
else if (strcmp_no_case( argument, "include:" ) == 0)
{
s = parse_absolute_path( &project_line_p, project_file );
parse_end( &project_line_p );
read_project_file( s );
free_mem( &s );
}
free_mem( &argument );
if (files != NULL)
{
/* Read the files in the current line
* and include them into the appropriate file_list. */
while (*project_line_p != EOS)
{
/* Read the next file name in the project file. */
argument = parse_absolute_path( &project_line_p, project_file );
/* Add file to dependency list . */
dependency = new_node( &files->dependencies,
sizeof( dependency_t ), LIST_END );
dependency->name = argument;
/* Set the object file if it's the first source file. */
if (files->source_file == NULL)
{
if (! has_extension( argument, extension ))
{
complain( "\"%s\" should have extension \"%s\".",
name_in_path( argument ), extension );
}
set_file_name( &files->source_file, argument );
set_binary_file_name( &files->object_file, argument );
if (! file_exists( files->source_file )
&& ! file_exists( files->object_file ))
{
complain( "Files \"%s\" and \"%s\" do not exist.",
name_in_path( files->source_file ),
name_in_path( files->object_file ) );
}
}
else if (file_exists( files->source_file )
&& ! file_exists( argument ))
{
/* The source file exists, but the include file doesn't. */
complain( "File \"%s\" does not exist.",
name_in_path( argument ) );
}
}
}
}
IF_ERROR
{
if (! err_pos_printed)
{
print_text( error_text, " (\"%s\", line %d)",
name_in_path( project_file ), line_count );
err_pos_printed = TRUE;
}
}
END_TRY;
}
free_mem( &project_line );
}
close_stream( &project_stream, project_file );
}
/*---------------------------------------------------------------------------*/
static bool_t
current_code_version( string_t file, string_t binary_file )
/* Return if FILE is compiled with the current code version. */
{
FILE *stream;
common_header_t header;
bool_t up_to_date;
int_t min_code_version, max_code_version;
struct stat status;
if (has_extension( file, "sym" ) || has_extension( file, "esym" ))
{
min_code_version = MIN_SYMBOL_CODE_VERSION;
max_code_version = SYMBOL_CODE_VERSION;
}
else if (has_extension( file, "lex" ))
{
min_code_version = MIN_LEXICON_CODE_VERSION;
max_code_version = LEXICON_CODE_VERSION;
}
else if (has_extension( file, "all" ) || has_extension( file, "mor" )
|| has_extension( file, "syn" ))
{
min_code_version = MIN_RULE_CODE_VERSION;
max_code_version = RULE_CODE_VERSION;
}
else
complain( "Internal error." );
/* Assume earliest possible time for non-existing files. */
if (stat( binary_file, &status ) == -1)
return FALSE;
stream = open_stream( binary_file, "rb" );
read_vector( &header, sizeof( header ), 1, stream, binary_file );
up_to_date = (header.code_version >= min_code_version
&& header.code_version <= max_code_version
&& header.split_hangul_syllables == split_hangul_syllables);
close_stream( &stream, binary_file );
return up_to_date;
}
/*---------------------------------------------------------------------------*/
static bool_t
is_obsolete( files_t *files, ... )
/* Check if FILES->OBJECT_FILE is older than one of FILES
* or one of the files given as optional arguments. */
{
va_list arg;
time_t object_time;
dependency_t *dependency;
string_t file_name;
bool_t obsolete;
if (! file_exists( files->source_file ))
return FALSE;
object_time = file_time( files->object_file );
obsolete = FALSE;
FOREACH( dependency, files->dependencies )
{
if (object_time < file_time( dependency->name ))
obsolete = TRUE;
}
va_start( arg, files );
for (file_name = va_arg( arg, string_t );
file_name != NULL;
file_name = va_arg( arg, string_t ))
{
if (object_time < file_time( file_name ))
obsolete = TRUE;
}
va_end( arg );
if (! current_code_version( files->source_file, files->object_file ))
obsolete = TRUE;
return obsolete;
}
/*---------------------------------------------------------------------------*/
int
main( int argc, char *argv[] )
/* The main function of "malmake". */
{
string_t project_file;
int_t i;
bool_t recompile;
init_basic( "malmake" );
init_input();
/* Parse arguments. */
if (argc == 2)
{
if (strcmp_no_case( argv[1], "--version" ) == 0
|| strcmp_no_case( argv[1], "-version" ) == 0
|| strcmp_no_case( argv[1], "-v" ) == 0)
{
program_message();
exit(0);
}
else if (strcmp_no_case( argv[1], "--help" ) == 0
|| strcmp_no_case( argv[1], "-help" ) == 0
|| strcmp_no_case( argv[1], "-h" ) == 0)
{
printf( "Compile all files of a Malaga grammar.\n\n"
"Usage:\n"
"malmake PROJECT_FILE "
"-- Compile files in PROJECT_FILE if needed.\n"
"malmake -v[ersion] "
"-- Print version information.\n"
"malmake -h[elp] "
"-- Print this help.\n\n"
"Option \"-n[ew]\" makes malmake recompile the whole project.\n"
"PROJECT_FILE must end on \".pro\".\n" );
exit(0);
}
}
project_file = NULL;
recompile = FALSE;
for (i = 1; i < argc; i++)
{
if (has_extension( argv[i], "pro" ))
set_file_name( &project_file, argv[i] );
else if (strcmp_no_case( argv[i], "-new" ) == 0
|| strcmp_no_case( argv[i], "-n" ) == 0)
{
recompile = TRUE;
}
else
complain( "Illegal argument \"%s\".", argv[i] );
}
if (project_file == NULL)
complain( "Missing project file name." );
read_project_file( project_file );
/* Test dependencies and compile files. */
if (symbol_files.source_file == NULL)
complain( "Missing symbol file names in project file." );
if (is_obsolete( &symbol_files, NULL ) || recompile)
{
execute( "malsym", symbol_files.source_file,
(split_hangul_syllables ? "-split-hangul-syllables" : NULL),
NULL, NULL );
}
if (allomorph_files.source_file == NULL)
complain( "Missing allomorph file names in project file." );
if (is_obsolete( &allomorph_files, symbol_files.object_file, NULL )
|| recompile)
{
execute( "malrul", allomorph_files.source_file,
symbol_files.source_file, NULL, NULL );
}
if (lexicon_files.source_file == NULL)
complain( "Missing lexicon file names in project file." );
if (is_obsolete( &lexicon_files, symbol_files.object_file,
allomorph_files.object_file, prelex_file, NULL )
|| recompile)
{
execute( "mallex", project_file, "-binary", NULL,
lexicon_files.source_file );
}
if (morphology_files.source_file != NULL
&& (is_obsolete( &morphology_files, symbol_files.object_file, NULL )
|| recompile))
{
execute( "malrul", morphology_files.source_file,
symbol_files.source_file, NULL, NULL );
}
if (extended_symbol_files.source_file != NULL)
{
if (is_obsolete( &extended_symbol_files, symbol_files.object_file, NULL )
|| recompile)
{
execute( "malsym", extended_symbol_files.source_file,
"-use", symbol_files.source_file, NULL );
}
}
else
{
extended_symbol_files.source_file
= new_string( symbol_files.source_file, NULL );
extended_symbol_files.object_file
= new_string( symbol_files.object_file, NULL );
}
if (syntax_files.source_file != NULL
&& (is_obsolete( &syntax_files, extended_symbol_files.object_file, NULL )
|| recompile))
{
execute( "malrul", syntax_files.source_file,
extended_symbol_files.source_file, NULL, NULL );
}
fprintf( stderr, "\"%s\" is up to date.\n", name_in_path( project_file ) );
free_all_files();
free_mem( &project_file );
terminate_input();
terminate_basic();
return 0;
}
/* End of file. =============================================================*/