Browse Source

parse: Parsing for excution and completion

Serj Kalichev 2 years ago
parent
commit
c28227c898
6 changed files with 163 additions and 45 deletions
  1. 21 7
      bin/klishd/klishd.c
  2. 18 1
      klish/kpargv.h
  3. 4 4
      klish/kscheme/kentry.c
  4. 2 1
      klish/ksession.h
  5. 35 11
      klish/ksession/kpargv.c
  6. 83 21
      klish/ksession/ksession_parse.c

+ 21 - 7
bin/klishd/klishd.c

@@ -176,24 +176,38 @@ int main(int argc, char **argv)
 
 	// Parsing
 	{
-	const char *line = "cmd o1 m7 o2 e1";
+//	const char *line = "cmd o4 m7 o2 e1";
+	const char *line = "cmd o4 o4 o4 m3 o2 e1 ";
 	kpargv_t *pargv = NULL;
 	kpargv_pargs_node_t *p_iter = NULL;
 	
 	session = ksession_new(scheme, "/lowview");
 	kpath_push(ksession_path(session), klevel_new(kscheme_find_entry_by_path(scheme, "/main")));
-	pargv = ksession_parse_line(session, line);
+	pargv = ksession_parse_line(session, line, KPURPOSE_COMPLETION);
 	if (pargv) {
 		printf("Level: %lu, Command: %s, Line '%s': %s\n",
-			kpargv_level(pargv), kentry_name(kpargv_command(pargv)),
-			line, kpargv_status_str(pargv));
+			kpargv_level(pargv),
+			kpargv_command(pargv) ? kentry_name(kpargv_command(pargv)) : "<none>",
+			line,
+			kpargv_status_str(pargv));
 
 		kparg_t *parg = NULL;
 		p_iter = kpargv_pargs_iter(pargv);
-		while ((parg = kpargv_pargs_each(&p_iter))) {
-			printf("%s(%s) ", kparg_value(parg), kentry_name(kparg_entry(parg)));
+		if (kpargv_pargs_len(pargv) > 0) {
+			while ((parg = kpargv_pargs_each(&p_iter))) {
+				printf("%s(%s) ", kparg_value(parg), kentry_name(kparg_entry(parg)));
+			}
+			printf("\n");
+		}
+
+		// Completions
+		if (!kpargv_completions_is_empty(pargv)) {
+			kentry_t *completion = NULL;
+			kpargv_completions_node_t *citer = kpargv_completions_iter(pargv);
+			printf("Completions (%s):\n", kpargv_last_arg(pargv));
+			while ((completion = kpargv_completions_each(&citer)))
+				printf("* %s\n", kentry_name(completion));
 		}
-		printf("\n");
 	}
 
 	kpargv_free(pargv);

+ 18 - 1
klish/kpargv.h

@@ -18,9 +18,19 @@ typedef enum {
 	KPARSE_INCOMPLETED,
 	KPARSE_ILLEGAL,
 	KPARSE_ERROR,
+	KPARSE_MAX,
 } kpargv_status_e;
 
 
+typedef enum {
+	KPURPOSE_NONE,
+	KPURPOSE_EXEC,
+	KPURPOSE_COMPLETION,
+	KPURPOSE_HELP,
+	KPURPOSE_MAX,
+} kpargv_purpose_e;
+
+
 typedef struct kpargv_s kpargv_t;
 typedef struct kparg_s kparg_t;
 
@@ -47,6 +57,7 @@ void kpargv_free(kpargv_t *pargv);
 // Status
 kpargv_status_e kpargv_status(const kpargv_t *pargv);
 bool_t kpargv_set_status(kpargv_t *pargv, kpargv_status_e status);
+const char *kpargv_status_decode(kpargv_status_e status);
 const char *kpargv_status_str(const kpargv_t *pargv);
 // Level
 size_t kpargv_level(const kpargv_t *pargv);
@@ -57,6 +68,12 @@ bool_t kpargv_set_command(kpargv_t *pargv, const kentry_t *command);
 // Continuable
 bool_t kpargv_continuable(const kpargv_t *pargv);
 bool_t kpargv_set_continuable(kpargv_t *pargv, bool_t continuable);
+// Purpose
+kpargv_purpose_e kpargv_purpose(const kpargv_t *pargv);
+bool_t kpargv_set_purpose(kpargv_t *pargv, kpargv_purpose_e purpose);
+// Last argument
+bool_t kpargv_set_last_arg(kpargv_t *pargv, const char *last_arg);
+const char *kpargv_last_arg(const kpargv_t *pargv);
 
 // Pargs
 faux_list_t *kpargv_pargs(const kpargv_t *pargv);
@@ -72,7 +89,7 @@ kparg_t *kpargv_entry_exists(const kpargv_t *pargv, const void *entry);
 faux_list_t *kpargv_completions(const kpargv_t *pargv);
 bool_t kpargv_add_completions(kpargv_t *pargv, kentry_t *completion);
 ssize_t kpargv_completions_len(const kpargv_t *pargv);
-bool_t kpargv_compleions_is_empty(const kpargv_t *pargv);
+bool_t kpargv_completions_is_empty(const kpargv_t *pargv);
 kpargv_completions_node_t *kpargv_completions_iter(const kpargv_t *pargv);
 kentry_t *kpargv_completions_each(kpargv_completions_node_t **iter);
 

+ 4 - 4
klish/kscheme/kentry.c

@@ -201,15 +201,15 @@ bool_t kentry_link(kentry_t *dst, const kentry_t *src)
 	// name - orig
 	// help - orig
 	// parent - orig
-	dst->container = src->container;
+	// container - orig
 	dst->mode = src->mode;
-	dst->min = src->min;
-	dst->max = src->max;
+	// min - orig
+	// max - orig
 	dst->ptype_str = src->ptype_str;
 	dst->ptype = src->ptype;
 	// ref_str - orig
 	// value - orig
-	dst->restore = src->restore;
+	// restore - orig
 	dst->entrys = src->entrys;
 	dst->actions = src->actions;
 

+ 2 - 1
klish/ksession.h

@@ -23,7 +23,8 @@ void ksession_free(ksession_t *session);
 const kscheme_t *ksession_scheme(const ksession_t *session);
 kpath_t *ksession_path(const ksession_t *session);
 
-kpargv_t *ksession_parse_line(ksession_t *session, const char *line);
+kpargv_t *ksession_parse_line(ksession_t *session, const char *line,
+	kpargv_purpose_e purpose);
 
 C_DECL_END
 

+ 35 - 11
klish/ksession/kpargv.c

@@ -18,9 +18,11 @@ struct kpargv_s {
 	size_t level; // Number of path's level where command was found
 	const kentry_t *command; // ENTRY that consider as command (has ACTIONs)
 	bool_t continuable; // Last argument can be expanded
+	kpargv_purpose_e purpose; // Exec/Completion/Help
+	char *last_arg;
 };
 
-// Level
+// Status
 KGET(pargv, kpargv_status_e, status);
 KSET(pargv, kpargv_status_e, status);
 
@@ -36,6 +38,14 @@ KSET(pargv, const kentry_t *, command);
 KGET_BOOL(pargv, continuable);
 KSET_BOOL(pargv, continuable);
 
+// Purpose
+KGET(pargv, kpargv_purpose_e, purpose);
+KSET(pargv, kpargv_purpose_e, purpose);
+
+// Last argument
+KSET_STR(pargv, last_arg);
+KGET_STR(pargv, last_arg);
+
 // Pargs
 KGET(pargv, faux_list_t *, pargs);
 KADD_NESTED(pargv, kparg_t *, pargs);
@@ -76,12 +86,20 @@ kpargv_t *kpargv_new()
 	pargv->status = KPARSE_NONE;
 	pargv->level = 0;
 	pargv->command = NULL;
+	pargv->continuable = BOOL_FALSE;
+	pargv->purpose = KPURPOSE_EXEC;
+	pargv->last_arg = NULL;
 
 	// Parsed arguments list
 	pargv->pargs = faux_list_new(FAUX_LIST_UNSORTED, FAUX_LIST_NONUNIQUE,
 		NULL, kpargv_pargs_kcompare, (void (*)(void *))kparg_free);
 	assert(pargv->pargs);
 
+	// Completions
+	pargv->completions = faux_list_new(FAUX_LIST_UNSORTED, FAUX_LIST_NONUNIQUE,
+		NULL, NULL, NULL);
+	assert(pargv->completions);
+
 	return pargv;
 }
 
