% Natural Language Parser for Scribbler II Robot % by Justin Mangue, 2013 % % Uses DCG grammar parsing to parse natural language input into Python code, to be executed on a Scribbler II robot. % % References used: % * "The Art of Prolog", Sterling & Shapiro, 1986. % ParseToCode is the main routine. Takes a sentence and converts it to a list of executable python code. % Call with parseToCode([sentence,as,a,list],Status,Code). parseToCode(Sentence, Status, CodeListOut) :- compound_sentence(ParseList, Sentence, []), Status = valid, generate_code_list(ParseList, [], CodeListOut), !. parseToCode(Sentence, Status, CodeOut) :- + compound_sentence(_, Sentence, []), Status = invalid_sentence, CodeOut = 'null', !. %%% GRAMMAR DEFINITIONS %%% % Compound Sentence is the top level. Consists of an optional address statement followed by one or more simple sentences joined by a connective. compound_sentence(Compound) --> optionally_address(robot), simple_sentence(Command), connective(and), compound_sentence(Sentence), { append([Command], [Sentence], Compound_NotFlat) }, { flatten2(Compound_NotFlat, Compound) }. compound_sentence(Command) --> optionally_address(robot), simple_sentence(Command). % A simple sentence is a command phrase, possibly followed by an optional repetition clause. simple_sentence([loop(C,T)]) --> command_phrase(C), (to_number(T), [times] ; to_number_eng(T)). simple_sentence([Simple]) --> command_phrase(Simple). % Command phrases are the basic command-level structure. Most consist of an action and some sort of argument. command_phrase(move(D,S)) --> action(go), direction(D), optional(for), unit_to_seconds(S). command_phrase(move(D,S)) --> action(go), unit_to_seconds(S), direction(D). command_phrase(move(forward,S)) --> action(go), unit_to_seconds(S). command_phrase(move(D,3)) --> action(go), direction(D). command_phrase(turn(D,S)) --> action(turn), optional(to), (optional(the) ; optional(your)), direction(D), unit_to_seconds(S). command_phrase(turn(D,S)) --> action(turn), optional(to), (optional(the) ; optional(your)), direction(D), { S is 90*(3.25 / 360) }. % 90 degrees by default command_phrase(turn(right,D)) --> action(turn), direction(around), { D is 180 * (3.25 / 360) }. % 180 degree turn command_phrase(turn(Dir,D)) --> action(spin), direction(around), optional(to), (optional(the) ; optional(your)), direction(Dir), { D is 3.25 }. % 360 degree turn command_phrase(turn(right,D)) --> action(spin), direction(around), { D is 3.25 }. % 360 degree turn command_phrase(pic(Mode)) --> action(take), adposition(a), photomode(Mode), object(picture). command_phrase(pic(0)) --> % b&w pic by default, for speed reasons action(take), adposition(a), object(picture). command_phrase(wait(S)) --> action(wait), optional(for), unit_to_seconds(S). command_phrase(wait(3.0)) --> action(wait). command_phrase(beep) --> action(beep). command_phrase(moonwalk(S)) --> action(moonwalk), optional(for), unit_to_seconds(S). command_phrase(moonwalk(4.0)) --> action(moonwalk). command_phrase(move_until_wall(D)) --> command_phrase(move(D,_)), condition(until), [you], condition(encounter), object(wall). % Vocabulary/synonym definitions action(go) --> [go] ; [move] ; [drive] ; [roll] ; [scoot]. action(turn) --> [turn] ; [rotate] ; [swivel]. action(spin) --> [spin]. action(take) --> [take] ; [obtain] ; [get] ; [snap]. action(wait) --> [wait] ; [pause] ; [stop]. action(beep) --> [beep]. action(moonwalk) --> [moonwalk]. direction(forward) --> [forward] ; [forwards] ; [ahead] ; [up]. direction(backward) --> [backward] ; [backwards] ; [back]. direction(left) --> [left] ; [counter-clockwise] ; [counter],[clockwise]. direction(right) --> [right] ; [clockwise]. direction(around) --> [around] ; [in],adposition(a),[circle]. adposition(a) --> [a] ; [one]. condition(until) --> [until]. condition(encounter) --> [encounter] ; [reach] ; [sense] ; [hit]. photomode(0) --> [grayscale] ; [greyscale] ; [gray] ; [grey] ; [black],[and],[white] ; [black],[&],[white]. photomode(1) --> [color]. object(picture) --> [photo] ; [picture] ; [pic] ; [snapshot]. object(wall) --> [a],[wall] ; [the],[wall] ; [an],[obstacle] ; [something]. pronoun(robot) --> [robot] ; [scribbler]. connective(and) --> [and] ; [then] ; [comma] ; [and], [then] ; [comma], [then] ; [comma], [and], [then]. % Optional form of address optionally_address(robot) --> (optional(robot) ; optional(scribbler)), optional(comma), optional(please). % Allow a DCG word to be optional optional(X) --> [X] ; []. % Number parsing predicates % English number recognition from 0.0 - 9999.9 to_number(N) --> num(N1), [point], digit(N2), { N is N1 + (0.1 * N2) }. to_number(N) --> num(N). to_number_eng(1) --> [once]. to_number_eng(2) --> [twice]. to_number_eng(3) --> [thrice]. num(0) --> [zero]. num(N) --> xxxx(N). num(N) --> xxx(N). num(N) --> xx(N). num(N) --> digit(N). num(N) --> [N], { number(N) }. xxxx(N) --> digit(D), [thousand], xxx(N1), { N is D*1000+N1 }. xxx(N) --> digit(D), [hundred], rest_xxx(N1), { N is D*100+N1 }. rest_xxx(0) --> []. rest_xxx(N) --> [and], xx(N). rest_xxx(N) --> xx(N). xx(N) --> digit(N). xx(N) --> teen(N). xx(N) --> tens(T), rest_xx(N1), { N is T+N1 }. rest_xx(0) --> []. rest_xx(N) --> digit(N). digit(1) --> [one]. digit(2) --> [two]. digit(3) --> [three]. digit(4) --> [four]. digit(5) --> [five]. digit(6) --> [six]. digit(7) --> [seven]. digit(8) --> [eight]. digit(9) --> [nine]. teen(10) --> [ten]. teen(11) --> [eleven]. teen(12) --> [twelve]. teen(13) --> [thirteen]. teen(14) --> [fourteen]. teen(15) --> [fifteen]. teen(16) --> [sixteen]. teen(17) --> [seventeen]. teen(18) --> [eighteen]. teen(19) --> [nineteen]. tens(20) --> [twenty]. tens(30) --> [thirty]. tens(40) --> [forty]. tens(50) --> [fifty]. tens(60) --> [sixty]. tens(70) --> [seventy]. tens(80) --> [eighty]. tens(90) --> [ninety]. % Scribbler Unit conversions % All of the Myro commands for Scribbler are expressed in seconds, so conversions of other unit types to seconds are required. % Seconds -> Seconds unit_to_seconds(S) --> to_number(S), { + S = 1 }, [seconds] ; to_number(S), { S = 1 }, [second]. % Milliseconds -> Seconds unit_to_seconds(MS) --> to_number(S), { + S = 1 }, { MS is (S / 1000) }, [milliseconds] ; to_number(S), { S = 1 }, { MS is (S / 1000) },[millisecond]. % Feet -> Seconds unit_to_seconds(S) --> to_number(Feet), { + Feet = 1 }, { S is Feet * 2.05 }, [feet] ; to_number(Feet), { Feet = 1 }, { S is 2.05 }, [foot]. % Inches -> Seconds unit_to_seconds(S) --> to_number(Inches), { + Inches = 1 }, { S is Inches * (2.05 / 12) }, [inches] ; to_number(Inches), { Inches = 1 }, { S is (2.05 / 12) }, [inch]. % Degrees -> Seconds unit_to_seconds(S) --> to_number(Degrees), { + Degrees = 1 }, { S is Degrees * (3.25 / 360) }, ([degrees] ; [º]) ; to_number(Degrees), { Degrees = 1 }, { S is (3.25 / 360) }, ([degree] ; [º]). %%% CODE GENERATION %%% % The top level structure is a code list, which is a list of one or more code fragments to be executed. % This part is pretty messy, but is needed to handle passing instructions back to Python from Prolog. generate_code_list([], CodeList, CodeList). generate_code_list([H|T], CodeList, CodeListOut) :- generate_code(H, CodeLineOut), append(CodeList, CodeLineOut, NewCodeList), generate_code_list(T, NewCodeList, CodeListOut). generate_code(move(forward,S), CodeOut) :- string_to_atom(Keyword, forward), CodeOut = [[Keyword,1.0,S]], !. generate_code(move(backward,S), CodeOut) :- string_to_atom(Keyword, backward), CodeOut = [[Keyword,1.0,S]], !. generate_code(move(left,S), CodeOut) :- string_to_atom(Keyword1, turnLeft), string_to_atom(Keyword2, forward), Ninety is 90*(3.25 / 360), CodeOut = [[Keyword1,1.0,Ninety],[Keyword2,1.0,S]], !. generate_code(move(right,S), CodeOut) :- string_to_atom(Keyword1, turnRight), string_to_atom(Keyword2, forward), Ninety is 90*(3.25 / 360), CodeOut = [[Keyword1,1.0,Ninety],[Keyword2,1.0,S]], !. generate_code(turn(left,S), CodeOut) :- string_to_atom(Keyword, turnLeft), CodeOut = [[Keyword,1.0,S]], !. generate_code(turn(right,S), CodeOut) :- string_to_atom(Keyword, turnRight), CodeOut = [[Keyword,1.0,S]], !. generate_code(wait(S), CodeOut) :- string_to_atom(Keyword, wait), CodeOut = [[Keyword,S]], !. generate_code(pic(Mode), CodeOut) :- string_to_atom(Keyword, takePhoto), CodeOut = [[Keyword,Mode]], !. generate_code(beep, CodeOut) :- string_to_atom(Keyword, beep), CodeOut = [[Keyword,0.4, 640]], !. generate_code(loop(C,T), CodeOut) :- generate_code(C,Code), append_n_times(Code,[],T,CodeOut), !. generate_code(move_until_wall(forward), CodeOut) :- string_to_atom(Keyword, move_until_wall), CodeOut = [[Keyword]], !. generate_code(move_until_wall(left), CodeOut) :- string_to_atom(Keyword1, turnLeft), string_to_atom(Keyword2, move_until_wall), Ninety is 90*(3.25 / 360), CodeOut = [[Keyword1,1.0,Ninety],[Keyword2]], !. generate_code(move_until_wall(right), CodeOut) :- string_to_atom(Keyword1, turnRight), string_to_atom(Keyword2, move_until_wall), Ninety is 90*(3.25 / 360), CodeOut = [[Keyword1,1.0,Ninety],[Keyword2]], !. generate_code(move_until_wall(backward), CodeOut) :- string_to_atom(Keyword1, turnRight), string_to_atom(Keyword2, move_until_wall), string_to_atom(Keyword3, turnLeft), OneEighty is 180*(3.25 / 360), CodeOut = [[Keyword1,1.0,OneEighty],[Keyword2],[Keyword3,1.0,OneEighty]], !. generate_code(moonwalk(S), CodeOut) :- string_to_atom(Keyword, moonwalk), CodeOut = [[Keyword,S]], !. %%% MISC SUPPORT PREDICATES %%% % Check if S is a sublist of L sublist(S, L) :- append(_, L2, L), append(S, _, L2). % Builds a list of N instances of item C, then returns the result in L append_n_times(_,L,0,L):- !. append_n_times(C,L,N,Result):- append(L,C,NewL), NewN is N-1, append_n_times(C,NewL,NewN,Result). % Flatten a list of lists flatten2([], []) :- !. flatten2([L|Ls], FlatL) :- !, flatten2(L, NewL), flatten2(Ls, NewLs), append(NewL, NewLs, FlatL). flatten2(L, [L]).