@@ -91,7 +109,10 @@ void kpargv_free(kpargv_t *pargv)
 	if (!pargv)
 		return;
 
+	faux_str_free(pargv->last_arg);
+
 	faux_list_free(pargv->pargs);
+	faux_list_free(pargv->completions);
 
 	free(pargv);
 }
@@ -122,18 +143,11 @@ kparg_t *kpargv_entry_exists(const kpargv_t *pargv, const void *entry)
 }
 
 
-const char *kpargv_status_str(const kpargv_t *pargv)
+const char *kpargv_status_decode(kpargv_status_e status)
 {
 	const char *s = "Unknown";
 
-	assert(pargv);
-	if (!pargv)
-		return NULL;
-
-	switch (kpargv_status(pargv)) {
-	case KPARSE_NONE:
-		s = "None";
-		break;
+	switch (status) {
 	case KPARSE_OK:
 		s = "Ok";
 		break;
@@ -149,10 +163,20 @@ const char *kpargv_status_str(const kpargv_t *pargv)
 	case KPARSE_ILLEGAL:
 		s = "Illegal";
 		break;
-	case KPARSE_ERROR:
+	default: // ERROR/MAX/NONE
 		s = "Error";
 		break;
 	}
 
 	return s;
 }
+
+
+const char *kpargv_status_str(const kpargv_t *pargv)
+{
+	assert(pargv);
+	if (!pargv)
+		return NULL;
+
+	return kpargv_status_decode(kpargv_status(pargv));
+}

+ 83 - 21
klish/ksession/ksession_parse.c

@@ -42,8 +42,10 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 {
 	kentry_t *entry = current_entry;
 	kentry_mode_e mode = KENTRY_MODE_NONE;
-	kpargv_status_e retcode = KPARSE_NOTFOUND; // For ENTRY itself
+	kpargv_status_e retcode = KPARSE_INPROGRESS; // For ENTRY itself
 	kpargv_status_e rc = KPARSE_NOTFOUND; // For nested ENTRYs
+	faux_argv_node_t *saved_argv_iter = NULL;
+	kpargv_purpose_e purpose = KPURPOSE_NONE;
 
 	assert(current_entry);
 	if (!current_entry)
@@ -55,14 +57,43 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 	if (!pargv)
 		return KPARSE_ERROR;
 
-	// If all arguments are resolved already then return INCOMPLETED
-	if (!*argv_iter)
-		return KPARSE_INCOMPLETED;
+	purpose = kpargv_purpose(pargv);
 
 	// Is entry candidate to resolve current arg?
 	// Container can't be a candidate.
 	if (!kentry_container(entry)) {
-		const char *current_arg = faux_argv_current(*argv_iter);
+		const char *current_arg = NULL;
+
+//printf("arg: %s, entry: %s\n", *argv_iter ? faux_argv_current(*argv_iter) : "<empty>",
+//	kentry_name(entry));
+
+		// When purpose is COMPLETION or HELP then fill completion list.
+		// Additionally if it's last continuable argument then lie to
+		// engine: make all last arguments NOTFOUND. It's necessary to walk
+		// through all variants to gether all completions.
+		if ((KPURPOSE_COMPLETION == purpose) ||
+			(KPURPOSE_HELP == purpose)) {
+			if (!*argv_iter) {
+				// That's time to add entry to completions list.
+				if (!kpargv_continuable(pargv))
+					kpargv_add_completions(pargv, entry);
+				return KPARSE_INCOMPLETED;
+			} else {
+				// Add entry to completions if it's last incompleted arg.
+				if (faux_argv_is_last(*argv_iter) &&
+					kpargv_continuable(pargv)) {
+					kpargv_add_completions(pargv, entry);
+					return KPARSE_NOTFOUND;
+				}
+			}
+		}
+
+		// If all arguments are resolved already then return INCOMPLETED
+		if (!*argv_iter)
+			return KPARSE_INCOMPLETED;
+
+		// Validate argument
+		current_arg = faux_argv_current(*argv_iter);
 		if (ksession_validate_arg(entry, current_arg)) {
 			kparg_t *parg = kparg_new(entry, current_arg);
 			kpargv_add_pargs(pargv, parg);
@@ -82,26 +113,38 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 	if (kentry_entrys_is_empty(entry))
 		return retcode;
 
-	// Walk through the nested entries
+	// Walk through the nested entries:
+	saved_argv_iter = *argv_iter;
+
+	// EMPTY mode
 	mode = kentry_mode(entry);
 	if (KENTRY_MODE_EMPTY == mode)
 		return retcode;
 
-	// SWITCH
+	// SWITCH mode
 	// Entries within SWITCH can't has 'min'/'max' else than 1.
 	// So these attributes will be ignored. Note SWITCH itself can have
 	// 'min'/'max'.
 	if (KENTRY_MODE_SWITCH == mode) {
 		kentry_entrys_node_t *iter = kentry_entrys_iter(entry);
 		kentry_t *nested = NULL;
+
 		while ((nested = kentry_entrys_each(&iter))) {
+printf("SWITCH arg: %s, entry %s\n", *argv_iter ? faux_argv_current(*argv_iter) : "<empty>", kentry_name(nested));
 			rc = ksession_parse_arg(nested, argv_iter, pargv);
-			// Any variant of error or INPROGRESS
-			if (rc != KPARSE_NOTFOUND)
+printf("%s\n", kpargv_status_decode(rc));
+			// If some arguments was consumed then we will not check
+			// next SWITCH's entries in any case.
+			if (saved_argv_iter != *argv_iter)
+				break;
+			// Try next entries if current status is NOTFOUND.
+			// The INCOMPLETED status is for completion list. In this
+			// case all next statuses will be INCOMPLETED too.
+			if ((rc != KPARSE_NOTFOUND) && (rc != KPARSE_INCOMPLETED))
 				break;
 		}
 
-	// SEQUENCE
+	// SEQUENCE mode
 	} else if (KENTRY_MODE_SEQUENCE == mode) {
 		kentry_entrys_node_t *iter = kentry_entrys_iter(entry);
 		kentry_entrys_node_t *saved_iter = iter;
@@ -112,25 +155,31 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 			size_t num = 0;
 			size_t min = kentry_min(nested);
 
-//printf("Arg: %s, entry %s\n", faux_argv_current(*argv_iter), kentry_name(nested));
 			// Filter out double parsing for optional entries.
 			if (kpargv_entry_exists(pargv, nested))
 				continue;
 			// Try to match argument and current entry
 			// (from 'min' to 'max' times)
 			for (num = 0; num < kentry_max(nested); num++) {
+printf("SEQ arg: %s, entry %s\n", *argv_iter ? faux_argv_current(*argv_iter) : "<empty>", kentry_name(nested));
 				nrc = ksession_parse_arg(nested, argv_iter, pargv);
+printf("%s\n", kpargv_status_decode(nrc));
 				if (nrc != KPARSE_INPROGRESS)
 					break;
 			}
 			// All errors will break the loop
-			if ((nrc != KPARSE_INPROGRESS) && (nrc != KPARSE_NOTFOUND)) {
+			if ((KPARSE_ERROR == nrc) ||
+				(KPARSE_ILLEGAL == nrc) ||
+				(KPARSE_NONE == nrc)) {
 				rc = nrc;
 				break;
 			}
-			// Not found all mandatory instances (NOTFOUND)
+			// Not found necessary number of mandatory instances
 			if (num < min) {
-				rc = KPARSE_NOTFOUND;
+				if (KPARSE_INPROGRESS == nrc)
+					rc = KPARSE_NOTFOUND;
+				else
+					rc = nrc; // NOTFOUND or INCOMPLETED
 				break;
 			}
 			// It's not an error if optional parameter is absend
@@ -146,20 +195,21 @@ static kpargv_status_e ksession_parse_arg(kentry_t *current_entry,
 			if ((0 == min) && (num > 0))
 				iter = saved_iter;
 		}
-	
 	}
 
-	// When ENTRY (not container) is found but mandatory nested ENTRY is
-	// not resolved. It's inconsistent. So NOTFOUND is not suitable in
-	// this case.
-	if ((KPARSE_NOTFOUND == rc) && (KPARSE_INPROGRESS == retcode))
-		return KPARSE_ILLEGAL;
+	// If nested result is NOTFOUND but argument was consumed
+	// within nested entries or by entry itself then whole sequence
+	// is ILLEGAL.
+	if ((KPARSE_NOTFOUND == rc) &&
+		((saved_argv_iter != *argv_iter) || !kentry_container(entry)))
+		rc = KPARSE_ILLEGAL;
 
 	return rc;
 }
 
 
-kpargv_t *ksession_parse_line(ksession_t *session, const char *line)
+kpargv_t *ksession_parse_line(ksession_t *session, const char *line,
+	kpargv_purpose_e purpose)
 {
 	faux_argv_t *argv = NULL;
 	faux_argv_node_t *argv_iter = NULL;
@@ -188,9 +238,12 @@ kpargv_t *ksession_parse_line(ksession_t *session, const char *line)
 	}
 	argv_iter = faux_argv_iter(argv);
 
+	// Initialize kpargv_t
 	pargv = kpargv_new();
 	assert(pargv);
 	kpargv_set_continuable(pargv, faux_argv_is_continuable(argv));
+	kpargv_set_purpose(pargv, purpose);
+
 	// Iterate levels of path from higher to lower. Note the reversed
 	// iterator will be used.
 	path = ksession_path(session);
@@ -201,8 +254,17 @@ kpargv_t *ksession_parse_line(ksession_t *session, const char *line)
 		pstatus = ksession_parse_arg(current_entry, &argv_iter, pargv);
 		if (pstatus != KPARSE_NOTFOUND)
 			break;
+		// NOTFOUND but some args were parsed.
+		// When it's completion for first argument (that can be continued)
+		// len == 0 and engine will search for completions on higher
+		// levels of path.
+		if (kpargv_pargs_len(pargv) > 0)
+			break;
 		level_found--;
 	}
+	// Save last argument
+	if (argv_iter)
+		kpargv_set_last_arg(pargv, faux_argv_current(argv_iter));
 	// It's a higher level of parsing, so some statuses can have different
 	// meanings
 	if (KPARSE_NONE == pstatus)